1use std::net::SocketAddr;
13use std::time::{Duration, SystemTime, UNIX_EPOCH};
14
15use tokio::net::TcpStream;
16
17use crate::error::{BrainError, Result};
18use crate::mux::HandshakeOutcome;
19use crate::mux::{MuxConnection, Subscription};
20use crate::wire::cbor::{f32_slice_to_le_bytes, from_cbor_bytes, to_cbor_bytes};
21use crate::wire::opcode::Opcode;
22use crate::wire::types::{
23 AnswerKindWire, AuthCredentials, AuthMethod, AuthPayload, CancelStreamAck, CancelStreamRequest,
24 CancellationReason, EncodeRequest, EncodeResponse, EncodeVectorDirectRequest,
25 EntityCreateRequest, EntityCreateResponse, EntityGetRequest, EntityGetResponse, EntityListItem,
26 EntityListRequest, EntityListResponseFrame, EntityMergeRequest, EntityMergeResponse,
27 EntityRenameRequest, EntityRenameResponse, EntityResolveRequest, EntityResolveResponse,
28 EntityTombstoneRequest, EntityTombstoneResponse, EntityUnmergeRequest, EntityUnmergeResponse,
29 EntityUpdateRequest, EntityUpdateResponse, ExtractorListRequest, ExtractorListResponseFrame,
30 ForgetRequest, ForgetResponse, GetCapabilitiesRequest, GetCapabilitiesResponse, GraphEdge,
31 GraphFetchRequest, GraphFetchResponseFrame, GraphNode, HelloCapabilities, HelloPayload,
32 InferenceStep, LinkRequest, LinkResponse, MaterializeProceduralRequest,
33 MaterializeProceduralResponse, MemoryInspectRequest, MemoryInspectResponse, MemoryListItem,
34 MemoryListRequest, MemoryListResponseFrame, MemoryResult, MtlsClaim, PingRequest, PlanRequest,
35 PlanResponseFrame, PlanStep, PongResponse, QueryExplainRequest, QueryExplainResponse,
36 QueryTraceRequest, QueryTraceResponse, ReasonRequest, ReasonResponseFrame, RecallRequest,
37 RecallResponseFrame, RelationCreateRequest, RelationCreateResponse, RelationGetRequest,
38 RelationGetResponse, RelationListFromRequest, RelationListFromResponseFrame,
39 RelationListToRequest, RelationListToResponseFrame, RelationSupersedeRequest,
40 RelationSupersedeResponse, RelationTombstoneRequest, RelationTombstoneResponse,
41 RelationTraverseRequest, RelationTraverseResponseFrame, RelationView, SchemaDropRequest,
42 SchemaDropResponse, SchemaGetRequest, SchemaGetResponse, SchemaListItemWire, SchemaListRequest,
43 SchemaListResponseFrame, SchemaReplaceRequest, SchemaReplaceResponse, SchemaUploadRequest,
44 SchemaUploadResponse, SchemaValidateRequest, SchemaValidateResponse, ServerFeatures,
45 SessionCreateRequest, SessionCreateResponse, SessionDeleteRequest, SessionDeleteResponse,
46 SessionListRequest, SessionListResponse, SpaceCreateRequest, SpaceCreateResponse,
47 SpaceDeleteRequest, SpaceDeleteResponse, SpaceListRequest, SpaceListResponse, SpacePermissions,
48 StatementCreateRequest, StatementCreateResponse, StatementGetRequest, StatementGetResponse,
49 StatementHistoryRequest, StatementHistoryResponseFrame, StatementListRequest,
50 StatementListResponseFrame, StatementRetractRequest, StatementRetractResponse,
51 StatementSupersedeRequest, StatementSupersedeResponse, StatementTombstoneRequest,
52 StatementTombstoneResponse, StatementView, SubscribeRequest, TraversalPathWire,
53 TxnAbortRequest, TxnAbortResponse, TxnBeginRequest, TxnBeginResponse, TxnCommitRequest,
54 TxnCommitResponse, UnlinkRequest, UnlinkResponse,
55};
56
57const DEFAULT_CLIENT_ID: &str = "brain-db-sdk-rust";
59
60#[derive(Clone, Debug)]
65pub enum Auth {
66 Token(Vec<u8>),
68 Mtls(MtlsClaim),
70}
71
72impl Auth {
73 fn method(&self) -> AuthMethod {
74 match self {
75 Auth::Token(_) => AuthMethod::Token,
76 Auth::Mtls(_) => AuthMethod::Mtls,
77 }
78 }
79
80 fn credentials(self) -> AuthCredentials {
81 match self {
82 Auth::Token(t) => AuthCredentials::Token(t),
83 Auth::Mtls(c) => AuthCredentials::Mtls(c),
84 }
85 }
86}
87
88#[derive(Clone, Debug)]
93pub struct ClientConfig {
94 pub client_id: String,
96 pub supported_versions: Vec<u8>,
99 pub capabilities: HelloCapabilities,
101 pub auth: Auth,
104 pub connect_timeout: Option<Duration>,
106 pub request_timeout: Option<Duration>,
108}
109
110impl ClientConfig {
111 #[must_use]
113 pub fn new(auth: Auth) -> Self {
114 Self {
115 client_id: DEFAULT_CLIENT_ID.to_string(),
116 supported_versions: vec![1],
117 capabilities: HelloCapabilities {
118 streaming: true,
119 compression_zstd: false,
120 server_push: false,
121 },
122 auth,
123 connect_timeout: Some(Duration::from_secs(10)),
124 request_timeout: Some(Duration::from_secs(30)),
125 }
126 }
127}
128
129#[derive(Clone, Debug)]
131pub struct ConnectionInfo {
132 pub space_id: [u8; 16],
135 pub server_id: String,
137 pub chosen_version: u8,
140 pub connection_id: [u8; 16],
142 pub bound_shard_id: u16,
145 pub permissions: SpacePermissions,
148 pub namespace: String,
152 pub server_features: ServerFeatures,
155}
156
157#[derive(Clone, Debug, PartialEq)]
162pub struct RecallAnswer {
163 pub answer_kind: AnswerKindWire,
165 pub memories: Vec<MemoryResult>,
167}
168
169impl RecallAnswer {
170 #[must_use]
172 pub fn is_empty(&self) -> bool {
173 self.memories.is_empty()
174 }
175
176 #[must_use]
178 pub fn memories(&self) -> &[MemoryResult] {
179 &self.memories
180 }
181}
182
183pub struct BrainClient {
186 conn: MuxConnection<TcpStream>,
187 connection: ConnectionInfo,
188}
189
190impl std::fmt::Debug for BrainClient {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 f.debug_struct("BrainClient")
194 .field("space_id", &hex16(&self.connection.space_id))
195 .field("namespace", &self.connection.namespace)
196 .field("chosen_version", &self.connection.chosen_version)
197 .finish_non_exhaustive()
198 }
199}
200
201fn hex16(id: &[u8; 16]) -> String {
203 id.iter().fold(String::with_capacity(32), |mut s, b| {
204 use std::fmt::Write as _;
205 let _ = write!(s, "{b:02x}");
206 s
207 })
208}
209
210impl BrainClient {
211 pub async fn connect(addr: SocketAddr, auth: Auth) -> Result<Self> {
215 Self::connect_with(addr, ClientConfig::new(auth)).await
216 }
217
218 pub async fn connect_with(addr: SocketAddr, config: ClientConfig) -> Result<Self> {
220 let stream = match config.connect_timeout {
221 Some(timeout) => tokio::time::timeout(timeout, TcpStream::connect(addr))
222 .await
223 .map_err(|_| BrainError::Timeout(timeout))??,
224 None => TcpStream::connect(addr).await?,
225 };
226 stream.set_nodelay(true)?;
229
230 Self::handshake_over(stream, config).await
231 }
232
233 async fn handshake_over(stream: TcpStream, config: ClientConfig) -> Result<Self> {
237 let hello = HelloPayload {
238 client_id: config.client_id,
239 supported_versions: config.supported_versions,
240 capabilities: config.capabilities,
241 client_connection_token: None,
242 };
243 let auth = AuthPayload {
244 method: config.auth.method(),
245 credentials: config.auth.credentials(),
246 };
247
248 let (conn, HandshakeOutcome { welcome, auth_ok }) =
249 MuxConnection::handshake(stream, hello, auth, config.request_timeout).await?;
250
251 let connection = ConnectionInfo {
252 space_id: auth_ok.space_id,
253 server_id: welcome.server_id,
254 chosen_version: welcome.chosen_version,
255 connection_id: welcome.connection_id,
256 bound_shard_id: auth_ok.bound_shard_id,
257 permissions: auth_ok.permissions,
258 namespace: auth_ok.namespace,
259 server_features: welcome.server_features,
260 };
261 Ok(Self { conn, connection })
262 }
263
264 #[must_use]
267 pub fn is_closed(&self) -> bool {
268 self.conn.is_closed()
269 }
270
271 #[must_use]
273 pub fn connection(&self) -> &ConnectionInfo {
274 &self.connection
275 }
276
277 #[must_use]
279 pub fn space_id(&self) -> [u8; 16] {
280 self.connection.space_id
281 }
282
283 #[must_use]
287 pub fn namespace(&self) -> &str {
288 &self.connection.namespace
289 }
290
291 pub async fn encode(&self, request: &EncodeRequest) -> Result<EncodeResponse> {
293 self.unary(
294 Opcode::EncodeReq,
295 Opcode::EncodeResp,
296 "ENCODE_RESP",
297 request,
298 )
299 .await
300 }
301
302 pub async fn recall(&self, request: &RecallRequest) -> Result<RecallAnswer> {
309 let frames = self.recall_frames(request).await?;
310 let mut memories = Vec::new();
311 let mut answer_kind = AnswerKindWire::None;
314 for frame in frames {
315 answer_kind = frame.answer_kind;
316 memories.extend(frame.memories);
317 }
318 Ok(RecallAnswer {
319 answer_kind,
320 memories,
321 })
322 }
323
324 pub async fn recall_frames(&self, request: &RecallRequest) -> Result<Vec<RecallResponseFrame>> {
328 let frames = self
329 .conn
330 .request(Opcode::RecallReq, to_cbor_bytes(request))
331 .await?;
332 let mut out = Vec::with_capacity(frames.len());
333 for frame in frames {
334 if frame.opcode != Opcode::RecallResp as u16 {
335 return Err(BrainError::Protocol(format!(
336 "expected RECALL_RESP ({:#06x}), got {:#06x}",
337 Opcode::RecallResp as u16,
338 frame.opcode
339 )));
340 }
341 out.push(from_cbor_bytes(&frame.payload)?);
342 }
343 Ok(out)
344 }
345
346 pub async fn forget(&self, request: &ForgetRequest) -> Result<ForgetResponse> {
348 self.unary(
349 Opcode::ForgetReq,
350 Opcode::ForgetResp,
351 "FORGET_RESP",
352 request,
353 )
354 .await
355 }
356
357 pub async fn link(&self, request: &LinkRequest) -> Result<LinkResponse> {
360 self.unary(Opcode::LinkReq, Opcode::LinkResp, "LINK_RESP", request)
361 .await
362 }
363
364 pub async fn unlink(&self, request: &UnlinkRequest) -> Result<UnlinkResponse> {
368 self.unary(
369 Opcode::UnlinkReq,
370 Opcode::UnlinkResp,
371 "UNLINK_RESP",
372 request,
373 )
374 .await
375 }
376
377 pub async fn plan(&self, request: &PlanRequest) -> Result<Vec<PlanStep>> {
381 let frames = self.plan_frames(request).await?;
382 let mut steps = Vec::new();
383 for frame in frames {
384 steps.extend(frame.steps);
385 }
386 Ok(steps)
387 }
388
389 pub async fn plan_frames(&self, request: &PlanRequest) -> Result<Vec<PlanResponseFrame>> {
392 self.streamed(Opcode::PlanReq, Opcode::PlanResp, "PLAN_RESP", request)
393 .await
394 }
395
396 pub async fn reason(&self, request: &ReasonRequest) -> Result<Vec<InferenceStep>> {
401 let frames = self.reason_frames(request).await?;
402 let mut inferences = Vec::new();
403 for frame in frames {
404 inferences.extend(frame.inferences);
405 }
406 Ok(inferences)
407 }
408
409 pub async fn reason_frames(&self, request: &ReasonRequest) -> Result<Vec<ReasonResponseFrame>> {
412 self.streamed(
413 Opcode::ReasonReq,
414 Opcode::ReasonResp,
415 "REASON_RESP",
416 request,
417 )
418 .await
419 }
420
421 pub async fn create_entity(
423 &self,
424 request: &EntityCreateRequest,
425 ) -> Result<EntityCreateResponse> {
426 self.unary(
427 Opcode::EntityCreateReq,
428 Opcode::EntityCreateResp,
429 "ENTITY_CREATE_RESP",
430 request,
431 )
432 .await
433 }
434
435 pub async fn update_entity(
437 &self,
438 request: &EntityUpdateRequest,
439 ) -> Result<EntityUpdateResponse> {
440 self.unary(
441 Opcode::EntityUpdateReq,
442 Opcode::EntityUpdateResp,
443 "ENTITY_UPDATE_RESP",
444 request,
445 )
446 .await
447 }
448
449 pub async fn rename_entity(
452 &self,
453 request: &EntityRenameRequest,
454 ) -> Result<EntityRenameResponse> {
455 self.unary(
456 Opcode::EntityRenameReq,
457 Opcode::EntityRenameResp,
458 "ENTITY_RENAME_RESP",
459 request,
460 )
461 .await
462 }
463
464 pub async fn merge_entities(
467 &self,
468 request: &EntityMergeRequest,
469 ) -> Result<EntityMergeResponse> {
470 self.unary(
471 Opcode::EntityMergeReq,
472 Opcode::EntityMergeResp,
473 "ENTITY_MERGE_RESP",
474 request,
475 )
476 .await
477 }
478
479 pub async fn unmerge_entity(
481 &self,
482 request: &EntityUnmergeRequest,
483 ) -> Result<EntityUnmergeResponse> {
484 self.unary(
485 Opcode::EntityUnmergeReq,
486 Opcode::EntityUnmergeResp,
487 "ENTITY_UNMERGE_RESP",
488 request,
489 )
490 .await
491 }
492
493 pub async fn tombstone_entity(
496 &self,
497 request: &EntityTombstoneRequest,
498 ) -> Result<EntityTombstoneResponse> {
499 self.unary(
500 Opcode::EntityTombstoneReq,
501 Opcode::EntityTombstoneResp,
502 "ENTITY_TOMBSTONE_RESP",
503 request,
504 )
505 .await
506 }
507
508 pub async fn create_statement(
511 &self,
512 request: &StatementCreateRequest,
513 ) -> Result<StatementCreateResponse> {
514 self.unary(
515 Opcode::StatementCreateReq,
516 Opcode::StatementCreateResp,
517 "STATEMENT_CREATE_RESP",
518 request,
519 )
520 .await
521 }
522
523 pub async fn create_relation(
525 &self,
526 request: &RelationCreateRequest,
527 ) -> Result<RelationCreateResponse> {
528 self.unary(
529 Opcode::RelationCreateReq,
530 Opcode::RelationCreateResp,
531 "RELATION_CREATE_RESP",
532 request,
533 )
534 .await
535 }
536
537 pub async fn upload_schema(
541 &self,
542 request: &SchemaUploadRequest,
543 ) -> Result<SchemaUploadResponse> {
544 self.unary(
545 Opcode::SchemaUploadReq,
546 Opcode::SchemaUploadResp,
547 "SCHEMA_UPLOAD_RESP",
548 request,
549 )
550 .await
551 }
552
553 pub async fn get_relation(&self, request: &RelationGetRequest) -> Result<RelationGetResponse> {
556 self.unary(
557 Opcode::RelationGetReq,
558 Opcode::RelationGetResp,
559 "RELATION_GET_RESP",
560 request,
561 )
562 .await
563 }
564
565 pub async fn supersede_relation(
567 &self,
568 request: &RelationSupersedeRequest,
569 ) -> Result<RelationSupersedeResponse> {
570 self.unary(
571 Opcode::RelationSupersedeReq,
572 Opcode::RelationSupersedeResp,
573 "RELATION_SUPERSEDE_RESP",
574 request,
575 )
576 .await
577 }
578
579 pub async fn tombstone_relation(
581 &self,
582 request: &RelationTombstoneRequest,
583 ) -> Result<RelationTombstoneResponse> {
584 self.unary(
585 Opcode::RelationTombstoneReq,
586 Opcode::RelationTombstoneResp,
587 "RELATION_TOMBSTONE_RESP",
588 request,
589 )
590 .await
591 }
592
593 pub async fn traverse_relations(
597 &self,
598 request: &RelationTraverseRequest,
599 ) -> Result<Vec<TraversalPathWire>> {
600 let frames = self.traverse_relations_frames(request).await?;
601 let mut paths = Vec::new();
602 for frame in frames {
603 paths.extend(frame.paths);
604 }
605 Ok(paths)
606 }
607
608 pub async fn traverse_relations_frames(
611 &self,
612 request: &RelationTraverseRequest,
613 ) -> Result<Vec<RelationTraverseResponseFrame>> {
614 self.streamed(
615 Opcode::RelationTraverseReq,
616 Opcode::RelationTraverseResp,
617 "RELATION_TRAVERSE_RESP",
618 request,
619 )
620 .await
621 }
622
623 pub async fn query_explain(
625 &self,
626 request: &QueryExplainRequest,
627 ) -> Result<QueryExplainResponse> {
628 self.unary(
629 Opcode::QueryExplainReq,
630 Opcode::QueryExplainResp,
631 "QUERY_EXPLAIN_RESP",
632 request,
633 )
634 .await
635 }
636
637 pub async fn query_trace(&self, request: &QueryTraceRequest) -> Result<QueryTraceResponse> {
639 self.unary(
640 Opcode::QueryTraceReq,
641 Opcode::QueryTraceResp,
642 "QUERY_TRACE_RESP",
643 request,
644 )
645 .await
646 }
647
648 pub async fn encode_vector_direct(
652 &self,
653 request: &EncodeVectorDirectRequest,
654 ) -> Result<EncodeResponse> {
655 let mut payload = to_cbor_bytes(request);
656 payload.extend_from_slice(&f32_slice_to_le_bytes(&request.vector));
657 let frame = self
658 .conn
659 .request_one(Opcode::EncodeVectorDirectReq, payload)
660 .await?;
661 if frame.opcode != Opcode::EncodeVectorDirectResp as u16 {
662 return Err(BrainError::Protocol(format!(
663 "expected ENCODE_VECTOR_DIRECT_RESP ({:#06x}), got {:#06x}",
664 Opcode::EncodeVectorDirectResp as u16,
665 frame.opcode
666 )));
667 }
668 Ok(from_cbor_bytes(&frame.payload)?)
669 }
670
671 pub async fn materialize_procedural(
673 &self,
674 request: &MaterializeProceduralRequest,
675 ) -> Result<MaterializeProceduralResponse> {
676 self.unary(
677 Opcode::MaterializeProceduralReq,
678 Opcode::MaterializeProceduralResp,
679 "MATERIALIZE_PROCEDURAL_RESP",
680 request,
681 )
682 .await
683 }
684
685 pub async fn capabilities(
689 &self,
690 request: &GetCapabilitiesRequest,
691 ) -> Result<GetCapabilitiesResponse> {
692 self.unary(
693 Opcode::GetCapabilitiesReq,
694 Opcode::GetCapabilitiesResp,
695 "GET_CAPABILITIES_RESP",
696 request,
697 )
698 .await
699 }
700
701 pub async fn extractor_list(
706 &self,
707 request: &ExtractorListRequest,
708 ) -> Result<ExtractorListResponseFrame> {
709 self.unary(
710 Opcode::ExtractorListReq,
711 Opcode::ExtractorListResp,
712 "EXTRACTOR_LIST_RESP",
713 request,
714 )
715 .await
716 }
717
718 pub async fn get_entity(&self, request: &EntityGetRequest) -> Result<EntityGetResponse> {
720 self.unary(
721 Opcode::EntityGetReq,
722 Opcode::EntityGetResp,
723 "ENTITY_GET_RESP",
724 request,
725 )
726 .await
727 }
728
729 pub async fn memory_inspect(
736 &self,
737 request: &MemoryInspectRequest,
738 ) -> Result<MemoryInspectResponse> {
739 self.unary(
740 Opcode::MemoryInspectReq,
741 Opcode::MemoryInspectResp,
742 "MEMORY_INSPECT_RESP",
743 request,
744 )
745 .await
746 }
747
748 pub async fn resolve_entity(
752 &self,
753 request: &EntityResolveRequest,
754 ) -> Result<EntityResolveResponse> {
755 self.unary(
756 Opcode::EntityResolveReq,
757 Opcode::EntityResolveResp,
758 "ENTITY_RESOLVE_RESP",
759 request,
760 )
761 .await
762 }
763
764 pub async fn get_statement(
767 &self,
768 request: &StatementGetRequest,
769 ) -> Result<StatementGetResponse> {
770 self.unary(
771 Opcode::StatementGetReq,
772 Opcode::StatementGetResp,
773 "STATEMENT_GET_RESP",
774 request,
775 )
776 .await
777 }
778
779 pub async fn get_schema(&self, request: &SchemaGetRequest) -> Result<SchemaGetResponse> {
782 self.unary(
783 Opcode::SchemaGetReq,
784 Opcode::SchemaGetResp,
785 "SCHEMA_GET_RESP",
786 request,
787 )
788 .await
789 }
790
791 pub async fn validate_schema(
793 &self,
794 request: &SchemaValidateRequest,
795 ) -> Result<SchemaValidateResponse> {
796 self.unary(
797 Opcode::SchemaValidateReq,
798 Opcode::SchemaValidateResp,
799 "SCHEMA_VALIDATE_RESP",
800 request,
801 )
802 .await
803 }
804
805 pub async fn replace_schema(
817 &self,
818 request: &SchemaReplaceRequest,
819 ) -> Result<SchemaReplaceResponse> {
820 self.unary(
821 Opcode::SchemaReplaceReq,
822 Opcode::SchemaReplaceResp,
823 "SCHEMA_REPLACE_RESP",
824 request,
825 )
826 .await
827 }
828
829 pub async fn drop_schema(&self, request: &SchemaDropRequest) -> Result<SchemaDropResponse> {
841 self.unary(
842 Opcode::SchemaDropReq,
843 Opcode::SchemaDropResp,
844 "SCHEMA_DROP_RESP",
845 request,
846 )
847 .await
848 }
849
850 pub async fn ping(&self, client_timestamp_unix_nanos: Option<u64>) -> Result<PongResponse> {
859 let ts = client_timestamp_unix_nanos.unwrap_or_else(|| {
860 SystemTime::now()
866 .duration_since(UNIX_EPOCH)
867 .map_or(0, |d| u64::try_from(d.as_nanos()).unwrap_or(u64::MAX))
868 });
869 let request = PingRequest {
870 client_timestamp_unix_nanos: ts,
871 };
872 self.unary(Opcode::Ping, Opcode::Pong, "PONG", &request)
873 .await
874 }
875
876 pub async fn cancel_stream(
892 &self,
893 target_stream_id: u32,
894 reason: CancellationReason,
895 ) -> Result<CancelStreamAck> {
896 let request = CancelStreamRequest {
897 target_stream_id,
898 reason,
899 };
900 self.unary(
901 Opcode::CancelStream,
902 Opcode::CancelStreamAck,
903 "CANCEL_STREAM_ACK",
904 &request,
905 )
906 .await
907 }
908
909 pub async fn list_entities(&self, request: &EntityListRequest) -> Result<Vec<EntityListItem>> {
913 let frames = self.list_entities_frames(request).await?;
914 let mut items = Vec::new();
915 for frame in frames {
916 items.extend(frame.items);
917 }
918 Ok(items)
919 }
920
921 pub async fn list_entities_frames(
924 &self,
925 request: &EntityListRequest,
926 ) -> Result<Vec<EntityListResponseFrame>> {
927 self.streamed(
928 Opcode::EntityListReq,
929 Opcode::EntityListResp,
930 "ENTITY_LIST_RESP",
931 request,
932 )
933 .await
934 }
935
936 pub async fn memory_list(&self, request: &MemoryListRequest) -> Result<Vec<MemoryListItem>> {
942 let frames = self.memory_list_frames(request).await?;
943 let mut items = Vec::new();
944 for frame in frames {
945 items.extend(frame.items);
946 }
947 Ok(items)
948 }
949
950 pub async fn memory_list_frames(
953 &self,
954 request: &MemoryListRequest,
955 ) -> Result<Vec<MemoryListResponseFrame>> {
956 self.streamed(
957 Opcode::MemoryListReq,
958 Opcode::MemoryListResp,
959 "MEMORY_LIST_RESP",
960 request,
961 )
962 .await
963 }
964
965 pub async fn graph_fetch(
971 &self,
972 request: &GraphFetchRequest,
973 ) -> Result<(Vec<GraphNode>, Vec<GraphEdge>)> {
974 let frames = self.graph_fetch_frames(request).await?;
975 let mut nodes = Vec::new();
976 let mut edges = Vec::new();
977 for frame in frames {
978 nodes.extend(frame.nodes);
979 edges.extend(frame.edges);
980 }
981 Ok((nodes, edges))
982 }
983
984 pub async fn graph_fetch_frames(
987 &self,
988 request: &GraphFetchRequest,
989 ) -> Result<Vec<GraphFetchResponseFrame>> {
990 self.streamed(
991 Opcode::GraphFetchReq,
992 Opcode::GraphFetchResp,
993 "GRAPH_FETCH_RESP",
994 request,
995 )
996 .await
997 }
998
999 pub async fn list_statements(
1002 &self,
1003 request: &StatementListRequest,
1004 ) -> Result<Vec<StatementView>> {
1005 let frames = self.list_statements_frames(request).await?;
1006 let mut items = Vec::new();
1007 for frame in frames {
1008 items.extend(frame.items);
1009 }
1010 Ok(items)
1011 }
1012
1013 pub async fn list_statements_frames(
1015 &self,
1016 request: &StatementListRequest,
1017 ) -> Result<Vec<StatementListResponseFrame>> {
1018 self.streamed(
1019 Opcode::StatementListReq,
1020 Opcode::StatementListResp,
1021 "STATEMENT_LIST_RESP",
1022 request,
1023 )
1024 .await
1025 }
1026
1027 pub async fn supersede_statement(
1030 &self,
1031 request: &StatementSupersedeRequest,
1032 ) -> Result<StatementSupersedeResponse> {
1033 self.unary(
1034 Opcode::StatementSupersedeReq,
1035 Opcode::StatementSupersedeResp,
1036 "STATEMENT_SUPERSEDE_RESP",
1037 request,
1038 )
1039 .await
1040 }
1041
1042 pub async fn tombstone_statement(
1045 &self,
1046 request: &StatementTombstoneRequest,
1047 ) -> Result<StatementTombstoneResponse> {
1048 self.unary(
1049 Opcode::StatementTombstoneReq,
1050 Opcode::StatementTombstoneResp,
1051 "STATEMENT_TOMBSTONE_RESP",
1052 request,
1053 )
1054 .await
1055 }
1056
1057 pub async fn retract_statement(
1060 &self,
1061 request: &StatementRetractRequest,
1062 ) -> Result<StatementRetractResponse> {
1063 self.unary(
1064 Opcode::StatementRetractReq,
1065 Opcode::StatementRetractResp,
1066 "STATEMENT_RETRACT_RESP",
1067 request,
1068 )
1069 .await
1070 }
1071
1072 pub async fn statement_history(
1076 &self,
1077 request: &StatementHistoryRequest,
1078 ) -> Result<Vec<StatementView>> {
1079 let frames = self.statement_history_frames(request).await?;
1080 let mut items = Vec::new();
1081 for frame in frames {
1082 items.extend(frame.items);
1083 }
1084 Ok(items)
1085 }
1086
1087 pub async fn statement_history_frames(
1090 &self,
1091 request: &StatementHistoryRequest,
1092 ) -> Result<Vec<StatementHistoryResponseFrame>> {
1093 self.streamed(
1094 Opcode::StatementHistoryReq,
1095 Opcode::StatementHistoryResp,
1096 "STATEMENT_HISTORY_RESP",
1097 request,
1098 )
1099 .await
1100 }
1101
1102 pub async fn list_relations_from(
1106 &self,
1107 request: &RelationListFromRequest,
1108 ) -> Result<Vec<RelationView>> {
1109 let frames = self.list_relations_from_frames(request).await?;
1110 let mut items = Vec::new();
1111 for frame in frames {
1112 items.extend(frame.items);
1113 }
1114 Ok(items)
1115 }
1116
1117 pub async fn list_relations_from_frames(
1120 &self,
1121 request: &RelationListFromRequest,
1122 ) -> Result<Vec<RelationListFromResponseFrame>> {
1123 self.streamed(
1124 Opcode::RelationListFromReq,
1125 Opcode::RelationListFromResp,
1126 "RELATION_LIST_FROM_RESP",
1127 request,
1128 )
1129 .await
1130 }
1131
1132 pub async fn list_relations_to(
1136 &self,
1137 request: &RelationListToRequest,
1138 ) -> Result<Vec<RelationView>> {
1139 let frames = self.list_relations_to_frames(request).await?;
1140 let mut items = Vec::new();
1141 for frame in frames {
1142 items.extend(frame.items);
1143 }
1144 Ok(items)
1145 }
1146
1147 pub async fn list_relations_to_frames(
1150 &self,
1151 request: &RelationListToRequest,
1152 ) -> Result<Vec<RelationListToResponseFrame>> {
1153 self.streamed(
1154 Opcode::RelationListToReq,
1155 Opcode::RelationListToResp,
1156 "RELATION_LIST_TO_RESP",
1157 request,
1158 )
1159 .await
1160 }
1161
1162 pub async fn list_schemas(
1166 &self,
1167 request: &SchemaListRequest,
1168 ) -> Result<Vec<SchemaListItemWire>> {
1169 let frames = self.list_schemas_frames(request).await?;
1170 let mut items = Vec::new();
1171 for frame in frames {
1172 items.extend(frame.items);
1173 }
1174 Ok(items)
1175 }
1176
1177 pub async fn list_schemas_frames(
1179 &self,
1180 request: &SchemaListRequest,
1181 ) -> Result<Vec<SchemaListResponseFrame>> {
1182 self.streamed(
1183 Opcode::SchemaListReq,
1184 Opcode::SchemaListResp,
1185 "SCHEMA_LIST_RESP",
1186 request,
1187 )
1188 .await
1189 }
1190
1191 pub async fn txn_begin(&self, request: &TxnBeginRequest) -> Result<TxnBeginResponse> {
1194 self.unary(
1195 Opcode::TxnBegin,
1196 Opcode::TxnBeginResp,
1197 "TXN_BEGIN_RESP",
1198 request,
1199 )
1200 .await
1201 }
1202
1203 pub async fn txn_commit(&self, request: &TxnCommitRequest) -> Result<TxnCommitResponse> {
1206 self.unary(
1207 Opcode::TxnCommit,
1208 Opcode::TxnCommitResp,
1209 "TXN_COMMIT_RESP",
1210 request,
1211 )
1212 .await
1213 }
1214
1215 pub async fn txn_abort(&self, request: &TxnAbortRequest) -> Result<TxnAbortResponse> {
1217 self.unary(
1218 Opcode::TxnAbort,
1219 Opcode::TxnAbortResp,
1220 "TXN_ABORT_RESP",
1221 request,
1222 )
1223 .await
1224 }
1225
1226 pub async fn create_space(&self, request: &SpaceCreateRequest) -> Result<SpaceCreateResponse> {
1230 self.unary(
1231 Opcode::SpaceCreateReq,
1232 Opcode::SpaceCreateResp,
1233 "SPACE_CREATE_RESP",
1234 request,
1235 )
1236 .await
1237 }
1238
1239 pub async fn list_spaces(&self, request: &SpaceListRequest) -> Result<SpaceListResponse> {
1242 self.unary(
1243 Opcode::SpaceListReq,
1244 Opcode::SpaceListResp,
1245 "SPACE_LIST_RESP",
1246 request,
1247 )
1248 .await
1249 }
1250
1251 pub async fn delete_space(&self, request: &SpaceDeleteRequest) -> Result<SpaceDeleteResponse> {
1254 self.unary(
1255 Opcode::SpaceDeleteReq,
1256 Opcode::SpaceDeleteResp,
1257 "SPACE_DELETE_RESP",
1258 request,
1259 )
1260 .await
1261 }
1262
1263 pub async fn create_session(
1266 &self,
1267 request: &SessionCreateRequest,
1268 ) -> Result<SessionCreateResponse> {
1269 self.unary(
1270 Opcode::SessionCreateReq,
1271 Opcode::SessionCreateResp,
1272 "SESSION_CREATE_RESP",
1273 request,
1274 )
1275 .await
1276 }
1277
1278 pub async fn list_sessions(&self, request: &SessionListRequest) -> Result<SessionListResponse> {
1281 self.unary(
1282 Opcode::SessionListReq,
1283 Opcode::SessionListResp,
1284 "SESSION_LIST_RESP",
1285 request,
1286 )
1287 .await
1288 }
1289
1290 pub async fn delete_session(
1294 &self,
1295 request: &SessionDeleteRequest,
1296 ) -> Result<SessionDeleteResponse> {
1297 self.unary(
1298 Opcode::SessionDeleteReq,
1299 Opcode::SessionDeleteResp,
1300 "SESSION_DELETE_RESP",
1301 request,
1302 )
1303 .await
1304 }
1305
1306 pub async fn subscribe(&self, request: &SubscribeRequest) -> Result<Subscription<TcpStream>> {
1312 self.conn.subscribe(request).await
1313 }
1314
1315 async fn streamed<Req, Frame>(
1319 &self,
1320 req_opcode: Opcode,
1321 resp_opcode: Opcode,
1322 resp_name: &str,
1323 request: &Req,
1324 ) -> Result<Vec<Frame>>
1325 where
1326 Req: serde::Serialize,
1327 Frame: serde::de::DeserializeOwned,
1328 {
1329 let frames = self
1330 .conn
1331 .request(req_opcode, to_cbor_bytes(request))
1332 .await?;
1333 let mut out = Vec::with_capacity(frames.len());
1334 for frame in frames {
1335 if frame.opcode != resp_opcode as u16 {
1336 return Err(BrainError::Protocol(format!(
1337 "expected {resp_name} ({:#06x}), got {:#06x}",
1338 resp_opcode as u16, frame.opcode
1339 )));
1340 }
1341 out.push(from_cbor_bytes(&frame.payload)?);
1342 }
1343 Ok(out)
1344 }
1345
1346 async fn unary<Req, Resp>(
1349 &self,
1350 req_opcode: Opcode,
1351 resp_opcode: Opcode,
1352 resp_name: &str,
1353 request: &Req,
1354 ) -> Result<Resp>
1355 where
1356 Req: serde::Serialize,
1357 Resp: serde::de::DeserializeOwned,
1358 {
1359 let frame = self
1360 .conn
1361 .request_one(req_opcode, to_cbor_bytes(request))
1362 .await?;
1363 if frame.opcode != resp_opcode as u16 {
1364 return Err(BrainError::Protocol(format!(
1365 "expected {resp_name} ({:#06x}), got {:#06x}",
1366 resp_opcode as u16, frame.opcode
1367 )));
1368 }
1369 Ok(from_cbor_bytes(&frame.payload)?)
1370 }
1371
1372 pub async fn close(self) -> Result<()> {
1374 self.conn.send_bye().await
1375 }
1376}
1377
1378#[must_use]
1380pub fn new_id() -> [u8; 16] {
1381 *uuid::Uuid::now_v7().as_bytes()
1382}
1383
1384const BRAIN_ROOT_NAMESPACE_UUID: uuid::Uuid = uuid::Uuid::from_bytes([
1387 0x6b, 0x72, 0x61, 0x69, 0x6e, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2d, 0x72, 0x6f, 0x6f, 0x74,
1388]);
1389
1390#[must_use]
1399pub fn derive_space_id(namespace: &str, space: &str) -> [u8; 16] {
1400 let ns = uuid::Uuid::new_v5(&BRAIN_ROOT_NAMESPACE_UUID, namespace.as_bytes());
1401 *uuid::Uuid::new_v5(&ns, space.as_bytes()).as_bytes()
1402}
1403
1404#[cfg(test)]
1405mod space_id_tests {
1406 use super::derive_space_id;
1407
1408 #[test]
1409 fn derive_space_id_matches_server_golden() {
1410 let id = derive_space_id("acme", "support-bot:user123");
1413 assert_eq!(
1414 uuid::Uuid::from_bytes(id).to_string(),
1415 "119061cc-b6db-5cde-8edd-0cefa33452eb"
1416 );
1417 }
1418
1419 #[test]
1420 fn same_space_diverges_across_namespaces() {
1421 assert_ne!(derive_space_id("ns_a", "u1"), derive_space_id("ns_b", "u1"));
1422 }
1423}