ruvector_graph_transformer_node/lib.rs
1//! Node.js bindings for RuVector Graph Transformer via NAPI-RS
2//!
3//! Exposes proof-gated operations, sublinear attention, physics-informed
4//! layers, biological-inspired learning, verified training, manifold
5//! distance, temporal causal attention, and economic game-theoretic
6//! attention to Node.js applications.
7//!
8//! This crate embeds a self-contained graph transformer implementation
9//! to avoid coupling with the evolving `ruvector-graph-transformer` crate.
10
11#![deny(clippy::all)]
12
13mod transformer;
14
15use napi::bindgen_prelude::*;
16use napi_derive::napi;
17use transformer::{CoreGraphTransformer, Edge as CoreEdge, PipelineStage as CorePipelineStage};
18
19/// Graph Transformer with proof-gated operations for Node.js.
20///
21/// Provides sublinear attention over graph structures, physics-informed
22/// layers (Hamiltonian dynamics), biologically-inspired learning (spiking
23/// networks, Hebbian plasticity), and verified training with proof receipts.
24///
25/// # Example
26/// ```javascript
27/// const { GraphTransformer } = require('ruvector-graph-transformer-node');
28/// const gt = new GraphTransformer();
29/// console.log(gt.version());
30/// ```
31#[napi]
32pub struct GraphTransformer {
33 inner: CoreGraphTransformer,
34}
35
36#[napi]
37impl GraphTransformer {
38 /// Create a new Graph Transformer instance.
39 ///
40 /// # Arguments
41 /// * `config` - Optional JSON configuration (reserved for future use)
42 ///
43 /// # Example
44 /// ```javascript
45 /// const gt = new GraphTransformer();
46 /// const gt2 = new GraphTransformer({ maxFuel: 10000 });
47 /// ```
48 #[napi(constructor)]
49 pub fn new(_config: Option<serde_json::Value>) -> Self {
50 Self {
51 inner: CoreGraphTransformer::new(),
52 }
53 }
54
55 /// Get the library version string.
56 ///
57 /// # Example
58 /// ```javascript
59 /// console.log(gt.version()); // "2.0.4"
60 /// ```
61 #[napi]
62 pub fn version(&self) -> String {
63 self.inner.version()
64 }
65
66 // ===================================================================
67 // Proof-Gated Operations
68 // ===================================================================
69
70 /// Create a proof gate for a given dimension.
71 ///
72 /// Returns a JSON object describing the gate (id, dimension, verified).
73 ///
74 /// # Arguments
75 /// * `dim` - The dimension to gate on
76 ///
77 /// # Example
78 /// ```javascript
79 /// const gate = gt.createProofGate(128);
80 /// console.log(gate.dimension); // 128
81 /// ```
82 #[napi]
83 pub fn create_proof_gate(&mut self, dim: u32) -> Result<serde_json::Value> {
84 let gate = self.inner.create_proof_gate(dim);
85 serde_json::to_value(&gate).map_err(|e| {
86 Error::new(
87 Status::GenericFailure,
88 format!("Serialization error: {}", e),
89 )
90 })
91 }
92
93 /// Prove that two dimensions are equal.
94 ///
95 /// Returns a proof result with proof_id, expected, actual, and verified fields.
96 ///
97 /// # Arguments
98 /// * `expected` - The expected dimension
99 /// * `actual` - The actual dimension
100 ///
101 /// # Example
102 /// ```javascript
103 /// const proof = gt.proveDimension(128, 128);
104 /// console.log(proof.verified); // true
105 /// ```
106 #[napi]
107 pub fn prove_dimension(&mut self, expected: u32, actual: u32) -> Result<serde_json::Value> {
108 let result = self
109 .inner
110 .prove_dimension(expected, actual)
111 .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))?;
112 serde_json::to_value(&result).map_err(|e| {
113 Error::new(
114 Status::GenericFailure,
115 format!("Serialization error: {}", e),
116 )
117 })
118 }
119
120 /// Create a proof attestation (serializable receipt) for a given proof ID.
121 ///
122 /// Returns the attestation as a byte buffer (82 bytes) that can be
123 /// embedded in RVF WITNESS_SEG entries.
124 ///
125 /// # Arguments
126 /// * `proof_id` - The proof term ID to create an attestation for
127 ///
128 /// # Example
129 /// ```javascript
130 /// const proof = gt.proveDimension(64, 64);
131 /// const attestation = gt.createAttestation(proof.proof_id);
132 /// console.log(attestation.length); // 82
133 /// ```
134 #[napi]
135 pub fn create_attestation(&self, proof_id: u32) -> Result<Vec<u8>> {
136 let att = self.inner.create_attestation(proof_id);
137 Ok(att.to_bytes())
138 }
139
140 /// Compose a chain of pipeline stages, verifying type compatibility.
141 ///
142 /// Each stage must have `name`, `input_type_id`, and `output_type_id`.
143 /// Returns a composed proof with the overall input/output types and
144 /// the number of stages verified.
145 ///
146 /// # Arguments
147 /// * `stages` - Array of stage descriptors as JSON objects
148 ///
149 /// # Example
150 /// ```javascript
151 /// const composed = gt.composeProofs([
152 /// { name: 'embed', input_type_id: 1, output_type_id: 2 },
153 /// { name: 'align', input_type_id: 2, output_type_id: 3 },
154 /// ]);
155 /// console.log(composed.chain_name); // "embed >> align"
156 /// ```
157 #[napi]
158 pub fn compose_proofs(&mut self, stages: Vec<serde_json::Value>) -> Result<serde_json::Value> {
159 let rust_stages: Vec<CorePipelineStage> = stages
160 .into_iter()
161 .map(|v| {
162 serde_json::from_value(v).map_err(|e| {
163 Error::new(
164 Status::InvalidArg,
165 format!("Invalid stage descriptor: {}", e),
166 )
167 })
168 })
169 .collect::<Result<Vec<_>>>()?;
170
171 let result = self
172 .inner
173 .compose_proofs(&rust_stages)
174 .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))?;
175
176 serde_json::to_value(&result).map_err(|e| {
177 Error::new(
178 Status::GenericFailure,
179 format!("Serialization error: {}", e),
180 )
181 })
182 }
183
184 /// Verify an attestation from its byte representation.
185 ///
186 /// Returns `true` if the attestation is structurally valid.
187 ///
188 /// # Arguments
189 /// * `bytes` - The attestation bytes (82 bytes minimum)
190 ///
191 /// # Example
192 /// ```javascript
193 /// const valid = gt.verifyAttestation(attestationBytes);
194 /// ```
195 #[napi]
196 pub fn verify_attestation(&self, bytes: Vec<u8>) -> bool {
197 self.inner.verify_attestation(&bytes)
198 }
199
200 // ===================================================================
201 // Sublinear Attention
202 // ===================================================================
203
204 /// Sublinear graph attention using personalized PageRank sparsification.
205 ///
206 /// Instead of attending to all N nodes (O(N*d)), uses PPR to select
207 /// the top-k most relevant nodes, achieving O(k*d) complexity.
208 ///
209 /// # Arguments
210 /// * `query` - Query vector (length must equal `dim`)
211 /// * `edges` - Adjacency list: edges[i] is the list of neighbor indices for node i
212 /// * `dim` - Dimension of the query vector
213 /// * `k` - Number of top nodes to attend to
214 ///
215 /// # Returns
216 /// JSON object with `scores`, `top_k_indices`, and `sparsity_ratio`
217 ///
218 /// # Example
219 /// ```javascript
220 /// const result = gt.sublinearAttention([1.0, 0.5], [[1, 2], [0, 2], [0, 1]], 2, 2);
221 /// console.log(result.top_k_indices);
222 /// ```
223 #[napi]
224 pub fn sublinear_attention(
225 &mut self,
226 query: Vec<f64>,
227 edges: Vec<Vec<u32>>,
228 dim: u32,
229 k: u32,
230 ) -> Result<serde_json::Value> {
231 let result = self
232 .inner
233 .sublinear_attention(&query, &edges, dim, k)
234 .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))?;
235
236 serde_json::to_value(&result).map_err(|e| {
237 Error::new(
238 Status::GenericFailure,
239 format!("Serialization error: {}", e),
240 )
241 })
242 }
243
244 /// Compute personalized PageRank scores from a source node.
245 ///
246 /// # Arguments
247 /// * `source` - Source node index
248 /// * `adjacency` - Adjacency list for the graph
249 /// * `alpha` - Teleport probability (typically 0.15)
250 ///
251 /// # Returns
252 /// Array of PPR scores, one per node
253 ///
254 /// # Example
255 /// ```javascript
256 /// const scores = gt.pprScores(0, [[1], [0, 2], [1]], 0.15);
257 /// ```
258 #[napi]
259 pub fn ppr_scores(
260 &mut self,
261 source: u32,
262 adjacency: Vec<Vec<u32>>,
263 alpha: f64,
264 ) -> Result<Vec<f64>> {
265 Ok(self.inner.ppr_scores(source, &adjacency, alpha))
266 }
267
268 // ===================================================================
269 // Physics-Informed Layers
270 // ===================================================================
271
272 /// Symplectic integrator step (leapfrog / Stormer-Verlet).
273 ///
274 /// Integrates Hamiltonian dynamics with a harmonic potential V(q) = 0.5*|q|^2,
275 /// preserving the symplectic structure (energy-conserving).
276 ///
277 /// # Arguments
278 /// * `positions` - Position coordinates
279 /// * `momenta` - Momentum coordinates (same length as positions)
280 /// * `dt` - Time step
281 ///
282 /// # Returns
283 /// JSON object with `positions`, `momenta`, and `energy`
284 ///
285 /// # Example
286 /// ```javascript
287 /// const state = gt.hamiltonianStep([1.0, 0.0], [0.0, 1.0], 0.01);
288 /// console.log(state.energy);
289 /// ```
290 #[napi]
291 pub fn hamiltonian_step(
292 &mut self,
293 positions: Vec<f64>,
294 momenta: Vec<f64>,
295 dt: f64,
296 ) -> Result<serde_json::Value> {
297 let result = self
298 .inner
299 .hamiltonian_step(&positions, &momenta, dt)
300 .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))?;
301
302 serde_json::to_value(&result).map_err(|e| {
303 Error::new(
304 Status::GenericFailure,
305 format!("Serialization error: {}", e),
306 )
307 })
308 }
309
310 /// Hamiltonian step with graph edge interactions.
311 ///
312 /// `positions` and `momenta` are arrays of coordinates. `edges` is an
313 /// array of `{ src, tgt }` objects defining graph interactions.
314 ///
315 /// # Returns
316 /// JSON object with `positions`, `momenta`, `energy`, and `energy_conserved`
317 ///
318 /// # Example
319 /// ```javascript
320 /// const state = gt.hamiltonianStepGraph(
321 /// [1.0, 0.0], [0.0, 1.0],
322 /// [{ src: 0, tgt: 1 }], 0.01
323 /// );
324 /// ```
325 #[napi]
326 pub fn hamiltonian_step_graph(
327 &mut self,
328 positions: Vec<f64>,
329 momenta: Vec<f64>,
330 edges: Vec<serde_json::Value>,
331 dt: f64,
332 ) -> Result<serde_json::Value> {
333 let rust_edges: Vec<CoreEdge> = edges
334 .into_iter()
335 .map(|v| {
336 serde_json::from_value(v)
337 .map_err(|e| Error::new(Status::InvalidArg, format!("Invalid edge: {}", e)))
338 })
339 .collect::<Result<Vec<_>>>()?;
340
341 let result = self
342 .inner
343 .hamiltonian_step_graph(&positions, &momenta, &rust_edges, dt)
344 .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))?;
345
346 serde_json::to_value(&result).map_err(|e| {
347 Error::new(
348 Status::GenericFailure,
349 format!("Serialization error: {}", e),
350 )
351 })
352 }
353
354 // ===================================================================
355 // Biological-Inspired
356 // ===================================================================
357
358 /// Spiking neural attention: event-driven sparse attention.
359 ///
360 /// Nodes emit attention only when their membrane potential exceeds
361 /// a threshold, producing sparse activation patterns.
362 ///
363 /// # Arguments
364 /// * `spikes` - Membrane potentials for each node
365 /// * `edges` - Adjacency list for the graph
366 /// * `threshold` - Firing threshold
367 ///
368 /// # Returns
369 /// Output activation vector (one value per node)
370 ///
371 /// # Example
372 /// ```javascript
373 /// const output = gt.spikingAttention([0.5, 1.5, 0.3], [[1], [0, 2], [1]], 1.0);
374 /// ```
375 #[napi]
376 pub fn spiking_attention(
377 &mut self,
378 spikes: Vec<f64>,
379 edges: Vec<Vec<u32>>,
380 threshold: f64,
381 ) -> Result<Vec<f64>> {
382 Ok(self.inner.spiking_attention(&spikes, &edges, threshold))
383 }
384
385 /// Hebbian learning rule update.
386 ///
387 /// Applies the outer-product Hebbian rule: w_ij += lr * pre_i * post_j.
388 /// The weight vector is a flattened (pre.len * post.len) matrix.
389 ///
390 /// # Arguments
391 /// * `pre` - Pre-synaptic activations
392 /// * `post` - Post-synaptic activations
393 /// * `weights` - Current weight vector (flattened matrix)
394 /// * `lr` - Learning rate
395 ///
396 /// # Returns
397 /// Updated weight vector
398 ///
399 /// # Example
400 /// ```javascript
401 /// const updated = gt.hebbianUpdate([1.0, 0.0], [0.0, 1.0], [0, 0, 0, 0], 0.1);
402 /// ```
403 #[napi]
404 pub fn hebbian_update(
405 &mut self,
406 pre: Vec<f64>,
407 post: Vec<f64>,
408 weights: Vec<f64>,
409 lr: f64,
410 ) -> Result<Vec<f64>> {
411 Ok(self.inner.hebbian_update(&pre, &post, &weights, lr))
412 }
413
414 /// Spiking step over 2D node features with adjacency matrix.
415 ///
416 /// `features` is an array of arrays (n x dim). `adjacency` is a flat
417 /// row-major array (n x n). Returns `{ features, spikes, weights }`.
418 ///
419 /// # Example
420 /// ```javascript
421 /// const result = gt.spikingStep(
422 /// [[0.8, 0.6], [0.1, 0.2]],
423 /// [0, 0.5, 0.3, 0]
424 /// );
425 /// ```
426 #[napi]
427 pub fn spiking_step(
428 &mut self,
429 features: Vec<Vec<f64>>,
430 adjacency: Vec<f64>,
431 ) -> Result<serde_json::Value> {
432 let result = self.inner.spiking_step(&features, &adjacency, 1.0);
433 serde_json::to_value(&result).map_err(|e| {
434 Error::new(
435 Status::GenericFailure,
436 format!("Serialization error: {}", e),
437 )
438 })
439 }
440
441 // ===================================================================
442 // Verified Training
443 // ===================================================================
444
445 /// A single verified SGD step with proof of gradient application.
446 ///
447 /// Applies w' = w - lr * grad and returns the new weights along with
448 /// a proof receipt, loss before/after, and gradient norm.
449 ///
450 /// # Arguments
451 /// * `weights` - Current weight vector
452 /// * `gradients` - Gradient vector (same length as weights)
453 /// * `lr` - Learning rate
454 ///
455 /// # Returns
456 /// JSON object with `weights`, `proof_id`, `loss_before`, `loss_after`, `gradient_norm`
457 ///
458 /// # Example
459 /// ```javascript
460 /// const result = gt.verifiedStep([1.0, 2.0], [0.1, 0.2], 0.01);
461 /// console.log(result.loss_after < result.loss_before); // true
462 /// ```
463 #[napi]
464 pub fn verified_step(
465 &mut self,
466 weights: Vec<f64>,
467 gradients: Vec<f64>,
468 lr: f64,
469 ) -> Result<serde_json::Value> {
470 let result = self
471 .inner
472 .verified_step(&weights, &gradients, lr)
473 .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))?;
474
475 serde_json::to_value(&result).map_err(|e| {
476 Error::new(
477 Status::GenericFailure,
478 format!("Serialization error: {}", e),
479 )
480 })
481 }
482
483 /// Verified training step with features, targets, and weights.
484 ///
485 /// Computes MSE loss, applies SGD, and produces a training certificate.
486 ///
487 /// # Arguments
488 /// * `features` - Input feature vector
489 /// * `targets` - Target values
490 /// * `weights` - Current weight vector
491 ///
492 /// # Returns
493 /// JSON object with `weights`, `certificate_id`, `loss`,
494 /// `loss_monotonic`, `lipschitz_satisfied`
495 ///
496 /// # Example
497 /// ```javascript
498 /// const result = gt.verifiedTrainingStep([1.0, 2.0], [0.5, 1.0], [0.5, 0.5]);
499 /// ```
500 #[napi]
501 pub fn verified_training_step(
502 &mut self,
503 features: Vec<f64>,
504 targets: Vec<f64>,
505 weights: Vec<f64>,
506 ) -> Result<serde_json::Value> {
507 let result = self
508 .inner
509 .verified_training_step(&features, &targets, &weights, 0.001)
510 .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))?;
511
512 serde_json::to_value(&result).map_err(|e| {
513 Error::new(
514 Status::GenericFailure,
515 format!("Serialization error: {}", e),
516 )
517 })
518 }
519
520 // ===================================================================
521 // Manifold
522 // ===================================================================
523
524 /// Product manifold distance (mixed curvature spaces).
525 ///
526 /// Splits vectors into sub-spaces according to the curvatures array:
527 /// - curvature > 0: spherical distance
528 /// - curvature < 0: hyperbolic distance
529 /// - curvature == 0: Euclidean distance
530 ///
531 /// # Arguments
532 /// * `a` - First point
533 /// * `b` - Second point (same length as `a`)
534 /// * `curvatures` - Curvature for each sub-space
535 ///
536 /// # Returns
537 /// The product manifold distance as a number
538 ///
539 /// # Example
540 /// ```javascript
541 /// const d = gt.productManifoldDistance([1, 0, 0, 1], [0, 1, 1, 0], [0.0, -1.0]);
542 /// ```
543 #[napi]
544 pub fn product_manifold_distance(&self, a: Vec<f64>, b: Vec<f64>, curvatures: Vec<f64>) -> f64 {
545 self.inner.product_manifold_distance(&a, &b, &curvatures)
546 }
547
548 /// Product manifold attention with mixed curvatures.
549 ///
550 /// Computes attention in a product of spherical, hyperbolic, and
551 /// Euclidean subspaces, combining the results.
552 ///
553 /// # Arguments
554 /// * `features` - Input feature vector
555 /// * `edges` - Array of `{ src, tgt }` objects
556 ///
557 /// # Returns
558 /// JSON object with `output`, `curvatures`, `distances`
559 ///
560 /// # Example
561 /// ```javascript
562 /// const result = gt.productManifoldAttention(
563 /// [1.0, 0.5, -0.3, 0.8],
564 /// [{ src: 0, tgt: 1 }]
565 /// );
566 /// ```
567 #[napi]
568 pub fn product_manifold_attention(
569 &mut self,
570 features: Vec<f64>,
571 edges: Vec<serde_json::Value>,
572 ) -> Result<serde_json::Value> {
573 let rust_edges: Vec<CoreEdge> = edges
574 .into_iter()
575 .map(|v| {
576 serde_json::from_value(v)
577 .map_err(|e| Error::new(Status::InvalidArg, format!("Invalid edge: {}", e)))
578 })
579 .collect::<Result<Vec<_>>>()?;
580
581 let curvatures = vec![0.0, -1.0]; // default mixed curvatures
582 let result = self
583 .inner
584 .product_manifold_attention(&features, &rust_edges, &curvatures);
585
586 serde_json::to_value(&result).map_err(|e| {
587 Error::new(
588 Status::GenericFailure,
589 format!("Serialization error: {}", e),
590 )
591 })
592 }
593
594 // ===================================================================
595 // Temporal
596 // ===================================================================
597
598 /// Causal attention with temporal ordering.
599 ///
600 /// Attention scores are masked so that a key at time t_j can only
601 /// attend to queries at time t_i <= t_j (no information leakage
602 /// from the future).
603 ///
604 /// # Arguments
605 /// * `query` - Query vector
606 /// * `keys` - Array of key vectors
607 /// * `timestamps` - Timestamp for each key (same length as keys)
608 ///
609 /// # Returns
610 /// Softmax attention weights (one per key, sums to 1.0)
611 ///
612 /// # Example
613 /// ```javascript
614 /// const scores = gt.causalAttention(
615 /// [1.0, 0.0],
616 /// [[1.0, 0.0], [0.0, 1.0], [0.5, 0.5]],
617 /// [1.0, 2.0, 3.0]
618 /// );
619 /// ```
620 #[napi]
621 pub fn causal_attention(
622 &mut self,
623 query: Vec<f64>,
624 keys: Vec<Vec<f64>>,
625 timestamps: Vec<f64>,
626 ) -> Result<Vec<f64>> {
627 Ok(self.inner.causal_attention(&query, &keys, ×tamps))
628 }
629
630 /// Causal attention over features, timestamps, and graph edges.
631 ///
632 /// Returns attention-weighted output features where each node can
633 /// only attend to neighbors with earlier or equal timestamps.
634 ///
635 /// # Arguments
636 /// * `features` - Feature value for each node
637 /// * `timestamps` - Timestamp for each node
638 /// * `edges` - Array of `{ src, tgt }` objects
639 ///
640 /// # Returns
641 /// Array of attention-weighted output values
642 ///
643 /// # Example
644 /// ```javascript
645 /// const output = gt.causalAttentionGraph(
646 /// [1.0, 0.5, 0.8],
647 /// [1.0, 2.0, 3.0],
648 /// [{ src: 0, tgt: 1 }, { src: 1, tgt: 2 }]
649 /// );
650 /// ```
651 #[napi]
652 pub fn causal_attention_graph(
653 &mut self,
654 features: Vec<f64>,
655 timestamps: Vec<f64>,
656 edges: Vec<serde_json::Value>,
657 ) -> Result<Vec<f64>> {
658 let rust_edges: Vec<CoreEdge> = edges
659 .into_iter()
660 .map(|v| {
661 serde_json::from_value(v)
662 .map_err(|e| Error::new(Status::InvalidArg, format!("Invalid edge: {}", e)))
663 })
664 .collect::<Result<Vec<_>>>()?;
665
666 Ok(self
667 .inner
668 .causal_attention_graph(&features, ×tamps, &rust_edges))
669 }
670
671 /// Extract Granger causality DAG from attention history.
672 ///
673 /// Tests pairwise Granger causality between all nodes and returns
674 /// edges where the F-statistic exceeds the significance threshold.
675 ///
676 /// # Arguments
677 /// * `attention_history` - Flat array (T x N, row-major)
678 /// * `num_nodes` - Number of nodes N
679 /// * `num_steps` - Number of time steps T
680 ///
681 /// # Returns
682 /// JSON object with `edges` and `num_nodes`
683 ///
684 /// # Example
685 /// ```javascript
686 /// const dag = gt.grangerExtract(flatHistory, 3, 20);
687 /// console.log(dag.edges); // [{ source, target, f_statistic, is_causal }]
688 /// ```
689 #[napi]
690 pub fn granger_extract(
691 &mut self,
692 attention_history: Vec<f64>,
693 num_nodes: u32,
694 num_steps: u32,
695 ) -> Result<serde_json::Value> {
696 let dag = self
697 .inner
698 .granger_extract(&attention_history, num_nodes, num_steps);
699
700 serde_json::to_value(&dag).map_err(|e| {
701 Error::new(
702 Status::GenericFailure,
703 format!("Serialization error: {}", e),
704 )
705 })
706 }
707
708 // ===================================================================
709 // Economic / Game-Theoretic
710 // ===================================================================
711
712 /// Game-theoretic attention: computes Nash equilibrium allocations.
713 ///
714 /// Each node is a player with features as utility parameters. Edges
715 /// define strategic interactions. Uses best-response iteration to
716 /// converge to Nash equilibrium.
717 ///
718 /// # Arguments
719 /// * `features` - Feature/utility value for each node
720 /// * `edges` - Array of `{ src, tgt }` objects
721 ///
722 /// # Returns
723 /// JSON object with `allocations`, `utilities`, `nash_gap`, `converged`
724 ///
725 /// # Example
726 /// ```javascript
727 /// const result = gt.gameTheoreticAttention(
728 /// [1.0, 0.5, 0.8],
729 /// [{ src: 0, tgt: 1 }, { src: 1, tgt: 2 }]
730 /// );
731 /// console.log(result.converged); // true
732 /// ```
733 #[napi]
734 pub fn game_theoretic_attention(
735 &mut self,
736 features: Vec<f64>,
737 edges: Vec<serde_json::Value>,
738 ) -> Result<serde_json::Value> {
739 let rust_edges: Vec<CoreEdge> = edges
740 .into_iter()
741 .map(|v| {
742 serde_json::from_value(v)
743 .map_err(|e| Error::new(Status::InvalidArg, format!("Invalid edge: {}", e)))
744 })
745 .collect::<Result<Vec<_>>>()?;
746
747 let result = self.inner.game_theoretic_attention(&features, &rust_edges);
748
749 serde_json::to_value(&result).map_err(|e| {
750 Error::new(
751 Status::GenericFailure,
752 format!("Serialization error: {}", e),
753 )
754 })
755 }
756
757 // ===================================================================
758 // Stats
759 // ===================================================================
760
761 /// Get aggregate statistics as a JSON object.
762 ///
763 /// # Example
764 /// ```javascript
765 /// const stats = gt.stats();
766 /// console.log(stats.proofs_verified);
767 /// ```
768 #[napi]
769 pub fn stats(&self) -> serde_json::Value {
770 serde_json::to_value(self.inner.stats()).unwrap_or(serde_json::Value::Null)
771 }
772
773 /// Reset all internal state (caches, counters, gates).
774 ///
775 /// # Example
776 /// ```javascript
777 /// gt.reset();
778 /// ```
779 #[napi]
780 pub fn reset(&mut self) {
781 self.inner.reset();
782 }
783}
784
785/// Get the library version.
786#[napi]
787pub fn version() -> String {
788 env!("CARGO_PKG_VERSION").to_string()
789}
790
791/// Module initialization message.
792#[napi]
793pub fn init() -> String {
794 "RuVector Graph Transformer Node.js bindings initialized".to_string()
795}