rusty_racer 0.1.7 → 0.1.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6e242a3632078fa63367b6e4d81ee7d720b6cfaef8355067137444b6fd48441
4
- data.tar.gz: 1996ff995ae82ca4dba4de4e17c2e577d820b606bed710813e27e32d7c7a178e
3
+ metadata.gz: af5578c6ac8ba4eb6d367097be2faef786fdc0cb077ac5c76c14f1ec50106d41
4
+ data.tar.gz: 5cc05af64db35a3202c3fd72d2b0809e621ee7fa59c3db4e913ea537d835c438
5
5
  SHA512:
6
- metadata.gz: 7bca218e9cb0384d7c4e61b8bb5d1c748eb38393163b5d104803698c8f51fe6fae9b4600d4a815be5b98b70a3dbce7ea60b2219d596a652e2f3b9c8144f50a55
7
- data.tar.gz: 05b6aa55215bb555000fb9b99b698ac974f74d73a4c9687d04f6fa84039d4890d92be58e7edf68a78befb98919e329e34d8c95e938cd886cf05d469219d2039a
6
+ metadata.gz: ab88c79367db640e4144c0844a2ffcc0168f353de01ad7c225902c756d350f7a48188d9a3c0890ec6da2a61dc83e6a2484e519ccb082bb171d4fa795f89a4e7f
7
+ data.tar.gz: da6ca243ceb13b6d353ce15c6b59e725d617c0712e146a13017f2bbb2d7715dad0705849c4ce36b1b941b0dcb1a2a1750390d557b80c3e7a196809a66a5d4efa
data/README.md CHANGED
@@ -15,7 +15,10 @@ Embed [V8](https://v8.dev/) in Ruby, built on [rusty_v8](https://crates.io/crate
15
15
  not just classic scripts.
16
16
  - **Faithful value marshalling**: `BigInt`, `Date`, `Map`, `Set`, typed binary
17
17
  (`Uint8Array`/`ArrayBuffer` ↔ binary `String`), and shared/cyclic object
18
- graphs all round-trip — no lossy JSON hop.
18
+ graphs all round-trip — no lossy JSON hop. A JS `Map` surfaces in Ruby as a
19
+ `RustyRacer::JSMap` (a `Hash` subclass — it reads like a `Hash` but keeps its
20
+ non-string keys) and marshals back to a JS `Map`; a plain `Hash` still maps to a
21
+ JS object.
19
22
  - **In-thread execution** — V8 runs on the calling Ruby thread, with no dedicated
20
23
  V8 thread and no per-op thread hop; fast when you run many small ops.
21
24
  - **Drop-in [ExecJS](#execjs) runtime** — any ExecJS consumer switches with no
@@ -58,7 +61,7 @@ ctx.eval("1 + 1") # => 2
58
61
  ctx.eval("({a: 1, b: [true, 'x']})") # => {"a"=>1, "b"=>[true, "x"]}
59
62
 
60
63
  # Call a JS function with marshalled args (BigInt/Date/Map/Set/shared refs
61
- # all round-trip faithfully).
64
+ # all round-trip faithfully; a JS Map surfaces as a RustyRacer::JSMap).
62
65
  ctx.eval("function add(a, b) { return a + b }")
63
66
  ctx.call("add", 20, 22) # => 42
64
67
  ctx.call_void("doSideEffect") # runs it; never marshals the return
@@ -4,7 +4,7 @@
4
4
  # libv8-rusty needed under the cibuildgem native-per-platform model).
5
5
  [package]
6
6
  name = "rusty_racer"
7
- version = "0.1.7"
7
+ version = "0.1.8"
8
8
  edition = "2021"
9
9
  publish = false
10
10
 
@@ -3019,6 +3019,13 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
3019
3019
  jsmodule.define_method("dispose", method!(JsModule::dispose, 0))?;
3020
3020
  jsmodule.define_method("disposed?", method!(JsModule::disposed, 0))?;
3021
3021
 
3022
+ // A JS Map marshals to this Hash subclass (not a plain Hash) so the reverse
3023
+ // direction can tell it apart from a JS Object — also a Hash — and rebuild a
3024
+ // JS Map instead of a plain object. It behaves exactly like a Hash (so
3025
+ // `assert_kind_of Hash` / `h[k]` still work); users may also construct one to
3026
+ // hand a JS Map to JS. See marshal.rs.
3027
+ module.define_class("JSMap", ruby.class_object().const_get::<_, magnus::RClass>("Hash")?)?;
3028
+
3022
3029
  let platform = module.define_module("Platform")?;
3023
3030
  platform.define_singleton_method("set_flags!", function!(platform_set_flags, -1))?;
3024
3031
 
@@ -48,8 +48,11 @@ pub(crate) enum JsVal {
48
48
  Array { id: u32, items: Vec<JsVal> },
49
49
  // JS object / Ruby Hash with string keys. Insertion order preserved.
50
50
  Obj { id: u32, entries: Vec<(String, JsVal)> },
51
- // JS Map <-> Ruby Hash. Keys are arbitrary values (not just strings), so
52
- // this is distinct from Obj. Insertion order preserved.
51
+ // JS Map <-> Ruby RustyRacer::JSMap (a Hash subclass). Keys are arbitrary
52
+ // values (not just strings), so this is distinct from Obj. Insertion order
53
+ // preserved. The JSMap subclass is what lets a Map round-trip: a plain Ruby
54
+ // Hash marshals to Obj (a JS Object), a JSMap to Map — the return leg tells
55
+ // them apart by class (Ruby has no native Map type).
53
56
  Map { id: u32, pairs: Vec<(JsVal, JsVal)> },
54
57
  // JS Set <-> Ruby Set (stdlib).
55
58
  Set { id: u32, items: Vec<JsVal> },
@@ -431,6 +434,15 @@ pub(crate) fn jsval_to_ruby(ruby: &Ruby, val: &JsVal) -> Result<Value, Error> {
431
434
  // reference. This invariant is load-bearing: do NOT refactor an arm to stash a
432
435
  // value in `built` without keeping it rooted by a live local until it's grafted.
433
436
 
437
+ // The RustyRacer::JSMap class (a Hash subclass), defined in init. A JS Map
438
+ // marshals to an instance of it so the reverse direction can distinguish it from
439
+ // a plain Hash (a JS Object) and rebuild a JS Map. Errors only if init didn't run.
440
+ fn js_map_class(ruby: &Ruby) -> Result<magnus::RClass, Error> {
441
+ ruby.class_object()
442
+ .const_get::<_, magnus::RModule>("RustyRacer")?
443
+ .const_get::<_, magnus::RClass>("JSMap")
444
+ }
445
+
434
446
  fn jsval_to_ruby_d(
435
447
  ruby: &Ruby,
436
448
  val: &JsVal,
@@ -494,16 +506,27 @@ fn jsval_to_ruby_d(
494
506
  }
495
507
  h.as_value()
496
508
  }
497
- // JS Map -> Ruby Hash (arbitrary marshalled keys, not just strings).
509
+ // JS Map -> RustyRacer::JSMap, a Hash subclass: arbitrary marshalled keys
510
+ // (not just strings) AND distinguishable from a JS Object (a plain Hash),
511
+ // so ruby_to_jsval can rebuild a JS Map rather than a plain object. Build
512
+ // empty then aset so a cyclic Map (a value referring back to the Map)
513
+ // resolves through the Ref table.
514
+ //
515
+ // LIMITATION: keys collapse by Ruby Hash equality (eql?/hash), but a JS
516
+ // Map keys by identity (SameValueZero). Primitive keys (string/number/
517
+ // bool/bigint) round-trip exactly; two distinct-but-structurally-equal
518
+ // OBJECT keys (e.g. `{}` and `{}`) become ONE entry — inherent to a Hash
519
+ // representation, and object identity can't survive copy-marshalling anyway.
498
520
  JsVal::Map { id, pairs } => {
499
- let h = ruby.hash_new();
500
- built.insert(*id, h.as_value());
521
+ let map_obj: Value = js_map_class(ruby)?.funcall("new", ())?;
522
+ built.insert(*id, map_obj);
523
+ let h = RHash::from_value(map_obj).expect("JSMap is a Hash");
501
524
  for (k, v) in pairs {
502
525
  let kk = jsval_to_ruby_d(ruby, k, built)?;
503
526
  let vv = jsval_to_ruby_d(ruby, v, built)?;
504
527
  let _ = h.aset(kk, vv);
505
528
  }
506
- h.as_value()
529
+ map_obj
507
530
  }
508
531
  // JS Set -> Ruby Set (stdlib); build empty then add so a cyclic Set
509
532
  // (a Set containing itself) resolves through the Ref table.
@@ -726,6 +749,31 @@ fn ruby_to_jsval_d(val: Value, seen: &mut RbSeen, depth: u32) -> Result<JsVal, E
726
749
  }
727
750
  return Ok(JsVal::Array { id, items });
728
751
  }
752
+ // RustyRacer::JSMap (a Hash subclass) -> JS Map, preserving arbitrary keys as
753
+ // real JS values (NOT string-coerced like a plain Hash -> JS Object). Must come
754
+ // BEFORE the Hash branch, since a JSMap IS-A Hash. This is what makes a JS Map
755
+ // round-trip (JS Map -> JSMap -> JS Map) instead of degrading to an object.
756
+ if let Ok(js_map) = js_map_class(&ruby) {
757
+ if val.is_kind_of(js_map) {
758
+ let id = match rb_container_id(seen, val, depth)? {
759
+ RbId::New(id) => id,
760
+ RbId::Reuse(jv) => return Ok(jv),
761
+ };
762
+ let hash = RHash::from_value(val).expect("JSMap is a Hash");
763
+ let pairs = RefCell::new(Vec::new());
764
+ hash.foreach(|k: Value, v: Value| {
765
+ pairs.borrow_mut().push((
766
+ ruby_to_jsval_d(k, seen, depth + 1)?,
767
+ ruby_to_jsval_d(v, seen, depth + 1)?,
768
+ ));
769
+ Ok(magnus::r_hash::ForEach::Continue)
770
+ })?;
771
+ return Ok(JsVal::Map {
772
+ id,
773
+ pairs: pairs.into_inner(),
774
+ });
775
+ }
776
+ }
729
777
  if let Ok(hash) = RHash::try_convert(val) {
730
778
  let id = match rb_container_id(seen, val, depth)? {
731
779
  RbId::New(id) => id,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RustyRacer
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.8"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rusty_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keita Urashima
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-14 00:00:00.000000000 Z
11
+ date: 2026-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys
@@ -54,7 +54,7 @@ metadata:
54
54
  source_code_uri: https://github.com/ursm/rusty_racer
55
55
  bug_tracker_uri: https://github.com/ursm/rusty_racer/issues
56
56
  rubygems_mfa_required: 'true'
57
- post_install_message:
57
+ post_install_message:
58
58
  rdoc_options: []
59
59
  require_paths:
60
60
  - lib
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
70
  version: '0'
71
71
  requirements: []
72
72
  rubygems_version: 3.5.22
73
- signing_key:
73
+ signing_key:
74
74
  specification_version: 4
75
75
  summary: Embed V8 in Ruby via rusty_v8 + Magnus (rb-sys)
76
76
  test_files: []