rusty_racer 0.2.5 → 0.2.6

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: 488967c6324cc118d0cf37974c23566725c7c6ce14ba48572330075d1b2dff66
4
- data.tar.gz: 48f562d20d1217e91a9727d1fa348312c51c49b9f82607f3bd2803247e76976c
3
+ metadata.gz: ebbfddd8204b62eed228d53d476247e3b76bf08a894b02fb3e8ffe78836b6b02
4
+ data.tar.gz: 77d0190f0387cbdf240d49ef4ac3b75c1550a18a74de8ceed78a8c97c1956d96
5
5
  SHA512:
6
- metadata.gz: faad4323b59c0eca2d69f66bb9698a41ab5837c0f417710b0f125ca39c0fad22e3d399f8fe730bd2c526a1e9cfe34ad9b8c8be5fdbc47b5c8a6e860eb23308f7
7
- data.tar.gz: 304a7388700465fcfc86a8e7ffeba87c9bd95455075bce889277abe44193f8f407326a136cfe0be39738d53061603d249f153f031866c9570bddec424ac5b807
6
+ metadata.gz: e0a3df4af6404d9357a008e2adcb4d8303a39db35ad9d3d4ef0240217222bd0fd0be40c9b18b36b9778e30e9eac8a5ce9b20514ecc962975cc25b1466d504aac
7
+ data.tar.gz: 497c28687de4bc14c7b666e99a3205406e84384459b923dca24b8d2080db552d3266bbffbfbecfb0a4e61bde9c17aa4413f39e321d1c866339df6944782c3930
data/CHANGELOG.md ADDED
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ ## 0.2.6
4
+
5
+ ### Added
6
+
7
+ - `Context#function(path)` returns a `RustyRacer::Function`: the function a
8
+ dotted path names, resolved once, with the object it was read off kept as its
9
+ `this`. `#call` / `#call_void` invoke it as `Context#call` / `#call_void`
10
+ would — same marshalling, timeout, errors and re-entrancy — without walking
11
+ the path or making a property name per call. It raises `DisposedError` once
12
+ its realm is reset or disposed and never keeps that realm alive; `#dispose`
13
+ releases it early, and garbage collection releases it too.
14
+
15
+ ### Changed
16
+
17
+ - Ruby → JS marshalling no longer runs the page's JavaScript. Arrays and
18
+ objects built for JS have their elements and entries defined
19
+ (`CreateDataProperty`) rather than assigned, so an accessor on
20
+ `Array.prototype` or `Object.prototype` can no longer intercept or swallow
21
+ call arguments, host function results or the host namespace's members. A
22
+ `"__proto__"` key in a Hash now becomes an own property of the object, as
23
+ `JSON.parse` makes it, instead of setting its prototype.
24
+ - Ruby → JS marshalling dispatches on the value's built-in type first: a String,
25
+ Symbol, Array or Hash argument no longer pays for a raised-and-rescued
26
+ conversion probe or a constant lookup (a String argument cost ~1.2 µs more
27
+ than none; now ~0.07 µs). Subclasses, `to_f` (an `ActiveSupport::Duration`),
28
+ `to_str`, `to_ary` and `to_hash` conversions marshal as before — except that
29
+ an Array or Hash subclass is now always an array or an object, where one that
30
+ also defined `to_str` (or a Hash subclass `to_ary`) used to cross as that.
31
+ - A getter or `Proxy` trap that throws while `Context#call` resolves its path
32
+ now raises a `RuntimeError` carrying the JS stack, and the thrown value
33
+ propagates through a host function like any other JS throw.
34
+ - A `Module` or `Script` whose wrapper is garbage-collected without `#dispose`
35
+ is released too (at the end of the isolate's next operation), where 0.2.5 kept
36
+ it until its realm was reset or disposed. `#dispose` itself now only queues
37
+ the release, as `Function#dispose` does, so it works from any thread — in
38
+ 0.2.5 a dispose from another thread marked the wrapper disposed but silently
39
+ kept the module or script for good.
40
+ - A module's url (its `import.meta.url`, its url as a referrer) is kept for as
41
+ long as V8 keeps the module, not as long as its wrapper or its realm: a
42
+ dependency the resolver compiled and Ruby dropped or disposed, or a function
43
+ of a disposed realm's module that another realm still holds, still reads it.
44
+ In 0.2.5 either read `undefined`.
45
+
46
+ ### Documented
47
+
48
+ - What reading a JS value into Ruby runs (getters, `Proxy` traps, holes read
49
+ through the prototype chain, `toString`) and the binary-String ↔ `Uint8Array`
50
+ rule — README, "Marshalling and the page's JavaScript".
51
+
52
+ ### Deliberately unchanged
53
+
54
+ - JS → Ruby still renders a value with no structural form — a function, a
55
+ RegExp, a depth-truncated object — through its ToString, which a page can
56
+ override (`Function.prototype.toString`) to run code or throw during the read.
57
+ V8's side-effect-free renderer (`Value::ToDetailString`, already used for
58
+ Symbols) would run nothing, but it truncates a function's source past 128
59
+ characters and renders objects as `#<Object>`, so switching is a behaviour
60
+ change left for a release that decides it on purpose.
data/README.md CHANGED
@@ -67,6 +67,11 @@ ctx.call("add", 20, 22) # => 42
67
67
  ctx.call_void("doSideEffect") # runs it; never marshals the return
68
68
  ctx.eval_void("globalThis.app = boot()") # ditto for the completion value
69
69
 
70
+ # Resolve a function once and keep it: no path walk or name per call, and its
71
+ # holder stays its `this` (see "Function handles" below).
72
+ add = ctx.function("add")
73
+ add.call(20, 22) # => 42
74
+
70
75
  # Ruby callbacks into JS; a raised Ruby exception becomes a JS exception.
71
76
  ctx.attach("rubyUpcase", ->(s) { s.upcase })
72
77
  ctx.eval("rubyUpcase('hi')") # => "HI"
@@ -96,6 +101,63 @@ make a plain `#eval` fail after every one of its writes has landed.
96
101
  `#eval_primitive` sits between the two: it returns the completion value only
97
102
  where it is a primitive, and nil for an object or a symbol, which it never reads.
98
103
 
104
+ ### Function handles
105
+
106
+ `Context#function(path)` resolves a dotted path (`"app.router.go"`) the way
107
+ `#call` does — each segment an ordinary property read off the context's
108
+ `globalThis` — once, and returns a `RustyRacer::Function` bound to what it found:
109
+ the function, and the object it was read off as its `this`. `#call(*args)` and
110
+ `#call_void(*args)` then invoke it exactly as `Context#call` / `#call_void` would
111
+ — same argument and result marshalling, same timeout, same errors, and they work
112
+ from inside a host function like any other op — without walking the path or
113
+ making a property name per call. Reassigning or deleting the path afterwards
114
+ does not change what the handle calls.
115
+
116
+ A handle lives as long as its realm does. Once its context is `reset` or
117
+ `dispose`d (or the isolate is), `#call` raises `RustyRacer::DisposedError`, and
118
+ the handle no longer keeps that realm alive. (`#disposed?` answers for the handle
119
+ and its isolate only: a realm's reset is found by the next call.) `#dispose` lets
120
+ go of one early, and a handle that is simply garbage-collected is released too,
121
+ at the end of the isolate's next operation. A path that leads into *another*
122
+ realm (`frames[0].fn` in an embedder whose realms see each other) holds that
123
+ realm's function for as long as the handle's own realm lives, as any reference
124
+ kept there would.
125
+
126
+ ### Releasing handles
127
+
128
+ A `Function`, `Script` or `Module` handle is released when it is `#dispose`d —
129
+ from any thread — or garbage-collected, at the end of the isolate's next
130
+ operation, and with its realm's `reset` or `dispose` at the latest. Releasing a
131
+ handle only drops the isolate's hold on what it names: functions a script
132
+ defined keep working, and a module V8 still keeps — linked into a live graph, or
133
+ reached from another realm — keeps its `import.meta.url` and its url as a
134
+ referrer, wrapper or realm or not.
135
+
136
+ ### Marshalling and the page's JavaScript
137
+
138
+ Values cross by copy, not by reference.
139
+
140
+ - **Ruby → JS builds new values without running any JavaScript.** An array's
141
+ elements and an object's entries are *defined* on the new container
142
+ (`CreateDataProperty`), never assigned, so an accessor a script has put on
143
+ `Array.prototype[0]` or `Object.prototype.x` neither sees nor swallows them,
144
+ and a `"__proto__"` key is an own property, as `JSON.parse` makes it. Maps and
145
+ Sets are filled by V8's original builtins, whatever `Map.prototype.set` is now.
146
+ - **JS → Ruby reads values the way a script would.** An object's own enumerable
147
+ string keys are listed and each is read with an ordinary `[[Get]]`, so a getter
148
+ runs; a `Proxy` is read through its `ownKeys`, `getOwnPropertyDescriptor` and
149
+ `get` traps; an array's holes read through its prototype chain, as `arr[i]`
150
+ does; a function, a RegExp or any other value with no structural form becomes
151
+ its `toString()`, which may be the page's. A throw anywhere in that fails the op
152
+ (see above), and `#call_void` / `#eval_void` skip the read entirely. Map and Set
153
+ contents, typed arrays and buffers are read without running JavaScript.
154
+ - **A String's encoding tag is its type.** A binary (`ASCII-8BIT`) String crosses
155
+ as a `Uint8Array`, and a `Uint8Array`, `ArrayBuffer` or `SharedArrayBuffer`
156
+ comes back as a binary String. Any text encoding crosses as a JS string, and
157
+ must hold valid characters — invalid UTF-8 raises `EncodingError` rather than
158
+ turn into U+FFFD — so text read with `File.binread` or off a socket needs its
159
+ real encoding (`force_encoding`) before it crosses as text.
160
+
99
161
  ES modules (the embedder owns the URL→module registry):
100
162
 
101
163
  ```ruby
@@ -5,7 +5,7 @@
5
5
  # links and loads with no custom archive.
6
6
  [package]
7
7
  name = "rusty_racer"
8
- version = "0.2.5"
8
+ version = "0.2.6"
9
9
  edition = "2024"
10
10
  publish = false
11
11
 
@@ -31,3 +31,8 @@ v8 = { version = "150.4.0", features = ["simdutf"] }
31
31
  # pthread stack-bounds query, to set V8's stack limit per op relative to the
32
32
  # current native thread (in-thread V8 runs on the Ruby thread's stack).
33
33
  libc = "0.2"
34
+
35
+ # `cargo test` links libruby: the crate calls into it, though the marshalling tests
36
+ # that run without a VM never do.
37
+ [dev-dependencies]
38
+ rb-sys = { version = "0.9", features = ["link-ruby"] }