@tishlang/tish 1.9.2 → 1.10.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 (78) hide show
  1. package/bin/tish +0 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +8 -6
  3. package/crates/js_to_tish/src/transform/stmt.rs +12 -13
  4. package/crates/tish/Cargo.toml +1 -1
  5. package/crates/tish/src/cargo_native_registry.rs +4 -1
  6. package/crates/tish/src/main.rs +11 -8
  7. package/crates/tish/tests/integration_test.rs +145 -7
  8. package/crates/tish_ast/src/ast.rs +3 -9
  9. package/crates/tish_build_utils/src/lib.rs +43 -15
  10. package/crates/tish_builtins/src/array.rs +2 -3
  11. package/crates/tish_builtins/src/construct.rs +15 -28
  12. package/crates/tish_builtins/src/globals.rs +18 -16
  13. package/crates/tish_builtins/src/helpers.rs +1 -4
  14. package/crates/tish_builtins/src/lib.rs +1 -0
  15. package/crates/tish_builtins/src/object.rs +10 -10
  16. package/crates/tish_builtins/src/string.rs +1 -3
  17. package/crates/tish_builtins/src/symbol.rs +83 -0
  18. package/crates/tish_compile/src/codegen.rs +123 -138
  19. package/crates/tish_compile/src/lib.rs +25 -3
  20. package/crates/tish_compile/src/resolve.rs +6 -3
  21. package/crates/tish_compile/src/types.rs +6 -6
  22. package/crates/tish_compile_js/src/codegen.rs +8 -5
  23. package/crates/tish_core/src/console_style.rs +9 -0
  24. package/crates/tish_core/src/json.rs +17 -7
  25. package/crates/tish_core/src/macros.rs +2 -2
  26. package/crates/tish_core/src/value.rs +192 -4
  27. package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
  28. package/crates/tish_eval/src/eval.rs +135 -73
  29. package/crates/tish_eval/src/http.rs +18 -12
  30. package/crates/tish_eval/src/lib.rs +29 -0
  31. package/crates/tish_eval/src/regex.rs +1 -1
  32. package/crates/tish_eval/src/value.rs +89 -4
  33. package/crates/tish_eval/src/value_convert.rs +30 -8
  34. package/crates/tish_fmt/src/lib.rs +4 -1
  35. package/crates/tish_lexer/src/lib.rs +7 -2
  36. package/crates/tish_llvm/src/lib.rs +2 -2
  37. package/crates/tish_lsp/src/builtin_goto.rs +111 -10
  38. package/crates/tish_lsp/src/import_goto.rs +35 -22
  39. package/crates/tish_lsp/src/main.rs +118 -85
  40. package/crates/tish_native/src/build.rs +187 -10
  41. package/crates/tish_native/src/lib.rs +92 -8
  42. package/crates/tish_parser/src/lib.rs +5 -2
  43. package/crates/tish_parser/src/parser.rs +45 -75
  44. package/crates/tish_pg/src/error.rs +1 -1
  45. package/crates/tish_pg/src/lib.rs +61 -73
  46. package/crates/tish_resolve/src/lib.rs +283 -158
  47. package/crates/tish_resolve/src/pos.rs +10 -2
  48. package/crates/tish_runtime/Cargo.toml +3 -0
  49. package/crates/tish_runtime/src/http.rs +39 -39
  50. package/crates/tish_runtime/src/http_fetch.rs +12 -12
  51. package/crates/tish_runtime/src/lib.rs +26 -43
  52. package/crates/tish_runtime/src/native_promise.rs +0 -11
  53. package/crates/tish_runtime/src/promise.rs +14 -1
  54. package/crates/tish_runtime/src/promise_io.rs +1 -4
  55. package/crates/tish_runtime/src/ws.rs +40 -27
  56. package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
  57. package/crates/tish_ui/src/jsx.rs +6 -4
  58. package/crates/tish_ui/src/lib.rs +2 -2
  59. package/crates/tish_ui/src/runtime/hooks.rs +5 -15
  60. package/crates/tish_ui/src/runtime/mod.rs +16 -17
  61. package/crates/tish_vm/Cargo.toml +2 -0
  62. package/crates/tish_vm/src/vm.rs +218 -153
  63. package/crates/tish_wasm/src/lib.rs +33 -7
  64. package/crates/tish_wasm_runtime/Cargo.toml +4 -1
  65. package/crates/tish_wasm_runtime/src/lib.rs +2 -1
  66. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  67. package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
  68. package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
  69. package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
  70. package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
  71. package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
  72. package/justfile +3 -3
  73. package/package.json +1 -1
  74. package/platform/darwin-arm64/tish +0 -0
  75. package/platform/darwin-x64/tish +0 -0
  76. package/platform/linux-arm64/tish +0 -0
  77. package/platform/linux-x64/tish +0 -0
  78. package/platform/win32-x64/tish.exe +0 -0
@@ -5,10 +5,8 @@
5
5
  //! - **Connection**: `send(data)`, `close()`, `readyState` (1=OPEN), `receive()` / `receiveTimeout(ms)`
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
- use std::cell::RefCell;
9
8
  use tishlang_core::VmRef;
10
9
  use std::collections::HashMap;
11
- use std::rc::Rc;
12
10
  use std::sync::atomic::{AtomicU32, Ordering};
13
11
  use std::sync::mpsc;
14
12
  use std::sync::{Arc, Mutex};
@@ -151,7 +149,7 @@ fn conn_id_from_value(v: &Value) -> Option<u32> {
151
149
  Value::Object(o) => {
152
150
  let b = o.borrow();
153
151
  // Direct conn: { _id, send, ... }
154
- if let Some(idv) = b.get(&Arc::from("_id")) {
152
+ if let Some(idv) = b.strings.get("_id") {
155
153
  if let Value::Number(n) = idv {
156
154
  if n.is_finite() && *n >= 0.0 {
157
155
  return Some(*n as u32);
@@ -159,7 +157,7 @@ fn conn_id_from_value(v: &Value) -> Option<u32> {
159
157
  }
160
158
  }
161
159
  // Wrapper: { ws: conn, ... }
162
- if let Some(ws) = b.get(&Arc::from("ws")) {
160
+ if let Some(ws) = b.strings.get("ws") {
163
161
  return conn_id_from_value(ws);
164
162
  }
165
163
  None
@@ -221,7 +219,7 @@ fn conn_object(id: u32) -> Value {
221
219
  Some(s) => {
222
220
  let mut ev: ObjectMap = ObjectMap::default();
223
221
  ev.insert(Arc::from("data"), Value::String(s.into()));
224
- Value::Object(VmRef::new(ev))
222
+ Value::object(ev)
225
223
  }
226
224
  None => Value::Null,
227
225
  }),
@@ -243,18 +241,18 @@ fn conn_object(id: u32) -> Value {
243
241
  Some(s) => {
244
242
  let mut ev: ObjectMap = ObjectMap::default();
245
243
  ev.insert(Arc::from("data"), Value::String(s.into()));
246
- Value::Object(VmRef::new(ev))
244
+ Value::object(ev)
247
245
  }
248
246
  None => Value::Null,
249
247
  }
250
248
  }),
251
249
  );
252
- Value::Object(VmRef::new(obj))
250
+ Value::object(obj)
253
251
  }
254
252
 
255
253
  fn parse_port(args: &[Value]) -> Option<u16> {
256
254
  args.first().and_then(|v| match v {
257
- Value::Object(o) => o.borrow().get(&Arc::from("port")).and_then(|v| match v {
255
+ Value::Object(o) => o.borrow().strings.get("port").and_then(|v| match v {
258
256
  Value::Number(n) if n.is_finite() && *n >= 0.0 => Some(*n as u16),
259
257
  _ => None,
260
258
  }),
@@ -485,7 +483,9 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
485
483
  .unwrap_or_default();
486
484
  let cb = args.get(2).cloned().unwrap_or(Value::Null);
487
485
  if event == "connection" {
488
- so.borrow_mut().insert(Arc::from("_onConnection"), cb);
486
+ so.borrow_mut()
487
+ .strings
488
+ .insert(Arc::from("_onConnection"), cb);
489
489
  }
490
490
  Value::Null
491
491
  });
@@ -498,14 +498,20 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
498
498
  loop {
499
499
  let handle_n = {
500
500
  let b = so.borrow();
501
- match b.get(&Arc::from("_handle")).cloned().unwrap_or(Value::Null) {
501
+ match b
502
+ .strings
503
+ .get("_handle")
504
+ .cloned()
505
+ .unwrap_or(Value::Null)
506
+ {
502
507
  Value::Number(n) if n.is_finite() && n >= 0.0 => n,
503
508
  _ => break,
504
509
  }
505
510
  };
506
511
  let cb = so
507
512
  .borrow()
508
- .get(&Arc::from("_onConnection"))
513
+ .strings
514
+ .get("_onConnection")
509
515
  .cloned()
510
516
  .unwrap_or(Value::Null);
511
517
  let ws = web_socket_server_accept(&[Value::Number(handle_n)]);
@@ -527,7 +533,8 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
527
533
  };
528
534
  let handle_n = so
529
535
  .borrow()
530
- .get(&Arc::from("_handle"))
536
+ .strings
537
+ .get("_handle")
531
538
  .cloned()
532
539
  .unwrap_or(Value::Null);
533
540
  let timeout_ms = args.get(1).cloned().unwrap_or(Value::Number(100.0));
@@ -545,7 +552,7 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
545
552
  m.insert(Arc::from("on"), on_fn);
546
553
  m.insert(Arc::from("listen"), listen_fn);
547
554
  m.insert(Arc::from("acceptTimeout"), accept_timeout_fn);
548
- Value::Object(VmRef::new(m))
555
+ Value::object(m)
549
556
  }
550
557
 
551
558
  #[cfg(test)]
@@ -560,7 +567,7 @@ mod tests {
560
567
  let opts = {
561
568
  let mut m: ObjectMap = ObjectMap::default();
562
569
  m.insert(Arc::from("port"), Value::Number(port as f64));
563
- Value::Object(VmRef::new(m))
570
+ Value::object(m)
564
571
  };
565
572
 
566
573
  let handle = match web_socket_server_listen(std::slice::from_ref(&opts)) {
@@ -575,20 +582,21 @@ mod tests {
575
582
  };
576
583
  // Echo one message
577
584
  for _ in 0..50 {
578
- let recv_fn = wso.borrow().get(&Arc::from("receive")).cloned();
585
+ let recv_fn = wso.borrow().strings.get("receive").cloned();
579
586
  if let Some(Value::Function(rf)) = recv_fn {
580
587
  let msg = rf(&[]);
581
588
  if !matches!(msg, Value::Null) {
582
589
  let data = match msg {
583
590
  Value::Object(ev) => ev
584
591
  .borrow()
585
- .get(&Arc::from("data"))
592
+ .strings
593
+ .get("data")
586
594
  .map(|v| v.to_display_string())
587
595
  .unwrap_or_default(),
588
596
  _ => String::new(),
589
597
  };
590
598
  if let Some(Value::Function(sf)) =
591
- wso.borrow().get(&Arc::from("send")).cloned()
599
+ wso.borrow().strings.get("send").cloned()
592
600
  {
593
601
  let _ = sf(&[Value::String(data.into())]);
594
602
  }
@@ -607,13 +615,13 @@ mod tests {
607
615
  let Value::Object(co) = client else {
608
616
  panic!("client not object");
609
617
  };
610
- let send = co.borrow().get(&Arc::from("send")).cloned().unwrap();
618
+ let send = co.borrow().strings.get("send").cloned().unwrap();
611
619
  let Value::Function(send_f) = send else {
612
620
  panic!("no send");
613
621
  };
614
622
  let _ = send_f(&[Value::String("hello".into())]);
615
623
 
616
- let recv = co.borrow().get(&Arc::from("receive")).cloned().unwrap();
624
+ let recv = co.borrow().strings.get("receive").cloned().unwrap();
617
625
  let Value::Function(recv_f) = recv else {
618
626
  panic!("no receive");
619
627
  };
@@ -630,7 +638,8 @@ mod tests {
630
638
  };
631
639
  let data = ev
632
640
  .borrow()
633
- .get(&Arc::from("data"))
641
+ .strings
642
+ .get("data")
634
643
  .map(|v| v.to_display_string())
635
644
  .unwrap_or_default();
636
645
  assert_eq!(data, "hello");
@@ -645,7 +654,7 @@ mod tests {
645
654
  let opts = {
646
655
  let mut m: ObjectMap = ObjectMap::default();
647
656
  m.insert(Arc::from("port"), Value::Number(port as f64));
648
- Value::Object(VmRef::new(m))
657
+ Value::object(m)
649
658
  };
650
659
 
651
660
  let handle = match web_socket_server_listen(std::slice::from_ref(&opts)) {
@@ -658,7 +667,7 @@ mod tests {
658
667
  let Value::Object(wso) = ws else {
659
668
  panic!("accept failed");
660
669
  };
661
- let recv_fn = wso.borrow().get(&Arc::from("receive")).cloned();
670
+ let recv_fn = wso.borrow().strings.get("receive").cloned();
662
671
  let Value::Function(rf) = recv_fn.unwrap() else {
663
672
  panic!("no receive");
664
673
  };
@@ -669,7 +678,8 @@ mod tests {
669
678
  let data = match &msg {
670
679
  Value::Object(ev) => ev
671
680
  .borrow()
672
- .get(&Arc::from("data"))
681
+ .strings
682
+ .get("data")
673
683
  .map(|v| v.to_display_string())
674
684
  .unwrap_or_default(),
675
685
  _ => String::new(),
@@ -695,7 +705,7 @@ mod tests {
695
705
  let Value::Object(co) = client else {
696
706
  panic!("client not object");
697
707
  };
698
- let send = co.borrow().get(&Arc::from("send")).cloned().unwrap();
708
+ let send = co.borrow().strings.get("send").cloned().unwrap();
699
709
  let Value::Function(send_f) = send else {
700
710
  panic!("no send");
701
711
  };
@@ -705,7 +715,8 @@ mod tests {
705
715
  // Client uses receiveTimeout like the agent
706
716
  let recv_timeout = co
707
717
  .borrow()
708
- .get(&Arc::from("receiveTimeout"))
718
+ .strings
719
+ .get("receiveTimeout")
709
720
  .cloned()
710
721
  .unwrap();
711
722
  let Value::Function(recv_timeout_f) = recv_timeout else {
@@ -719,7 +730,8 @@ mod tests {
719
730
  };
720
731
  let data1 = ev1
721
732
  .borrow()
722
- .get(&Arc::from("data"))
733
+ .strings
734
+ .get("data")
723
735
  .map(|v| v.to_display_string())
724
736
  .unwrap_or_default();
725
737
  assert!(
@@ -734,7 +746,8 @@ mod tests {
734
746
  };
735
747
  let data2 = ev2
736
748
  .borrow()
737
- .get(&Arc::from("data"))
749
+ .strings
750
+ .get("data")
738
751
  .map(|v| v.to_display_string())
739
752
  .unwrap_or_default();
740
753
  assert!(
@@ -52,11 +52,15 @@ fn fetch_readable_stream_read_chunks() {
52
52
  _ => panic!("expected object response, got {:?}", resp),
53
53
  };
54
54
  assert!(obj
55
+ .strings
55
56
  .get(&std::sync::Arc::from("ok"))
56
57
  .map(|v| matches!(v, Value::Bool(true)))
57
58
  .unwrap_or(false));
58
59
 
59
- let body = obj.get(&std::sync::Arc::from("body")).expect("body");
60
+ let body = obj
61
+ .strings
62
+ .get(&std::sync::Arc::from("body"))
63
+ .expect("body");
60
64
  let stream = match body {
61
65
  Value::Opaque(s) => s.as_ref(),
62
66
  _ => panic!("expected ReadableStream opaque"),
@@ -74,14 +78,12 @@ fn fetch_readable_stream_read_chunks() {
74
78
  let (done, chunk_bytes) = match chunk {
75
79
  Value::Object(o) => {
76
80
  let m = o.borrow();
77
- let done = m
78
- .get(&std::sync::Arc::from("done"))
79
- .and_then(|v| match v {
80
- Value::Bool(b) => Some(*b),
81
- _ => None,
82
- })
83
- .unwrap_or(true);
81
+ let done = match m.strings.get(&std::sync::Arc::from("done")) {
82
+ Some(Value::Bool(b)) => *b,
83
+ _ => true,
84
+ };
84
85
  let bytes = m
86
+ .strings
85
87
  .get(&std::sync::Arc::from("value"))
86
88
  .map(|v| byte_array_to_vec(v))
87
89
  .unwrap_or_default();
@@ -344,7 +344,9 @@ fn collect_fun_decl_names_expr(expr: &Expr, names: &mut HashSet<String>) {
344
344
  | Expr::PrefixInc { .. }
345
345
  | Expr::PostfixDec { .. }
346
346
  | Expr::PrefixDec { .. } => {}
347
- Expr::JsxElement { props, children, .. } => {
347
+ Expr::JsxElement {
348
+ props, children, ..
349
+ } => {
348
350
  for p in props {
349
351
  match p {
350
352
  JsxProp::Attr { value, .. } => {
@@ -466,14 +468,14 @@ where
466
468
  JsxProp::Spread(e) => {
467
469
  let val = emit_expr(e)?;
468
470
  parts.push(format!(
469
- "if let Value::Object(ref _spread) = {} {{ for (k, v) in _spread.borrow().iter() {{ _obj.insert(Arc::clone(k), v.clone()); }} }}",
471
+ "if let Value::Object(ref _spread) = {} {{ for (k, v) in _spread.borrow().strings.iter() {{ _obj.insert(Arc::clone(k), v.clone()); }} }}",
470
472
  val
471
473
  ));
472
474
  }
473
475
  }
474
476
  }
475
477
  Ok(format!(
476
- "{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(VmRef::new(_obj)) }}",
478
+ "{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::object(_obj) }}",
477
479
  parts.join(" ")
478
480
  ))
479
481
  } else {
@@ -495,7 +497,7 @@ where
495
497
  }
496
498
  }
497
499
  Ok(format!(
498
- "Value::Object(VmRef::new(ObjectMap::from([{}])))",
500
+ "Value::object(ObjectMap::from([{}]))",
499
501
  kv.join(", ")
500
502
  ))
501
503
  }
@@ -14,6 +14,6 @@ pub use runtime::{
14
14
  alloc_root_id, current_root_id, drop_host_for_root, fragment_value, install_host_for_root,
15
15
  install_thread_local_host, native_create_root, native_use_effect, native_use_memo,
16
16
  native_use_state, run_with_current_root, ui_h, ui_text, unregister_root,
17
- unregister_root_hooks_and_effects, with_host_for_root, with_thread_local_host, HeadlessHost, Host,
18
- FRAGMENT_SENTINEL, LEGACY_ROOT_ID, RootId,
17
+ unregister_root_hooks_and_effects, with_host_for_root, with_thread_local_host, HeadlessHost,
18
+ Host, RootId, FRAGMENT_SENTINEL, LEGACY_ROOT_ID,
19
19
  };
@@ -189,20 +189,14 @@ fn memo_dep_eq(a: &Value, b: &Value) -> bool {
189
189
  if ab.len() != bb.len() {
190
190
  return false;
191
191
  }
192
- ab.iter()
193
- .zip(bb.iter())
194
- .all(|(x, y)| memo_dep_eq(x, y))
192
+ ab.iter().zip(bb.iter()).all(|(x, y)| memo_dep_eq(x, y))
195
193
  }
196
194
  _ => false,
197
195
  }
198
196
  }
199
197
 
200
198
  fn memo_deps_unchanged(prev: &[Value], next: &[Value]) -> bool {
201
- prev.len() == next.len()
202
- && prev
203
- .iter()
204
- .zip(next.iter())
205
- .all(|(a, b)| memo_dep_eq(a, b))
199
+ prev.len() == next.len() && prev.iter().zip(next.iter()).all(|(a, b)| memo_dep_eq(a, b))
206
200
  }
207
201
 
208
202
  fn root_id_for_hooks() -> RootId {
@@ -338,11 +332,7 @@ fn flush_pending_effects(root_id: RootId) {
338
332
  .map(|st| std::mem::take(&mut *st.pending_effects.borrow_mut()))
339
333
  .unwrap_or_default()
340
334
  });
341
- let cells_rc = HOOKS.with(|h| {
342
- h.borrow()
343
- .get(&root_id)
344
- .map(|st| st.effect_cells.clone())
345
- });
335
+ let cells_rc = HOOKS.with(|h| h.borrow().get(&root_id).map(|st| st.effect_cells.clone()));
346
336
  let Some(cells_rc) = cells_rc else {
347
337
  return;
348
338
  };
@@ -405,10 +395,10 @@ pub fn native_create_root(args: &[Value]) -> Value {
405
395
  drain_flush_queue();
406
396
  Value::Null
407
397
  });
408
- Value::Object(VmRef::new(ObjectMap::from([(
398
+ Value::object(ObjectMap::from([(
409
399
  std::sync::Arc::from("render"),
410
400
  render_fn,
411
- )])))
401
+ )]))
412
402
  }
413
403
 
414
404
  /// Request a re-render (coalesced; safe if called during flush).
@@ -3,19 +3,20 @@
3
3
  mod hooks;
4
4
 
5
5
  use std::cell::RefCell;
6
- use std::rc::Rc;
7
6
  use std::sync::Arc;
8
7
 
9
8
  pub use hooks::{
10
9
  alloc_root_id, current_root_id, drop_host_for_root, install_host_for_root, native_create_root,
11
10
  native_use_effect, native_use_memo, native_use_state, run_with_current_root, schedule_flush,
12
- unregister_root, unregister_root_hooks_and_effects, with_host_for_root, HookState,
13
- LEGACY_ROOT_ID, RootId,
11
+ unregister_root, unregister_root_hooks_and_effects, with_host_for_root, HookState, RootId,
12
+ LEGACY_ROOT_ID,
14
13
  };
15
14
 
16
15
  use tishlang_core::{ObjectMap, Value, VmRef};
17
16
 
18
- /// Sentinel string for `Fragment` (native). JS/Lattish uses `Symbol`; hosts compare via equality.
17
+ /// Sentinel string for `Fragment` (native). Full runtimes may also use `Symbol.for("tish.fragment")`
18
+ /// when the global `Symbol` is present; [`is_fragment_tag`] accepts both that registry symbol and
19
+ /// this string for compatibility.
19
20
  pub const FRAGMENT_SENTINEL: &str = "__tish_ui_Fragment__";
20
21
 
21
22
  /// `Fragment` marker value for `h(Fragment, null, children)`.
@@ -25,7 +26,11 @@ pub fn fragment_value() -> Value {
25
26
 
26
27
  /// Returns true if `tag` refers to [`fragment_value`].
27
28
  pub fn is_fragment_tag(tag: &Value) -> bool {
28
- matches!(tag, Value::String(s) if s.as_ref() == FRAGMENT_SENTINEL)
29
+ match tag {
30
+ Value::String(s) => s.as_ref() == FRAGMENT_SENTINEL,
31
+ Value::Symbol(s) => s.registry_key.as_deref() == Some("tish.fragment"),
32
+ _ => false,
33
+ }
29
34
  }
30
35
 
31
36
  /// `text(s)` helper — returns string as `Value::String` for JSX text nodes.
@@ -49,7 +54,7 @@ pub fn ui_h(args: &[Value]) -> Value {
49
54
  let mut merged = if matches!(props, Value::Null) {
50
55
  ObjectMap::default()
51
56
  } else if let Value::Object(obj) = props {
52
- obj.borrow().clone()
57
+ obj.borrow().strings.clone()
53
58
  } else {
54
59
  ObjectMap::default()
55
60
  };
@@ -59,7 +64,7 @@ pub fn ui_h(args: &[Value]) -> Value {
59
64
  Value::Array(VmRef::new(children_vec.clone())),
60
65
  );
61
66
  }
62
- return f(&[Value::Object(VmRef::new(merged))]);
67
+ return f(&[Value::object(merged)]);
63
68
  }
64
69
 
65
70
  if is_fragment_tag(&tag) {
@@ -107,24 +112,18 @@ fn vnode_element(tag: Arc<str>, props: Value, children: Vec<Value>) -> Value {
107
112
  props
108
113
  },
109
114
  );
110
- m.insert(
111
- Arc::from("children"),
112
- Value::Array(VmRef::new(children)),
113
- );
115
+ m.insert(Arc::from("children"), Value::Array(VmRef::new(children)));
114
116
  m.insert(Arc::from("_el"), Value::Null);
115
- Value::Object(VmRef::new(m))
117
+ Value::object(m)
116
118
  }
117
119
 
118
120
  fn vnode_fragment(children: Vec<Value>) -> Value {
119
121
  let mut m = ObjectMap::default();
120
122
  m.insert(Arc::from("tag"), fragment_value());
121
123
  m.insert(Arc::from("props"), Value::Null);
122
- m.insert(
123
- Arc::from("children"),
124
- Value::Array(VmRef::new(children)),
125
- );
124
+ m.insert(Arc::from("children"), Value::Array(VmRef::new(children)));
126
125
  m.insert(Arc::from("_el"), Value::Null);
127
- Value::Object(VmRef::new(m))
126
+ Value::object(m)
128
127
  }
129
128
 
130
129
  /// Pluggable UI backend (Floem, DOM, SwiftUI, …). Main-thread / single-threaded by default.
@@ -25,6 +25,8 @@ timers = ["dep:tishlang_runtime"]
25
25
  # Any HTTP build needs Send-safe values so handlers can be dispatched
26
26
  # across worker threads or processes. HTTP implies timers (fetch/Promise often pair with setTimeout).
27
27
  http = ["dep:tishlang_runtime", "tishlang_runtime/http", "send-values", "timers"]
28
+ # Promise + `tish:http.await` / `Promise` export without the full HTTP client stack (e.g. wasm32-wasip1).
29
+ promise = ["dep:tishlang_runtime", "tishlang_runtime/promise", "send-values"]
28
30
  ws = ["dep:tishlang_runtime", "tishlang_runtime/ws"]
29
31
 
30
32
  [dependencies]