@ignex/nova 0.1.3 → 0.1.5

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 (98) hide show
  1. package/README.md +4 -1
  2. package/docs/ai/TREE.md +69 -9
  3. package/docs/architecture.md +75 -27
  4. package/docs/events.md +83 -1
  5. package/docs/generic-bindings.md +10 -0
  6. package/docs/wire-format.md +65 -18
  7. package/package.json +2 -1
  8. package/prebuilds/linux-x64/libignex_ffi.so +0 -0
  9. package/public/generate.ts +97 -3
  10. package/public/server.ts +10 -0
  11. package/rust/src/generated/backend.rs +503 -0
  12. package/rust/src/transcode/generated.rs +376 -17
  13. package/src/bridge/nats/inbound.ts +46 -0
  14. package/src/bridge/nats/index.ts +131 -0
  15. package/src/bridge/nats/real-transport.ts +133 -0
  16. package/src/bridge/nats/types.ts +80 -0
  17. package/src/codegen/constants.ts +14 -4
  18. package/src/codegen/direct-gen.ts +20 -6
  19. package/src/codegen/registry-gen.ts +10 -6
  20. package/src/codegen/rust-glue-gen.ts +10 -3
  21. package/src/codegen/schema-model.ts +28 -3
  22. package/src/codegen/ts-ser-gen.ts +12 -3
  23. package/src/core/auth.ts +65 -4
  24. package/src/core/client-rpc.ts +75 -0
  25. package/src/core/client-state.ts +42 -0
  26. package/src/core/client-wire.ts +142 -8
  27. package/src/core/client.ts +72 -3
  28. package/src/core/groups.ts +5 -0
  29. package/src/core/metrics.ts +38 -21
  30. package/src/core/outbound.ts +50 -6
  31. package/src/core/rate-limit.ts +69 -0
  32. package/src/core/replay.ts +41 -1
  33. package/src/core/resume.ts +181 -0
  34. package/src/core/rooms.ts +10 -3
  35. package/src/core/routing.ts +128 -5
  36. package/src/core/server/client-info.ts +37 -0
  37. package/src/core/server/http-routes.ts +59 -0
  38. package/src/core/{server.ts → server/index.ts} +112 -120
  39. package/src/core/server/metrics-view.ts +53 -0
  40. package/src/core/server/socket-lifecycle.ts +57 -0
  41. package/src/core/state.ts +73 -1
  42. package/src/core/topic-log.ts +86 -0
  43. package/src/events/clients.ts +18 -0
  44. package/src/events/cluster/dedupe.ts +43 -0
  45. package/src/events/cluster/envelope.ts +149 -0
  46. package/src/events/cluster/index.ts +50 -0
  47. package/src/events/cluster/keys.ts +33 -0
  48. package/src/events/cluster/kinds.ts +32 -0
  49. package/src/events/cluster/presence-table.ts +99 -0
  50. package/src/events/cluster/presence.ts +53 -0
  51. package/src/events/cluster/redis-client.ts +50 -0
  52. package/src/events/cluster/store-memory.ts +67 -0
  53. package/src/events/cluster/store-redis.ts +44 -0
  54. package/src/events/cluster/subjects.ts +30 -0
  55. package/src/events/cluster/sync.ts +476 -0
  56. package/src/events/cluster/transport-nats.ts +24 -0
  57. package/src/events/cluster/transport-redis.ts +120 -0
  58. package/src/events/cluster-rpc.ts +196 -0
  59. package/src/events/delivery.ts +83 -0
  60. package/src/events/emit.ts +57 -11
  61. package/src/events/hub/context-factory.ts +79 -0
  62. package/src/events/hub/dispatch.ts +86 -0
  63. package/src/events/hub/index.ts +536 -0
  64. package/src/events/hub/internal.ts +31 -0
  65. package/src/events/hub/metrics-snapshot.ts +84 -0
  66. package/src/events/hub/resolve-cluster.ts +49 -0
  67. package/src/events/queue.ts +36 -9
  68. package/src/events/registry.ts +90 -54
  69. package/src/events/schedule.ts +73 -0
  70. package/src/events/trace.ts +283 -0
  71. package/src/events/types/client.ts +68 -0
  72. package/src/events/types/cluster.ts +40 -0
  73. package/src/events/types/context.ts +50 -0
  74. package/src/events/types/emit-target.ts +29 -0
  75. package/src/events/types/groups.ts +35 -0
  76. package/src/events/types/hub.ts +124 -0
  77. package/src/events/types/index.ts +30 -0
  78. package/src/events/types/metrics.ts +52 -0
  79. package/src/events/types/options.ts +62 -0
  80. package/src/generated/direct-ser.ts +146 -59
  81. package/src/generated/fbs/backend.fbs +23 -0
  82. package/src/generated/registry.ts +92 -33
  83. package/src/generated/rust/backend_generated.rs +503 -0
  84. package/src/generated/ts/backend.ts +4 -0
  85. package/src/generated/ts/resume.ts +74 -0
  86. package/src/generated/ts/resumed.ts +88 -0
  87. package/src/generated/ts/rpc-call.ts +112 -0
  88. package/src/generated/ts/rpc-result.ts +126 -0
  89. package/src/generated/ts/snapshot-request.ts +19 -5
  90. package/src/generated/ts-ser.ts +109 -16
  91. package/src/generated/wire-registry.json +7 -3
  92. package/src/schema/index.ts +45 -1
  93. package/src/transport/transport.ts +117 -77
  94. package/src/bridge/nats.ts +0 -309
  95. package/src/events/cluster.ts +0 -732
  96. package/src/events/hub.ts +0 -481
  97. package/src/events/types.ts +0 -378
  98. package/src/transport/stats.ts +0 -48
@@ -9,9 +9,9 @@ use std::os::raw::c_char;
9
9
  use serde::Deserialize;
10
10
  use crate::generated::backend;
11
11
 
12
- pub const WIRE_VERSION: u8 = 1;
13
- pub const WIRE_HEADER_LEN: usize = 5; // [version:1][event_id:u32 LE]
14
- pub const SCHEMA_FINGERPRINT: u64 = 1656642724; // fnv1a32(canonical model)
12
+ pub const WIRE_VERSION: u8 = 2;
13
+ pub const WIRE_HEADER_LEN: usize = 14; // [version:1][event_id:u32 LE][flags:1][seq:u64 LE]
14
+ pub const SCHEMA_FINGERPRINT: u64 = 3647747841; // fnv1a32(canonical model)
15
15
 
16
16
  thread_local! {
17
17
  static FBB: RefCell<flatbuffers::FlatBufferBuilder<'static>> =
@@ -473,12 +473,15 @@ pub fn build_leave_group<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &L
473
473
  pub struct SnapshotRequestJson {
474
474
  #[serde(default)]
475
475
  pub topic: String,
476
+ #[serde(default, deserialize_with = "de_i64")]
477
+ pub from_seq: i64,
476
478
  }
477
479
 
478
480
  pub fn build_snapshot_request<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &SnapshotRequestJson) -> flatbuffers::WIPOffset<backend::SnapshotRequest<'a>> {
479
481
  let topic = fbb.create_string(&j.topic);
480
482
  backend::SnapshotRequest::create(fbb, &backend::SnapshotRequestArgs {
481
483
  topic: Some(topic),
484
+ from_seq: j.from_seq,
482
485
  ..Default::default()
483
486
  })
484
487
  }
@@ -513,6 +516,88 @@ pub fn build_pong<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &PongJson
513
516
  })
514
517
  }
515
518
 
519
+ #[derive(Deserialize, Default)]
520
+ #[serde(rename_all = "camelCase")]
521
+ pub struct ResumeJson {
522
+ #[serde(default, deserialize_with = "de_i64")]
523
+ pub last_seq: i64,
524
+ }
525
+
526
+ pub fn build_resume<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &ResumeJson) -> flatbuffers::WIPOffset<backend::Resume<'a>> {
527
+
528
+ backend::Resume::create(fbb, &backend::ResumeArgs {
529
+ last_seq: j.last_seq,
530
+ ..Default::default()
531
+ })
532
+ }
533
+
534
+ #[derive(Deserialize, Default)]
535
+ #[serde(rename_all = "camelCase")]
536
+ pub struct ResumedJson {
537
+ #[serde(default)]
538
+ pub ok: bool,
539
+ #[serde(default, deserialize_with = "de_i64")]
540
+ pub from: i64,
541
+ }
542
+
543
+ pub fn build_resumed<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &ResumedJson) -> flatbuffers::WIPOffset<backend::Resumed<'a>> {
544
+
545
+ backend::Resumed::create(fbb, &backend::ResumedArgs {
546
+ ok: j.ok,
547
+ from: j.from,
548
+ ..Default::default()
549
+ })
550
+ }
551
+
552
+ #[derive(Deserialize, Default)]
553
+ #[serde(rename_all = "camelCase")]
554
+ pub struct RpcCallJson {
555
+ #[serde(default)]
556
+ pub id: String,
557
+ #[serde(default)]
558
+ pub name: String,
559
+ #[serde(default)]
560
+ pub payload_b64: String,
561
+ }
562
+
563
+ pub fn build_rpc_call<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &RpcCallJson) -> flatbuffers::WIPOffset<backend::RpcCall<'a>> {
564
+ let id = fbb.create_string(&j.id);
565
+ let name = fbb.create_string(&j.name);
566
+ let payload_b64 = fbb.create_string(&j.payload_b64);
567
+ backend::RpcCall::create(fbb, &backend::RpcCallArgs {
568
+ id: Some(id),
569
+ name: Some(name),
570
+ payload_b64: Some(payload_b64),
571
+ ..Default::default()
572
+ })
573
+ }
574
+
575
+ #[derive(Deserialize, Default)]
576
+ #[serde(rename_all = "camelCase")]
577
+ pub struct RpcResultJson {
578
+ #[serde(default)]
579
+ pub id: String,
580
+ #[serde(default)]
581
+ pub ok: bool,
582
+ #[serde(default)]
583
+ pub err: String,
584
+ #[serde(default)]
585
+ pub payload_b64: String,
586
+ }
587
+
588
+ pub fn build_rpc_result<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &RpcResultJson) -> flatbuffers::WIPOffset<backend::RpcResult<'a>> {
589
+ let id = fbb.create_string(&j.id);
590
+ let err = fbb.create_string(&j.err);
591
+ let payload_b64 = fbb.create_string(&j.payload_b64);
592
+ backend::RpcResult::create(fbb, &backend::RpcResultArgs {
593
+ id: Some(id),
594
+ ok: j.ok,
595
+ err: Some(err),
596
+ payload_b64: Some(payload_b64),
597
+ ..Default::default()
598
+ })
599
+ }
600
+
516
601
  pub fn serialize_quote(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
517
602
  let j: QuoteJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
518
603
  copy_finished(out, |fbb| build_quote(fbb, &j))
@@ -588,6 +673,26 @@ pub fn serialize_pong(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeErr
588
673
  copy_finished(out, |fbb| build_pong(fbb, &j))
589
674
  }
590
675
 
676
+ pub fn serialize_resume(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
677
+ let j: ResumeJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
678
+ copy_finished(out, |fbb| build_resume(fbb, &j))
679
+ }
680
+
681
+ pub fn serialize_resumed(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
682
+ let j: ResumedJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
683
+ copy_finished(out, |fbb| build_resumed(fbb, &j))
684
+ }
685
+
686
+ pub fn serialize_rpc_call(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
687
+ let j: RpcCallJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
688
+ copy_finished(out, |fbb| build_rpc_call(fbb, &j))
689
+ }
690
+
691
+ pub fn serialize_rpc_result(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
692
+ let j: RpcResultJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
693
+ copy_finished(out, |fbb| build_rpc_result(fbb, &j))
694
+ }
695
+
591
696
  fn read_u32(b: &[u8], off: &mut usize) -> u32 {
592
697
  let v = u32::from_le_bytes([b[*off], b[*off + 1], b[*off + 2], b[*off + 3]]);
593
698
  *off += 4;
@@ -713,7 +818,7 @@ fn read_packed_welcome_groups<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
713
818
  }
714
819
 
715
820
 
716
- #[no_mangle]
821
+ #[export_name = "fb_quote_serialize"]
717
822
  pub unsafe extern "C" fn fb_quote_serialize(
718
823
  symbol: *const c_char,
719
824
  bid: f64,
@@ -753,6 +858,10 @@ pub unsafe extern "C" fn fb_quote_serialize(
753
858
  *out.add(0) = WIRE_VERSION;
754
859
  let id_bytes = 2995289047u32.to_le_bytes();
755
860
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
861
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
862
+ // server stamps per-destination delivery seqs on send.
863
+ *out.add(5) = 0;
864
+ std::ptr::write_bytes(out.add(6), 0, 8);
756
865
  }
757
866
  needed
758
867
  }
@@ -762,7 +871,7 @@ pub unsafe extern "C" fn fb_quote_serialize(
762
871
  .unwrap_or(0)
763
872
  }
764
873
 
765
- #[no_mangle]
874
+ #[export_name = "fb_trade_serialize"]
766
875
  pub unsafe extern "C" fn fb_trade_serialize(
767
876
  symbol: *const c_char,
768
877
  price: f64,
@@ -801,6 +910,10 @@ pub unsafe extern "C" fn fb_trade_serialize(
801
910
  *out.add(0) = WIRE_VERSION;
802
911
  let id_bytes = 1362521689u32.to_le_bytes();
803
912
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
913
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
914
+ // server stamps per-destination delivery seqs on send.
915
+ *out.add(5) = 0;
916
+ std::ptr::write_bytes(out.add(6), 0, 8);
804
917
  }
805
918
  needed
806
919
  }
@@ -810,7 +923,7 @@ pub unsafe extern "C" fn fb_trade_serialize(
810
923
  .unwrap_or(0)
811
924
  }
812
925
 
813
- #[no_mangle]
926
+ #[export_name = "fb_portfolio_serialize"]
814
927
  pub unsafe extern "C" fn fb_portfolio_serialize(
815
928
  account_id: *const c_char,
816
929
  positions_ptr: *const u8,
@@ -854,6 +967,10 @@ pub unsafe extern "C" fn fb_portfolio_serialize(
854
967
  *out.add(0) = WIRE_VERSION;
855
968
  let id_bytes = 464274147u32.to_le_bytes();
856
969
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
970
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
971
+ // server stamps per-destination delivery seqs on send.
972
+ *out.add(5) = 0;
973
+ std::ptr::write_bytes(out.add(6), 0, 8);
857
974
  }
858
975
  needed
859
976
  }
@@ -863,7 +980,7 @@ pub unsafe extern "C" fn fb_portfolio_serialize(
863
980
  .unwrap_or(0)
864
981
  }
865
982
 
866
- #[no_mangle]
983
+ #[export_name = "fb_complex_serialize"]
867
984
  pub unsafe extern "C" fn fb_complex_serialize(
868
985
  id: *const c_char,
869
986
  names_ptr: *const u8,
@@ -919,6 +1036,10 @@ pub unsafe extern "C" fn fb_complex_serialize(
919
1036
  *out.add(0) = WIRE_VERSION;
920
1037
  let id_bytes = 2318166199u32.to_le_bytes();
921
1038
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1039
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1040
+ // server stamps per-destination delivery seqs on send.
1041
+ *out.add(5) = 0;
1042
+ std::ptr::write_bytes(out.add(6), 0, 8);
922
1043
  }
923
1044
  needed
924
1045
  }
@@ -928,7 +1049,7 @@ pub unsafe extern "C" fn fb_complex_serialize(
928
1049
  .unwrap_or(0)
929
1050
  }
930
1051
 
931
- #[no_mangle]
1052
+ #[export_name = "fb_big_val_serialize"]
932
1053
  pub unsafe extern "C" fn fb_big_val_serialize(
933
1054
  id: *const c_char,
934
1055
  seq: i64,
@@ -962,6 +1083,10 @@ pub unsafe extern "C" fn fb_big_val_serialize(
962
1083
  *out.add(0) = WIRE_VERSION;
963
1084
  let id_bytes = 3211729064u32.to_le_bytes();
964
1085
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1086
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1087
+ // server stamps per-destination delivery seqs on send.
1088
+ *out.add(5) = 0;
1089
+ std::ptr::write_bytes(out.add(6), 0, 8);
965
1090
  }
966
1091
  needed
967
1092
  }
@@ -971,7 +1096,7 @@ pub unsafe extern "C" fn fb_big_val_serialize(
971
1096
  .unwrap_or(0)
972
1097
  }
973
1098
 
974
- #[no_mangle]
1099
+ #[export_name = "fb_hello_serialize"]
975
1100
  pub unsafe extern "C" fn fb_hello_serialize(
976
1101
  version: i64,
977
1102
  caps_ptr: *const u8,
@@ -1006,6 +1131,10 @@ pub unsafe extern "C" fn fb_hello_serialize(
1006
1131
  *out.add(0) = WIRE_VERSION;
1007
1132
  let id_bytes = 1335831723u32.to_le_bytes();
1008
1133
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1134
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1135
+ // server stamps per-destination delivery seqs on send.
1136
+ *out.add(5) = 0;
1137
+ std::ptr::write_bytes(out.add(6), 0, 8);
1009
1138
  }
1010
1139
  needed
1011
1140
  }
@@ -1015,7 +1144,7 @@ pub unsafe extern "C" fn fb_hello_serialize(
1015
1144
  .unwrap_or(0)
1016
1145
  }
1017
1146
 
1018
- #[no_mangle]
1147
+ #[export_name = "fb_welcome_serialize"]
1019
1148
  pub unsafe extern "C" fn fb_welcome_serialize(
1020
1149
  client_id: *const c_char,
1021
1150
  groups_ptr: *const u8,
@@ -1049,6 +1178,10 @@ pub unsafe extern "C" fn fb_welcome_serialize(
1049
1178
  *out.add(0) = WIRE_VERSION;
1050
1179
  let id_bytes = 669089267u32.to_le_bytes();
1051
1180
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1181
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1182
+ // server stamps per-destination delivery seqs on send.
1183
+ *out.add(5) = 0;
1184
+ std::ptr::write_bytes(out.add(6), 0, 8);
1052
1185
  }
1053
1186
  needed
1054
1187
  }
@@ -1058,7 +1191,7 @@ pub unsafe extern "C" fn fb_welcome_serialize(
1058
1191
  .unwrap_or(0)
1059
1192
  }
1060
1193
 
1061
- #[no_mangle]
1194
+ #[export_name = "fb_subscribe_serialize"]
1062
1195
  pub unsafe extern "C" fn fb_subscribe_serialize(
1063
1196
  topic: *const c_char,
1064
1197
  out: *mut u8,
@@ -1088,6 +1221,10 @@ pub unsafe extern "C" fn fb_subscribe_serialize(
1088
1221
  *out.add(0) = WIRE_VERSION;
1089
1222
  let id_bytes = 2946386435u32.to_le_bytes();
1090
1223
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1224
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1225
+ // server stamps per-destination delivery seqs on send.
1226
+ *out.add(5) = 0;
1227
+ std::ptr::write_bytes(out.add(6), 0, 8);
1091
1228
  }
1092
1229
  needed
1093
1230
  }
@@ -1097,7 +1234,7 @@ pub unsafe extern "C" fn fb_subscribe_serialize(
1097
1234
  .unwrap_or(0)
1098
1235
  }
1099
1236
 
1100
- #[no_mangle]
1237
+ #[export_name = "fb_unsubscribe_serialize"]
1101
1238
  pub unsafe extern "C" fn fb_unsubscribe_serialize(
1102
1239
  topic: *const c_char,
1103
1240
  out: *mut u8,
@@ -1127,6 +1264,10 @@ pub unsafe extern "C" fn fb_unsubscribe_serialize(
1127
1264
  *out.add(0) = WIRE_VERSION;
1128
1265
  let id_bytes = 4190043798u32.to_le_bytes();
1129
1266
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1267
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1268
+ // server stamps per-destination delivery seqs on send.
1269
+ *out.add(5) = 0;
1270
+ std::ptr::write_bytes(out.add(6), 0, 8);
1130
1271
  }
1131
1272
  needed
1132
1273
  }
@@ -1136,7 +1277,7 @@ pub unsafe extern "C" fn fb_unsubscribe_serialize(
1136
1277
  .unwrap_or(0)
1137
1278
  }
1138
1279
 
1139
- #[no_mangle]
1280
+ #[export_name = "fb_join_group_serialize"]
1140
1281
  pub unsafe extern "C" fn fb_join_group_serialize(
1141
1282
  group: *const c_char,
1142
1283
  out: *mut u8,
@@ -1166,6 +1307,10 @@ pub unsafe extern "C" fn fb_join_group_serialize(
1166
1307
  *out.add(0) = WIRE_VERSION;
1167
1308
  let id_bytes = 3026600144u32.to_le_bytes();
1168
1309
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1310
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1311
+ // server stamps per-destination delivery seqs on send.
1312
+ *out.add(5) = 0;
1313
+ std::ptr::write_bytes(out.add(6), 0, 8);
1169
1314
  }
1170
1315
  needed
1171
1316
  }
@@ -1175,7 +1320,7 @@ pub unsafe extern "C" fn fb_join_group_serialize(
1175
1320
  .unwrap_or(0)
1176
1321
  }
1177
1322
 
1178
- #[no_mangle]
1323
+ #[export_name = "fb_leave_group_serialize"]
1179
1324
  pub unsafe extern "C" fn fb_leave_group_serialize(
1180
1325
  group: *const c_char,
1181
1326
  out: *mut u8,
@@ -1205,6 +1350,10 @@ pub unsafe extern "C" fn fb_leave_group_serialize(
1205
1350
  *out.add(0) = WIRE_VERSION;
1206
1351
  let id_bytes = 3755061443u32.to_le_bytes();
1207
1352
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1353
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1354
+ // server stamps per-destination delivery seqs on send.
1355
+ *out.add(5) = 0;
1356
+ std::ptr::write_bytes(out.add(6), 0, 8);
1208
1357
  }
1209
1358
  needed
1210
1359
  }
@@ -1214,9 +1363,10 @@ pub unsafe extern "C" fn fb_leave_group_serialize(
1214
1363
  .unwrap_or(0)
1215
1364
  }
1216
1365
 
1217
- #[no_mangle]
1366
+ #[export_name = "fb_snapshot_request_serialize"]
1218
1367
  pub unsafe extern "C" fn fb_snapshot_request_serialize(
1219
1368
  topic: *const c_char,
1369
+ from_seq: i64,
1220
1370
  out: *mut u8,
1221
1371
  out_cap: usize,
1222
1372
  ) -> usize {
@@ -1235,6 +1385,7 @@ pub unsafe extern "C" fn fb_snapshot_request_serialize(
1235
1385
  let topic = fbb.create_string(topic);
1236
1386
  backend::SnapshotRequest::create(fbb, &backend::SnapshotRequestArgs {
1237
1387
  topic: Some(topic),
1388
+ from_seq: from_seq,
1238
1389
  ..Default::default()
1239
1390
  })
1240
1391
  }) {
@@ -1244,6 +1395,10 @@ pub unsafe extern "C" fn fb_snapshot_request_serialize(
1244
1395
  *out.add(0) = WIRE_VERSION;
1245
1396
  let id_bytes = 1249175018u32.to_le_bytes();
1246
1397
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1398
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1399
+ // server stamps per-destination delivery seqs on send.
1400
+ *out.add(5) = 0;
1401
+ std::ptr::write_bytes(out.add(6), 0, 8);
1247
1402
  }
1248
1403
  needed
1249
1404
  }
@@ -1253,7 +1408,7 @@ pub unsafe extern "C" fn fb_snapshot_request_serialize(
1253
1408
  .unwrap_or(0)
1254
1409
  }
1255
1410
 
1256
- #[no_mangle]
1411
+ #[export_name = "fb_ping_serialize"]
1257
1412
  pub unsafe extern "C" fn fb_ping_serialize(
1258
1413
  ts: i64,
1259
1414
  out: *mut u8,
@@ -1283,6 +1438,10 @@ pub unsafe extern "C" fn fb_ping_serialize(
1283
1438
  *out.add(0) = WIRE_VERSION;
1284
1439
  let id_bytes = 375255177u32.to_le_bytes();
1285
1440
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1441
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1442
+ // server stamps per-destination delivery seqs on send.
1443
+ *out.add(5) = 0;
1444
+ std::ptr::write_bytes(out.add(6), 0, 8);
1286
1445
  }
1287
1446
  needed
1288
1447
  }
@@ -1292,7 +1451,7 @@ pub unsafe extern "C" fn fb_ping_serialize(
1292
1451
  .unwrap_or(0)
1293
1452
  }
1294
1453
 
1295
- #[no_mangle]
1454
+ #[export_name = "fb_pong_serialize"]
1296
1455
  pub unsafe extern "C" fn fb_pong_serialize(
1297
1456
  ts: i64,
1298
1457
  out: *mut u8,
@@ -1322,6 +1481,202 @@ pub unsafe extern "C" fn fb_pong_serialize(
1322
1481
  *out.add(0) = WIRE_VERSION;
1323
1482
  let id_bytes = 2061178551u32.to_le_bytes();
1324
1483
  std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1484
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1485
+ // server stamps per-destination delivery seqs on send.
1486
+ *out.add(5) = 0;
1487
+ std::ptr::write_bytes(out.add(6), 0, 8);
1488
+ }
1489
+ needed
1490
+ }
1491
+ Err(_) => 0,
1492
+ }
1493
+ }))
1494
+ .unwrap_or(0)
1495
+ }
1496
+
1497
+ #[export_name = "fb_resume_serialize"]
1498
+ pub unsafe extern "C" fn fb_resume_serialize(
1499
+ last_seq: i64,
1500
+ out: *mut u8,
1501
+ out_cap: usize,
1502
+ ) -> usize {
1503
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1504
+ if out.is_null() && out_cap != 0 {
1505
+ return 0;
1506
+ }
1507
+
1508
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1509
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1510
+ &mut []
1511
+ } else {
1512
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1513
+ };
1514
+ match copy_finished(payload, |fbb| {
1515
+
1516
+ backend::Resume::create(fbb, &backend::ResumeArgs {
1517
+ last_seq: last_seq,
1518
+ ..Default::default()
1519
+ })
1520
+ }) {
1521
+ Ok(written) => {
1522
+ let needed = written + WIRE_HEADER_LEN;
1523
+ if needed <= out_cap {
1524
+ *out.add(0) = WIRE_VERSION;
1525
+ let id_bytes = 4281967466u32.to_le_bytes();
1526
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1527
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1528
+ // server stamps per-destination delivery seqs on send.
1529
+ *out.add(5) = 0;
1530
+ std::ptr::write_bytes(out.add(6), 0, 8);
1531
+ }
1532
+ needed
1533
+ }
1534
+ Err(_) => 0,
1535
+ }
1536
+ }))
1537
+ .unwrap_or(0)
1538
+ }
1539
+
1540
+ #[export_name = "fb_resumed_serialize"]
1541
+ pub unsafe extern "C" fn fb_resumed_serialize(
1542
+ ok: u8,
1543
+ from: i64,
1544
+ out: *mut u8,
1545
+ out_cap: usize,
1546
+ ) -> usize {
1547
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1548
+ if out.is_null() && out_cap != 0 {
1549
+ return 0;
1550
+ }
1551
+
1552
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1553
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1554
+ &mut []
1555
+ } else {
1556
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1557
+ };
1558
+ match copy_finished(payload, |fbb| {
1559
+
1560
+ backend::Resumed::create(fbb, &backend::ResumedArgs {
1561
+ ok: ok != 0,
1562
+ from: from,
1563
+ ..Default::default()
1564
+ })
1565
+ }) {
1566
+ Ok(written) => {
1567
+ let needed = written + WIRE_HEADER_LEN;
1568
+ if needed <= out_cap {
1569
+ *out.add(0) = WIRE_VERSION;
1570
+ let id_bytes = 3585847050u32.to_le_bytes();
1571
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1572
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1573
+ // server stamps per-destination delivery seqs on send.
1574
+ *out.add(5) = 0;
1575
+ std::ptr::write_bytes(out.add(6), 0, 8);
1576
+ }
1577
+ needed
1578
+ }
1579
+ Err(_) => 0,
1580
+ }
1581
+ }))
1582
+ .unwrap_or(0)
1583
+ }
1584
+
1585
+ #[export_name = "fb_rpc_call_serialize"]
1586
+ pub unsafe extern "C" fn fb_rpc_call_serialize(
1587
+ id: *const c_char,
1588
+ name: *const c_char,
1589
+ payload_b64: *const c_char,
1590
+ out: *mut u8,
1591
+ out_cap: usize,
1592
+ ) -> usize {
1593
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1594
+ if out.is_null() && out_cap != 0 {
1595
+ return 0;
1596
+ }
1597
+ let id = if id.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(id).to_bytes()) };
1598
+ let name = if name.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(name).to_bytes()) };
1599
+ let payload_b64 = if payload_b64.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(payload_b64).to_bytes()) };
1600
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1601
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1602
+ &mut []
1603
+ } else {
1604
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1605
+ };
1606
+ match copy_finished(payload, |fbb| {
1607
+ let id = fbb.create_string(id);
1608
+ let name = fbb.create_string(name);
1609
+ let payload_b64 = fbb.create_string(payload_b64);
1610
+ backend::RpcCall::create(fbb, &backend::RpcCallArgs {
1611
+ id: Some(id),
1612
+ name: Some(name),
1613
+ payload_b64: Some(payload_b64),
1614
+ ..Default::default()
1615
+ })
1616
+ }) {
1617
+ Ok(written) => {
1618
+ let needed = written + WIRE_HEADER_LEN;
1619
+ if needed <= out_cap {
1620
+ *out.add(0) = WIRE_VERSION;
1621
+ let id_bytes = 986404564u32.to_le_bytes();
1622
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1623
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1624
+ // server stamps per-destination delivery seqs on send.
1625
+ *out.add(5) = 0;
1626
+ std::ptr::write_bytes(out.add(6), 0, 8);
1627
+ }
1628
+ needed
1629
+ }
1630
+ Err(_) => 0,
1631
+ }
1632
+ }))
1633
+ .unwrap_or(0)
1634
+ }
1635
+
1636
+ #[export_name = "fb_rpc_result_serialize"]
1637
+ pub unsafe extern "C" fn fb_rpc_result_serialize(
1638
+ id: *const c_char,
1639
+ ok: u8,
1640
+ err: *const c_char,
1641
+ payload_b64: *const c_char,
1642
+ out: *mut u8,
1643
+ out_cap: usize,
1644
+ ) -> usize {
1645
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1646
+ if out.is_null() && out_cap != 0 {
1647
+ return 0;
1648
+ }
1649
+ let id = if id.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(id).to_bytes()) };
1650
+ let err = if err.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(err).to_bytes()) };
1651
+ let payload_b64 = if payload_b64.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(payload_b64).to_bytes()) };
1652
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1653
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1654
+ &mut []
1655
+ } else {
1656
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1657
+ };
1658
+ match copy_finished(payload, |fbb| {
1659
+ let id = fbb.create_string(id);
1660
+ let err = fbb.create_string(err);
1661
+ let payload_b64 = fbb.create_string(payload_b64);
1662
+ backend::RpcResult::create(fbb, &backend::RpcResultArgs {
1663
+ id: Some(id),
1664
+ ok: ok != 0,
1665
+ err: Some(err),
1666
+ payload_b64: Some(payload_b64),
1667
+ ..Default::default()
1668
+ })
1669
+ }) {
1670
+ Ok(written) => {
1671
+ let needed = written + WIRE_HEADER_LEN;
1672
+ if needed <= out_cap {
1673
+ *out.add(0) = WIRE_VERSION;
1674
+ let id_bytes = 648609069u32.to_le_bytes();
1675
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1676
+ // [flags:1][seq:u64 LE] — pristine (flags=0, seq=0); the
1677
+ // server stamps per-destination delivery seqs on send.
1678
+ *out.add(5) = 0;
1679
+ std::ptr::write_bytes(out.add(6), 0, 8);
1325
1680
  }
1326
1681
  needed
1327
1682
  }
@@ -1348,6 +1703,10 @@ pub fn serialize_event(event_id: u32, json: &[u8], out: &mut [u8]) -> Result<usi
1348
1703
  1249175018 => serialize_snapshot_request(json, out),
1349
1704
  375255177 => serialize_ping(json, out),
1350
1705
  2061178551 => serialize_pong(json, out),
1706
+ 4281967466 => serialize_resume(json, out),
1707
+ 3585847050 => serialize_resumed(json, out),
1708
+ 986404564 => serialize_rpc_call(json, out),
1709
+ 648609069 => serialize_rpc_result(json, out),
1351
1710
  _ => Err(TranscodeError::UnknownEvent),
1352
1711
  }
1353
1712
  }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Inbound pipeline — the bridge's NATS→clients forward. Subscribes a subject
3
+ * and, for each arriving frame: reads the envelope header (cheap), rejects
4
+ * transport-internal control frames and non-allowlisted events, then decodes
5
+ * the payload and hands `{name, payload}` to the wired `onInbound` callback.
6
+ *
7
+ * Decode failures are counted in `stats.bridgeInboundErrors`, never thrown —
8
+ * the WS hot path must not depend on broker input being well-formed.
9
+ */
10
+ import type { Bindings } from "../../bindings/types";
11
+ import type { NatsBridgeStats, NatsTransport } from "./types";
12
+
13
+ export function subscribeInboundSubject(deps: {
14
+ transport: NatsTransport;
15
+ bindings: Bindings;
16
+ stats: NatsBridgeStats;
17
+ subject: string;
18
+ /** allowlist; `null` = forward every app event */
19
+ allowlist: Set<string> | null;
20
+ /** set once by the server (the fan-out-to-clients callback) */
21
+ getOnInbound(): ((name: string, payload: unknown) => void) | null;
22
+ }): () => void {
23
+ const { transport, bindings, stats, subject, allowlist } = deps;
24
+ return transport.subscribe(subject, (data) => {
25
+ // header-first: reject junk/control frames before any payload decode
26
+ const header = bindings.readFrameHeader(data);
27
+ if (!header) {
28
+ stats.bridgeInboundErrors++;
29
+ return;
30
+ }
31
+ if (bindings.isControlId(header.id)) {
32
+ stats.bridgeInboundErrors++; // never forward transport-internal frames
33
+ return;
34
+ }
35
+ if (allowlist && !allowlist.has(header.name)) return;
36
+ let payload: unknown;
37
+ try {
38
+ payload = bindings.decodePayload(header.id, data);
39
+ } catch {
40
+ stats.bridgeInboundErrors++;
41
+ return;
42
+ }
43
+ stats.bridgeInbound++;
44
+ deps.getOnInbound()?.(header.name, payload);
45
+ });
46
+ }