Skip to content

Commit 547bfef

Browse files
Mbd06bclaude
andcommitted
feat(p2p): export P2PStatusInfo and DrainStatusInfo via ts-rs
The seeder's wait-for-drain helper previously hand-rolled the P2PStatusInfo / DrainStatusInfo interfaces locally -- exactly the kind of type-drift source the project has been burned by before. Adds TS derives to P2PStatusInfo, DrainStatusInfo, and ReplicationStatus so cargo test export_bindings generates canonical TypeScript types into the storage-client-ts sdk. Seeder now imports from the generated bindings instead of declaring its own. P2PStatusInfo intentionally keeps snake_case on the wire because multiple existing consumers (doorway federation/main/server routes, elohim-app connection-indicator, simulate.sh, genesis Jenkinsfile) already read snake_case field names from /p2p/status. Adding rename_all = "camelCase" would have been a breaking wire change for a polish commit. ts-rs emits the TypeScript type with snake_case field names to match. DrainStatusInfo and ReplicationStatus keep their existing camelCase (all their fields are single-word anyway). usize fields on P2PStatusInfo and ReplicationStatus use #[ts(type = "number")] to avoid the ts-rs bigint trap -- peer and row counts here fit comfortably in a JS number. Also fixes a latent bug in wait-for-drain: the log line referenced status.connectedPeers, but the wire format is connected_peers, so the peers suffix never rendered. Now uses the correct field. Seeder @elohim/storage-client dependency flipped from "^0.1.0" to "workspace:*" so pnpm actually resolves it from the monorepo workspace instead of whatever registry snapshot was pinned. Without this the new generated types would never reach the seeder's node_modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b2296c0 commit 547bfef

File tree

9 files changed

+117
-35
lines changed

9 files changed

+117
-35
lines changed

elohim/elohim-storage/src/p2p/mod.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ use libp2p::{
4747
tcp, yamux, Multiaddr, PeerId, SwarmBuilder,
4848
};
4949
use serde::Serialize;
50+
use ts_rs::TS;
5051
use sha2::{Digest, Sha256};
5152
use std::sync::Arc;
5253
use std::time::Duration;
@@ -219,8 +220,9 @@ pub struct P2PNode {
219220
/// Drain queue observability. Exposed via `P2PStatusInfo` so other peers can
220221
/// judge how busy/overloaded this node is and potentially route around it
221222
/// — not just for the local seeder's drain-complete check.
222-
#[derive(Debug, Clone, Serialize, Default)]
223+
#[derive(Debug, Clone, Serialize, Default, TS)]
223224
#[serde(rename_all = "camelCase")]
225+
#[ts(export, export_to = "../../sdk/storage-client-ts/src/generated/")]
224226
pub struct DrainStatusInfo {
225227
/// Total rows in the local content projection (scoped to lamad app).
226228
pub total: i32,
@@ -230,17 +232,28 @@ pub struct DrainStatusInfo {
230232
pub pending: i32,
231233
}
232234

233-
/// P2P node status for observability
234-
#[derive(Debug, Clone, Serialize)]
235+
/// P2P node status for observability.
236+
///
237+
/// NOTE: This struct intentionally does NOT use `rename_all = "camelCase"`
238+
/// because the wire format on `/p2p/status` has historically been snake_case
239+
/// and multiple consumers (doorway federation/main/server, elohim-app
240+
/// connection-indicator, simulate.sh, genesis Jenkinsfile) read snake_case
241+
/// field names. ts-rs will emit snake_case field names in the generated
242+
/// TypeScript type, preserving backward compatibility.
243+
#[derive(Debug, Clone, Serialize, TS)]
244+
#[ts(export, export_to = "../../sdk/storage-client-ts/src/generated/")]
235245
pub struct P2PStatusInfo {
236246
pub peer_id: String,
237247
pub listen_addresses: Vec<String>,
248+
#[ts(type = "number")]
238249
pub connected_peers: usize,
239250
pub bootstrap_nodes: Vec<String>,
251+
#[ts(type = "number")]
240252
pub sync_documents: usize,
241253
/// NAT status detected by autonat: "Unknown", "Public", "Private"
242254
pub nat_status: String,
243255
/// Number of active relay reservations
256+
#[ts(type = "number")]
244257
pub relay_reservations: usize,
245258
/// Addresses announced to the network
246259
pub announce_addresses: Vec<String>,

elohim/elohim-storage/src/p2p/replication.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ use serde::Serialize;
77
use std::collections::{HashMap, HashSet};
88
use std::sync::Arc;
99
use tokio::sync::RwLock;
10+
use ts_rs::TS;
1011

1112
/// Replication progress exposed via /p2p/status
12-
#[derive(Debug, Clone, Default, Serialize)]
13+
#[derive(Debug, Clone, Default, Serialize, TS)]
1314
#[serde(rename_all = "camelCase")]
15+
#[ts(export, export_to = "../../sdk/storage-client-ts/src/generated/")]
1416
pub struct ReplicationStatus {
1517
/// Content IDs discovered but not yet fetched
18+
#[ts(type = "number")]
1619
pub pending: usize,
1720
/// Content IDs successfully replicated
21+
#[ts(type = "number")]
1822
pub completed: usize,
1923
/// Content IDs that failed fetch (will retry)
24+
#[ts(type = "number")]
2025
pub failed: usize,
2126
/// True when all discovered content has been fetched or failed with max retries
2227
pub caught_up: bool,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2+
3+
/**
4+
* Drain queue observability. Exposed via `P2PStatusInfo` so other peers can
5+
* judge how busy/overloaded this node is and potentially route around it
6+
* — not just for the local seeder's drain-complete check.
7+
*/
8+
export type DrainStatusInfo = {
9+
/**
10+
* Total rows in the local content projection (scoped to lamad app).
11+
*/
12+
total: number,
13+
/**
14+
* Rows that have been successfully published to the libp2p Kad DHT.
15+
*/
16+
published: number,
17+
/**
18+
* Rows not yet drained. When this is 0 and stable, drain is caught up.
19+
*/
20+
pending: number, };
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2+
import type { DrainStatusInfo } from "./DrainStatusInfo";
3+
import type { ReplicationStatus } from "./ReplicationStatus";
4+
5+
/**
6+
* P2P node status for observability.
7+
*
8+
* NOTE: This struct intentionally does NOT use `rename_all = "camelCase"`
9+
* because the wire format on `/p2p/status` has historically been snake_case
10+
* and multiple consumers (doorway federation/main/server, elohim-app
11+
* connection-indicator, simulate.sh, genesis Jenkinsfile) read snake_case
12+
* field names. ts-rs will emit snake_case field names in the generated
13+
* TypeScript type, preserving backward compatibility.
14+
*/
15+
export type P2PStatusInfo = { peer_id: string, listen_addresses: Array<string>, connected_peers: number, bootstrap_nodes: Array<string>, sync_documents: number,
16+
/**
17+
* NAT status detected by autonat: "Unknown", "Public", "Private"
18+
*/
19+
nat_status: string,
20+
/**
21+
* Number of active relay reservations
22+
*/
23+
relay_reservations: number,
24+
/**
25+
* Addresses announced to the network
26+
*/
27+
announce_addresses: Array<string>,
28+
/**
29+
* Relay mode this node is running in
30+
*/
31+
relay_mode: string,
32+
/**
33+
* Replication progress for identity-driven content sync
34+
*/
35+
replication: ReplicationStatus,
36+
/**
37+
* Drain queue state — None when the DB pool or query is unavailable.
38+
* Consumers should treat None as "data not available" (e.g., wait or
39+
* avoid using this peer as a load signal), NOT as "caught up".
40+
*/
41+
drain: DrainStatusInfo | null, };
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2+
3+
/**
4+
* Replication progress exposed via /p2p/status
5+
*/
6+
export type ReplicationStatus = {
7+
/**
8+
* Content IDs discovered but not yet fetched
9+
*/
10+
pending: number,
11+
/**
12+
* Content IDs successfully replicated
13+
*/
14+
completed: number,
15+
/**
16+
* Content IDs that failed fetch (will retry)
17+
*/
18+
failed: number,
19+
/**
20+
* True when all discovered content has been fetched or failed with max retries
21+
*/
22+
caughtUp: boolean, };

elohim/sdk/storage-client-ts/src/generated/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export * from "./DignityFloorView";
108108
export * from "./DiscussionView";
109109
export * from "./DistributionPlanView";
110110
export * from "./DistributionRequestView";
111+
export * from "./DrainStatusInfo";
111112
export * from "./EconomicEvent";
112113
export * from "./EconomicEventView";
113114
export * from "./ExcessSupply";
@@ -157,6 +158,7 @@ export * from "./OrganizationContextView";
157158
export * from "./OsmElementType";
158159
export * from "./OsmReference";
159160
export * from "./OwnedNodeView";
161+
export * from "./P2PStatusInfo";
160162
export * from "./PackageManifestView";
161163
export * from "./ParticipantPositionView";
162164
export * from "./PlaceDashboardEntry";
@@ -184,6 +186,7 @@ export * from "./RelationshipSeedView";
184186
export * from "./RelationshipView";
185187
export * from "./RelationshipWithContent";
186188
export * from "./RelationshipWithContentView";
189+
export * from "./ReplicationStatus";
187190
export * from "./ReportCustodianMetricsInputView";
188191
export * from "./ResourceNature";
189192
export * from "./RespondToChallengeInputView";

genesis/seeder/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"test:watch": "vitest"
5252
},
5353
"dependencies": {
54-
"@elohim/storage-client": "^0.1.0",
54+
"@elohim/storage-client": "workspace:*",
5555
"@holochain/client": "^0.20.1",
5656
"multiformats": "^13.4.1",
5757
"ws": "^8.16.0"

genesis/seeder/src/wait-for-drain.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,7 @@
2626
* resulting observable state.
2727
*/
2828

29-
interface DrainStatus {
30-
total: number;
31-
published: number;
32-
pending: number;
33-
}
34-
35-
interface P2pStatus {
36-
// Only the fields the seeder cares about. The full P2PStatusInfo has
37-
// more (peerId, listenAddresses, natStatus, relayMode, etc.) but those
38-
// are not load-bearing for drain-completion detection.
39-
drain: DrainStatus | null;
40-
connectedPeers?: number;
41-
}
29+
import type { DrainStatusInfo, P2PStatusInfo } from '@elohim/storage-client';
4230

4331
export interface WaitForDrainOptions {
4432
/** Total wait budget before throwing. Default: 5 minutes. */
@@ -63,7 +51,7 @@ export interface WaitForDrainOptions {
6351
export async function waitForDrain(
6452
baseUrl: string,
6553
options: WaitForDrainOptions = {},
66-
): Promise<DrainStatus> {
54+
): Promise<DrainStatusInfo> {
6755
const timeoutMs = options.timeoutMs ?? 5 * 60_000;
6856
const pollIntervalMs = options.pollIntervalMs ?? 2_000;
6957
const expectedMinTotal = options.expectedMinTotal ?? 1;
@@ -74,7 +62,7 @@ export async function waitForDrain(
7462
const deadline = Date.now() + timeoutMs;
7563
let lastLoggedPending = -1;
7664
let consecutiveFetchErrors = 0;
77-
let lastSeenDrain: DrainStatus | null = null;
65+
let lastSeenDrain: DrainStatusInfo | null = null;
7866
let sawNonNullDrain = false;
7967

8068
console.log(
@@ -96,7 +84,7 @@ export async function waitForDrain(
9684
}
9785
} else {
9886
consecutiveFetchErrors = 0;
99-
const status = (await resp.json()) as P2pStatus;
87+
const status = (await resp.json()) as P2PStatusInfo;
10088

10189
if (status.drain === null || status.drain === undefined) {
10290
// Broken node signal — do NOT treat as "done".
@@ -108,8 +96,8 @@ export async function waitForDrain(
10896
// Only log when progress changes, to keep pipeline output readable.
10997
if (pending !== lastLoggedPending) {
11098
const peersSuffix =
111-
status.connectedPeers !== undefined
112-
? ` (connectedPeers=${status.connectedPeers})`
99+
status.connected_peers !== undefined
100+
? ` (connectedPeers=${status.connected_peers})`
113101
: '';
114102
console.log(
115103
`waitForDrain: ${published}/${total} published, ${pending} pending${peersSuffix}`,

pnpm-lock.yaml

Lines changed: 2 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)