@tishlang/tish 1.7.0 → 1.9.0

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 (99) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/transform/expr.rs +28 -8
  5. package/crates/js_to_tish/src/transform/stmt.rs +49 -22
  6. package/crates/tish/Cargo.toml +14 -5
  7. package/crates/tish/src/cargo_native_registry.rs +29 -0
  8. package/crates/tish/src/cli_help.rs +16 -10
  9. package/crates/tish/src/main.rs +87 -32
  10. package/crates/tish/src/repl_completion.rs +3 -3
  11. package/crates/tish/tests/cargo_example_compile.rs +1 -1
  12. package/crates/tish/tests/integration_test.rs +19 -7
  13. package/crates/tish/tests/shortcircuit.rs +1 -1
  14. package/crates/tish_ast/src/ast.rs +80 -9
  15. package/crates/tish_build_utils/Cargo.toml +4 -0
  16. package/crates/tish_build_utils/src/lib.rs +105 -2
  17. package/crates/tish_builtins/Cargo.toml +5 -1
  18. package/crates/tish_builtins/src/array.rs +13 -12
  19. package/crates/tish_builtins/src/construct.rs +34 -33
  20. package/crates/tish_builtins/src/globals.rs +12 -11
  21. package/crates/tish_builtins/src/helpers.rs +2 -1
  22. package/crates/tish_builtins/src/object.rs +3 -2
  23. package/crates/tish_builtins/src/string.rs +73 -3
  24. package/crates/tish_bytecode/src/compiler.rs +12 -14
  25. package/crates/tish_bytecode/src/opcode.rs +12 -3
  26. package/crates/tish_compile/Cargo.toml +1 -0
  27. package/crates/tish_compile/src/codegen.rs +745 -199
  28. package/crates/tish_compile/src/infer.rs +6 -0
  29. package/crates/tish_compile/src/lib.rs +4 -3
  30. package/crates/tish_compile/src/resolve.rs +180 -82
  31. package/crates/tish_compile/src/types.rs +175 -11
  32. package/crates/tish_compile_js/Cargo.toml +1 -0
  33. package/crates/tish_compile_js/src/codegen.rs +152 -29
  34. package/crates/tish_compile_js/src/lib.rs +3 -1
  35. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
  36. package/crates/tish_core/Cargo.toml +8 -0
  37. package/crates/tish_core/src/json.rs +102 -53
  38. package/crates/tish_core/src/lib.rs +3 -1
  39. package/crates/tish_core/src/macros.rs +5 -5
  40. package/crates/tish_core/src/value.rs +53 -15
  41. package/crates/tish_core/src/vmref.rs +178 -0
  42. package/crates/tish_eval/Cargo.toml +17 -2
  43. package/crates/tish_eval/src/eval.rs +90 -28
  44. package/crates/tish_eval/src/http.rs +61 -0
  45. package/crates/tish_eval/src/lib.rs +3 -3
  46. package/crates/tish_eval/src/natives.rs +41 -0
  47. package/crates/tish_eval/src/value.rs +7 -3
  48. package/crates/tish_eval/src/value_convert.rs +13 -5
  49. package/crates/tish_fmt/src/lib.rs +120 -30
  50. package/crates/tish_lexer/src/lib.rs +20 -5
  51. package/crates/tish_lexer/src/token.rs +4 -0
  52. package/crates/tish_llvm/src/lib.rs +3 -1
  53. package/crates/tish_lsp/Cargo.toml +4 -1
  54. package/crates/tish_lsp/README.md +1 -1
  55. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  56. package/crates/tish_lsp/src/import_goto.rs +549 -0
  57. package/crates/tish_lsp/src/main.rs +502 -102
  58. package/crates/tish_native/src/build.rs +3 -2
  59. package/crates/tish_native/src/lib.rs +6 -2
  60. package/crates/tish_opt/src/lib.rs +17 -2
  61. package/crates/tish_parser/src/lib.rs +10 -3
  62. package/crates/tish_parser/src/parser.rs +346 -56
  63. package/crates/tish_pg/Cargo.toml +34 -0
  64. package/crates/tish_pg/README.md +38 -0
  65. package/crates/tish_pg/src/error.rs +52 -0
  66. package/crates/tish_pg/src/lib.rs +967 -0
  67. package/crates/tish_resolve/Cargo.toml +13 -0
  68. package/crates/tish_resolve/src/lib.rs +3436 -0
  69. package/crates/tish_resolve/src/pos.rs +133 -0
  70. package/crates/tish_runtime/Cargo.toml +68 -3
  71. package/crates/tish_runtime/src/http.rs +1123 -141
  72. package/crates/tish_runtime/src/http_fetch.rs +15 -14
  73. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  74. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  75. package/crates/tish_runtime/src/lib.rs +159 -29
  76. package/crates/tish_runtime/src/promise.rs +199 -36
  77. package/crates/tish_runtime/src/promise_io.rs +2 -1
  78. package/crates/tish_runtime/src/timers.rs +37 -1
  79. package/crates/tish_runtime/src/ws.rs +26 -28
  80. package/crates/tish_ui/src/jsx.rs +279 -8
  81. package/crates/tish_ui/src/lib.rs +5 -2
  82. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  83. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  84. package/crates/tish_vm/Cargo.toml +15 -5
  85. package/crates/tish_vm/src/vm.rs +506 -259
  86. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
  87. package/crates/tish_wasm/src/lib.rs +17 -14
  88. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  89. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  90. package/crates/tishlang_cargo_bindgen/Cargo.toml +1 -0
  91. package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
  92. package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
  93. package/justfile +8 -0
  94. package/package.json +1 -1
  95. package/platform/darwin-arm64/tish +0 -0
  96. package/platform/darwin-x64/tish +0 -0
  97. package/platform/linux-arm64/tish +0 -0
  98. package/platform/linux-x64/tish +0 -0
  99. package/platform/win32-x64/tish.exe +0 -0
@@ -1,72 +1,235 @@
1
- //! Promise static methods for compiled Tish (resolve, reject, all, race).
1
+ //! Promise helpers for the bytecode VM and native codegen (`Promise.resolve`, etc.).
2
+ //!
3
+ //! The global `Promise` value is an **object** with a `__call` entry so the VM can
4
+ //! invoke `Promise(executor)` like `new Promise(executor)` in JS. Static methods live
5
+ //! on the same object (`resolve`, `reject`, `all`, `race`).
2
6
 
3
- use std::cell::RefCell;
4
- use std::rc::Rc;
5
- use std::sync::Arc;
6
- use tishlang_core::{ObjectMap, Value};
7
+ use std::sync::mpsc;
8
+ use std::sync::{Arc, Mutex};
7
9
 
8
- /// Promise.resolve(value) - returns the value (immediate resolve).
10
+ use tishlang_core::{ObjectMap, TishPromise, Value, VmRef};
11
+
12
+ /// Fulfilled or rejected before anyone awaits — `block_until_settled` consumes the result once.
13
+ pub struct ImmediateSettledPromise {
14
+ slot: Mutex<Option<Result<Value, Value>>>,
15
+ }
16
+
17
+ impl ImmediateSettledPromise {
18
+ fn new(result: Result<Value, Value>) -> Self {
19
+ Self {
20
+ slot: Mutex::new(Some(result)),
21
+ }
22
+ }
23
+ }
24
+
25
+ impl TishPromise for ImmediateSettledPromise {
26
+ fn block_until_settled(&self) -> std::result::Result<Value, Value> {
27
+ self.slot
28
+ .lock()
29
+ .unwrap()
30
+ .take()
31
+ .unwrap_or(Err(Value::String(
32
+ "Promise already settled or consumed".into(),
33
+ )))
34
+ }
35
+ }
36
+
37
+ fn fulfilled(v: Value) -> Value {
38
+ Value::Promise(Arc::new(ImmediateSettledPromise::new(Ok(v))))
39
+ }
40
+
41
+ fn rejected(v: Value) -> Value {
42
+ Value::Promise(Arc::new(ImmediateSettledPromise::new(Err(v))))
43
+ }
44
+
45
+ fn flatten_chain_out(v: Value) -> std::result::Result<Value, Value> {
46
+ match v {
47
+ Value::Promise(p) => p.block_until_settled(),
48
+ other => Ok(other),
49
+ }
50
+ }
51
+
52
+ /// `Promise(executor)` — executor runs synchronously; `resolve` / `reject` unblock `recv`.
53
+ struct DeferredChannelPromise {
54
+ rx: Mutex<Option<mpsc::Receiver<Result<Value, Value>>>>,
55
+ }
56
+
57
+ impl TishPromise for DeferredChannelPromise {
58
+ fn block_until_settled(&self) -> std::result::Result<Value, Value> {
59
+ let rx = self.rx.lock().unwrap().take();
60
+ match rx {
61
+ Some(r) => r.recv().unwrap_or(Err(Value::String(
62
+ "Promise executor did not call resolve or reject".into(),
63
+ ))),
64
+ None => Err(Value::String(
65
+ "Promise already consumed or settled".into(),
66
+ )),
67
+ }
68
+ }
69
+ }
70
+
71
+ /// `.then` / `.catch` chain: when awaited, settle the predecessor then optionally invoke a handler.
72
+ pub struct ThenPromise {
73
+ pred: Arc<dyn TishPromise>,
74
+ on_fulfilled: Option<Value>,
75
+ on_rejected: Option<Value>,
76
+ }
77
+
78
+ impl TishPromise for ThenPromise {
79
+ fn block_until_settled(&self) -> std::result::Result<Value, Value> {
80
+ match self.pred.block_until_settled() {
81
+ Ok(v) => {
82
+ if let Some(Value::Function(f)) = &self.on_fulfilled {
83
+ flatten_chain_out(f(&[v]))
84
+ } else {
85
+ Ok(v)
86
+ }
87
+ }
88
+ Err(e) => {
89
+ if let Some(Value::Function(f)) = &self.on_rejected {
90
+ flatten_chain_out(f(&[e]))
91
+ } else {
92
+ Err(e)
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ /// `Promise.resolve(value)` — adopt promises, otherwise wrap in a fulfilled promise.
9
100
  pub fn promise_resolve(args: &[Value]) -> Value {
10
- args.first().cloned().unwrap_or(Value::Null)
101
+ match args.first() {
102
+ Some(Value::Promise(p)) => Value::Promise(Arc::clone(p)),
103
+ Some(v) => fulfilled(v.clone()),
104
+ None => fulfilled(Value::Null),
105
+ }
11
106
  }
12
107
 
13
- /// Promise.reject(value) - returns value as "rejected" placeholder.
14
- /// Note: await on this in native compile may not throw; use try/catch in interpreter for full support.
108
+ /// `Promise.reject(reason)` always a rejected promise.
15
109
  pub fn promise_reject(args: &[Value]) -> Value {
16
- args.first().cloned().unwrap_or(Value::Null)
110
+ rejected(
111
+ args.first()
112
+ .cloned()
113
+ .unwrap_or(Value::Null),
114
+ )
17
115
  }
18
116
 
19
- /// Promise.all(iterable) - awaits each Promise in the array, returns array of resolved values.
117
+ /// `Promise.all(iterable)` block on each promise in order; non-promises pass through.
20
118
  pub fn promise_all(args: &[Value]) -> Value {
21
119
  match args.first() {
22
120
  Some(Value::Array(arr)) => {
23
- let arr = arr.borrow();
24
- let resolved: Vec<Value> = arr
25
- .iter()
26
- .map(|v| {
27
- if let Value::Promise(p) = v {
28
- match tishlang_core::TishPromise::block_until_settled(p.as_ref()) {
29
- Ok(val) => val,
30
- Err(rejection) => rejection,
31
- }
32
- } else {
33
- v.clone()
121
+ let mut out: Vec<Value> = Vec::new();
122
+ for v in arr.borrow().iter() {
123
+ let item = if let Value::Promise(p) = v {
124
+ match p.block_until_settled() {
125
+ Ok(x) => x,
126
+ Err(rej) => return rejected(rej),
34
127
  }
35
- })
36
- .collect();
37
- Value::Array(Rc::new(RefCell::new(resolved)))
128
+ } else {
129
+ v.clone()
130
+ };
131
+ out.push(item);
132
+ }
133
+ fulfilled(Value::Array(VmRef::new(out)))
38
134
  }
39
- Some(v) => v.clone(),
40
- None => Value::Null,
135
+ Some(v) => fulfilled(v.clone()),
136
+ None => fulfilled(Value::Null),
41
137
  }
42
138
  }
43
139
 
44
- /// Promise.race(iterable) - returns first element of array.
140
+ /// `Promise.race(iterable)` first element wins (blocking first promise if it is one).
45
141
  pub fn promise_race(args: &[Value]) -> Value {
46
142
  match args.first() {
47
- Some(Value::Array(arr)) => arr.borrow().first().cloned().unwrap_or(Value::Null),
48
- _ => Value::Null,
143
+ Some(Value::Array(arr)) => {
144
+ let borrowed = arr.borrow();
145
+ for v in borrowed.iter() {
146
+ if let Value::Promise(p) = v {
147
+ return match p.block_until_settled() {
148
+ Ok(x) => fulfilled(x),
149
+ Err(e) => rejected(e),
150
+ };
151
+ }
152
+ return fulfilled(v.clone());
153
+ }
154
+ Value::Null
155
+ }
156
+ Some(v) => fulfilled(v.clone()),
157
+ None => Value::Null,
49
158
  }
50
159
  }
51
160
 
52
- /// Build the Promise object with resolve, reject, all, race static methods.
161
+ /// Build the global `Promise` object: `__call` (constructor) + static methods.
53
162
  pub fn promise_object() -> Value {
54
163
  let mut map: ObjectMap = ObjectMap::default();
164
+
165
+ let ctor = Value::native(|args: &[Value]| match args.first() {
166
+ Some(Value::Function(f)) => {
167
+ let (tx, rx) = mpsc::channel();
168
+ let tx_cell = Arc::new(Mutex::new(Some(tx)));
169
+ let resolve = Value::native({
170
+ let tx_cell = Arc::clone(&tx_cell);
171
+ move |a: &[Value]| {
172
+ if let Some(t) = tx_cell.lock().unwrap().take() {
173
+ let _ = t.send(Ok(
174
+ a.first().cloned().unwrap_or(Value::Null),
175
+ ));
176
+ }
177
+ Value::Null
178
+ }
179
+ });
180
+ let reject = Value::native({
181
+ let tx_cell = Arc::clone(&tx_cell);
182
+ move |a: &[Value]| {
183
+ if let Some(t) = tx_cell.lock().unwrap().take() {
184
+ let _ = t.send(Err(
185
+ a.first().cloned().unwrap_or(Value::Null),
186
+ ));
187
+ }
188
+ Value::Null
189
+ }
190
+ });
191
+ let _ = f(&[resolve, reject]);
192
+ Value::Promise(Arc::new(DeferredChannelPromise {
193
+ rx: Mutex::new(Some(rx)),
194
+ }))
195
+ }
196
+ _ => Value::Null,
197
+ });
198
+
199
+ map.insert(Arc::from("__call"), ctor);
55
200
  map.insert(
56
201
  Arc::from("resolve"),
57
- Value::Function(Rc::new(|args: &[Value]| promise_resolve(args))),
202
+ Value::native(|args: &[Value]| promise_resolve(args)),
58
203
  );
59
204
  map.insert(
60
205
  Arc::from("reject"),
61
- Value::Function(Rc::new(|args: &[Value]| promise_reject(args))),
206
+ Value::native(|args: &[Value]| promise_reject(args)),
62
207
  );
63
208
  map.insert(
64
209
  Arc::from("all"),
65
- Value::Function(Rc::new(|args: &[Value]| promise_all(args))),
210
+ Value::native(|args: &[Value]| promise_all(args)),
66
211
  );
67
212
  map.insert(
68
213
  Arc::from("race"),
69
- Value::Function(Rc::new(|args: &[Value]| promise_race(args))),
214
+ Value::native(|args: &[Value]| promise_race(args)),
70
215
  );
71
- Value::Object(Rc::new(RefCell::new(map)))
216
+ Value::Object(VmRef::new(map))
217
+ }
218
+
219
+ /// `.then(onFulfilled, onRejected)` for a `Value::Promise` instance (VM `GetMember`).
220
+ pub fn promise_instance_then(p: &Arc<dyn TishPromise>, args: &[Value]) -> Value {
221
+ Value::Promise(Arc::new(ThenPromise {
222
+ pred: Arc::clone(p),
223
+ on_fulfilled: args.first().cloned(),
224
+ on_rejected: args.get(1).cloned(),
225
+ }))
226
+ }
227
+
228
+ /// `.catch(onRejected)` for a `Value::Promise` instance.
229
+ pub fn promise_instance_catch(p: &Arc<dyn TishPromise>, args: &[Value]) -> Value {
230
+ Value::Promise(Arc::new(ThenPromise {
231
+ pred: Arc::clone(p),
232
+ on_fulfilled: None,
233
+ on_rejected: args.first().cloned(),
234
+ }))
72
235
  }
@@ -1,6 +1,7 @@
1
1
  //! Promises carrying only Send payloads (string results for text(), etc.).
2
2
 
3
3
  use std::cell::RefCell;
4
+ use tishlang_core::VmRef;
4
5
  use std::rc::Rc;
5
6
  use std::sync::{Arc, Mutex};
6
7
  use tishlang_core::{ObjectMap, TishPromise, Value};
@@ -10,7 +11,7 @@ fn error_value(msg: String) -> Value {
10
11
  let mut obj: ObjectMap = ObjectMap::with_capacity(2);
11
12
  obj.insert(Arc::from("error"), Value::String(msg.into()));
12
13
  obj.insert(Arc::from("ok"), Value::Bool(false));
13
- Value::Object(Rc::new(RefCell::new(obj)))
14
+ Value::Object(VmRef::new(obj))
14
15
  }
15
16
 
16
17
  pub struct StringResultPromise {
@@ -1,4 +1,4 @@
1
- //! setTimeout, clearTimeout for compiled Tish and VM.
1
+ //! setTimeout, setInterval, clearTimeout, clearInterval for compiled Tish and VM.
2
2
  //! Callbacks run when blocking ops (e.g. ws.receiveTimeout) yield in their poll loop.
3
3
 
4
4
  use std::cell::RefCell;
@@ -35,11 +35,18 @@ fn extract_num(v: Option<&Value>) -> u64 {
35
35
 
36
36
  /// Sleep for ms, running due timers before sleeping. Use this instead of thread::sleep
37
37
  /// in blocking loops so setTimeout callbacks can fire.
38
+ #[allow(dead_code)] // Used by embedders with blocking poll loops; AppKit uses [`drain_timers`] instead.
38
39
  pub fn sleep_with_drain(ms: u64) {
39
40
  run_due_timers();
40
41
  std::thread::sleep(Duration::from_millis(ms));
41
42
  }
42
43
 
44
+ /// Run all due timer callbacks (e.g. from an AppKit / GUI event pump).
45
+ #[inline]
46
+ pub fn drain_timers() {
47
+ run_due_timers();
48
+ }
49
+
43
50
  /// Run all due timer callbacks.
44
51
  fn run_due_timers() {
45
52
  let due = take_due_timers();
@@ -109,6 +116,30 @@ pub fn set_timeout(args: &[Value]) -> Value {
109
116
  Value::Number(id as f64)
110
117
  }
111
118
 
119
+ /// setInterval(callback, intervalMs, ...args) — first run after `intervalMs`, then repeats.
120
+ pub fn set_interval(args: &[Value]) -> Value {
121
+ let callback = args.first().cloned().unwrap_or(Value::Null);
122
+ let interval_ms = extract_num(args.get(1)).min(3600_000);
123
+ let extra_args: Vec<Value> = args.iter().skip(2).cloned().collect();
124
+ if matches!(callback, Value::Null) {
125
+ return Value::Number(next_id() as f64);
126
+ }
127
+ let id = next_id();
128
+ let due = Instant::now() + Duration::from_millis(interval_ms);
129
+ REGISTRY.with(|r| {
130
+ r.borrow_mut().insert(
131
+ id,
132
+ TimerEntry {
133
+ due,
134
+ callback,
135
+ args: extra_args,
136
+ interval_ms,
137
+ },
138
+ );
139
+ });
140
+ Value::Number(id as f64)
141
+ }
142
+
112
143
  /// clearTimeout(id) - removes timer.
113
144
  pub fn clear_timeout(args: &[Value]) -> Value {
114
145
  let id = args
@@ -123,3 +154,8 @@ pub fn clear_timeout(args: &[Value]) -> Value {
123
154
  });
124
155
  Value::Null
125
156
  }
157
+
158
+ /// clearInterval(id) — same registry as clearTimeout.
159
+ pub fn clear_interval(args: &[Value]) -> Value {
160
+ clear_timeout(args)
161
+ }
@@ -6,6 +6,7 @@
6
6
  //! - **Broadcast** (Node pattern): `server.clients.forEach(ws => ws.send(data))` or iterate room conns and `wsSend(ws, data)` (same as `ws.send(data)`)
7
7
 
8
8
  use std::cell::RefCell;
9
+ use tishlang_core::VmRef;
9
10
  use std::collections::HashMap;
10
11
  use std::rc::Rc;
11
12
  use std::sync::atomic::{AtomicU32, Ordering};
@@ -199,36 +200,36 @@ fn conn_object(id: u32) -> Value {
199
200
  obj.insert(Arc::from("readyState"), Value::Number(1.0)); // OPEN
200
201
  obj.insert(
201
202
  Arc::from("send"),
202
- Value::Function(Rc::new(move |args: &[Value]| {
203
+ Value::native(move |args: &[Value]| {
203
204
  let data = args
204
205
  .first()
205
206
  .map(|v| v.to_display_string())
206
207
  .unwrap_or_default();
207
208
  Value::Bool(conn_send(id, data))
208
- })),
209
+ }),
209
210
  );
210
211
  obj.insert(
211
212
  Arc::from("close"),
212
- Value::Function(Rc::new(move |_args: &[Value]| {
213
+ Value::native(move |_args: &[Value]| {
213
214
  unregister(id);
214
215
  Value::Null
215
- })),
216
+ }),
216
217
  );
217
218
  obj.insert(
218
219
  Arc::from("receive"),
219
- Value::Function(Rc::new(move |_args: &[Value]| match conn_receive(id) {
220
+ Value::native(move |_args: &[Value]| match conn_receive(id) {
220
221
  Some(s) => {
221
222
  let mut ev: ObjectMap = ObjectMap::default();
222
223
  ev.insert(Arc::from("data"), Value::String(s.into()));
223
- Value::Object(Rc::new(RefCell::new(ev)))
224
+ Value::Object(VmRef::new(ev))
224
225
  }
225
226
  None => Value::Null,
226
- })),
227
+ }),
227
228
  );
228
229
  let id_timeout = id;
229
230
  obj.insert(
230
231
  Arc::from("receiveTimeout"),
231
- Value::Function(Rc::new(move |args: &[Value]| {
232
+ Value::native(move |args: &[Value]| {
232
233
  let timeout_ms = args
233
234
  .first()
234
235
  .and_then(|v| match v {
@@ -242,13 +243,13 @@ fn conn_object(id: u32) -> Value {
242
243
  Some(s) => {
243
244
  let mut ev: ObjectMap = ObjectMap::default();
244
245
  ev.insert(Arc::from("data"), Value::String(s.into()));
245
- Value::Object(Rc::new(RefCell::new(ev)))
246
+ Value::Object(VmRef::new(ev))
246
247
  }
247
248
  None => Value::Null,
248
249
  }
249
- })),
250
+ }),
250
251
  );
251
- Value::Object(Rc::new(RefCell::new(obj)))
252
+ Value::Object(VmRef::new(obj))
252
253
  }
253
254
 
254
255
  fn parse_port(args: &[Value]) -> Option<u16> {
@@ -472,9 +473,9 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
472
473
  }
473
474
 
474
475
  // Node.js-compatible: server.clients is array of connected WebSocket instances
475
- let clients: Rc<RefCell<Vec<Value>>> = Rc::new(RefCell::new(Vec::new()));
476
+ let clients: VmRef<Vec<Value>> = VmRef::new(Vec::new());
476
477
 
477
- let on_fn = Rc::new(|args: &[Value]| {
478
+ let on_fn = Value::native(|args: &[Value]| {
478
479
  let Some(Value::Object(so)) = args.first() else {
479
480
  return Value::Null;
480
481
  };
@@ -489,8 +490,8 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
489
490
  Value::Null
490
491
  });
491
492
 
492
- let clients_listen = Rc::clone(&clients);
493
- let listen_fn = Rc::new(move |args: &[Value]| {
493
+ let clients_listen = clients.clone();
494
+ let listen_fn = Value::native(move |args: &[Value]| {
494
495
  let Some(Value::Object(so)) = args.first() else {
495
496
  return Value::Null;
496
497
  };
@@ -519,8 +520,8 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
519
520
  Value::Null
520
521
  });
521
522
 
522
- let clients_accept = Rc::clone(&clients);
523
- let accept_timeout_fn = Rc::new(move |args: &[Value]| {
523
+ let clients_accept = clients.clone();
524
+ let accept_timeout_fn = Value::native(move |args: &[Value]| {
524
525
  let Some(Value::Object(so)) = args.first() else {
525
526
  return Value::Null;
526
527
  };
@@ -541,13 +542,10 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
541
542
  m.insert(Arc::from("_handle"), handle_val);
542
543
  m.insert(Arc::from("_onConnection"), Value::Null);
543
544
  m.insert(Arc::from("clients"), Value::Array(clients));
544
- m.insert(Arc::from("on"), Value::Function(on_fn));
545
- m.insert(Arc::from("listen"), Value::Function(listen_fn));
546
- m.insert(
547
- Arc::from("acceptTimeout"),
548
- Value::Function(accept_timeout_fn),
549
- );
550
- Value::Object(Rc::new(RefCell::new(m)))
545
+ m.insert(Arc::from("on"), on_fn);
546
+ m.insert(Arc::from("listen"), listen_fn);
547
+ m.insert(Arc::from("acceptTimeout"), accept_timeout_fn);
548
+ Value::Object(VmRef::new(m))
551
549
  }
552
550
 
553
551
  #[cfg(test)]
@@ -562,7 +560,7 @@ mod tests {
562
560
  let opts = {
563
561
  let mut m: ObjectMap = ObjectMap::default();
564
562
  m.insert(Arc::from("port"), Value::Number(port as f64));
565
- Value::Object(Rc::new(RefCell::new(m)))
563
+ Value::Object(VmRef::new(m))
566
564
  };
567
565
 
568
566
  let handle = match web_socket_server_listen(std::slice::from_ref(&opts)) {
@@ -647,7 +645,7 @@ mod tests {
647
645
  let opts = {
648
646
  let mut m: ObjectMap = ObjectMap::default();
649
647
  m.insert(Arc::from("port"), Value::Number(port as f64));
650
- Value::Object(Rc::new(RefCell::new(m)))
648
+ Value::Object(VmRef::new(m))
651
649
  };
652
650
 
653
651
  let handle = match web_socket_server_listen(std::slice::from_ref(&opts)) {
@@ -679,8 +677,8 @@ mod tests {
679
677
  if data.contains("\"type\":\"join\"") || data.contains("\"type\": \"join\"") {
680
678
  let joined = r#"{"type":"joined","sessionId":"default"}"#;
681
679
  let presence = r#"{"type":"presence","agentLanes":["ai-a"]}"#;
682
- ws_send_native(&Value::Object(Rc::clone(&wso)), joined);
683
- ws_send_native(&Value::Object(Rc::clone(&wso)), presence);
680
+ ws_send_native(&Value::Object(wso.clone()), joined);
681
+ ws_send_native(&Value::Object(wso.clone()), presence);
684
682
  return;
685
683
  }
686
684
  }