Skip to content
File

Blob: src/rust/jsg/lib.rs

rust812 lines
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5use std::future::Future;
6use std::num::ParseIntError;
7use std::ops::Deref;
8 
9pub mod feature_flags;
10pub mod macros;
11pub mod modules;
12pub mod resource;
13pub mod v8;
14mod wrappable;
15 
16pub use feature_flags::FeatureFlags;
17pub use resource::Rc;
18pub use resource::Resource;
19pub use resource::Weak;
20pub use v8::ArrayBuffer;
21pub use v8::ArrayBufferView;
22pub use v8::BackingStore;
23pub use v8::BigInt64Array;
24pub use v8::BigUint64Array;
25pub use v8::Float32Array;
26pub use v8::Float64Array;
27pub use v8::GcVisitor;
28pub use v8::Int8Array;
29pub use v8::Int16Array;
30pub use v8::Int32Array;
31pub use v8::IsolatePtr;
32pub use v8::Uint8Array;
33pub use v8::Uint16Array;
34pub use v8::Uint32Array;
35pub use v8::ffi::ExceptionType;
36pub use wrappable::FromJS;
37pub use wrappable::ToJS;
38pub use wrappable::Traced;
39 
40#[cxx::bridge(namespace = "workerd::rust::jsg")]
41mod ffi {
42 extern "Rust" {
43 type Realm;
44 
45 /// Create a fully-initialized Realm with feature flags.
46 /// `feature_flags_data` is canonical (single-segment, no segment table) Cap'n Proto
47 /// bytes produced by `capnp::canonicalize()` on the C++ side.
48 #[expect(clippy::unnecessary_box_returns)]
49 unsafe fn realm_create(isolate: *mut Isolate, feature_flags_data: &[u8]) -> Box<Realm>;
50 }
51 
52 unsafe extern "C++" {
53 include!("workerd/rust/jsg/ffi.h");
54 
55 type Isolate = crate::v8::ffi::Isolate;
56 
57 // Realm
58 pub unsafe fn realm_from_isolate(isolate: *mut Isolate) -> *mut Realm;
59 }
60}
61 
62pub type Result<T, E = Error> = std::result::Result<T, E>;
63 
64impl From<&str> for ExceptionType {
65 fn from(value: &str) -> Self {
66 match value {
67 "OperationError" => Self::OperationError,
68 "DataError" => Self::DataError,
69 "DataCloneError" => Self::DataCloneError,
70 "InvalidAccessError" => Self::InvalidAccessError,
71 "InvalidStateError" => Self::InvalidStateError,
72 "InvalidCharacterError" => Self::InvalidCharacterError,
73 "NotSupportedError" => Self::NotSupportedError,
74 "SyntaxError" => Self::SyntaxError,
75 "TimeoutError" => Self::TimeoutError,
76 "TypeMismatchError" => Self::TypeMismatchError,
77 "AbortError" => Self::AbortError,
78 "NotFoundError" => Self::NotFoundError,
79 "TypeError" => Self::TypeError,
80 "RangeError" => Self::RangeError,
81 "ReferenceError" => Self::ReferenceError,
82 _ => Self::Error,
83 }
84 }
85}
86 
87#[derive(Debug, Clone)]
88pub struct Error {
89 pub name: ExceptionType,
90 pub message: String,
91}
92 
93impl std::fmt::Display for Error {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(f, "{}: {}", self.name, self.message)
96 }
97}
98 
99/// Generates constructor methods for each `ExceptionType` variant.
100/// e.g., `new_type_error("message")` creates an Error with `ExceptionType::TypeError`
101macro_rules! impl_error_constructors {
102 ($($variant:ident => $fn_name:ident),* $(,)?) => {
103 impl Error {
104 $(
105 pub fn $fn_name(message: impl Into<String>) -> Self {
106 Self {
107 name: ExceptionType::$variant,
108 message: message.into(),
109 }
110 }
111 )*
112 }
113 };
114}
115 
116impl_error_constructors! {
117 OperationError => new_operation_error,
118 DataError => new_data_error,
119 DataCloneError => new_data_clone_error,
120 InvalidAccessError => new_invalid_access_error,
121 InvalidStateError => new_invalid_state_error,
122 InvalidCharacterError => new_invalid_character_error,
123 NotSupportedError => new_not_supported_error,
124 SyntaxError => new_syntax_error,
125 TimeoutError => new_timeout_error,
126 TypeMismatchError => new_type_mismatch_error,
127 AbortError => new_abort_error,
128 NotFoundError => new_not_found_error,
129 TypeError => new_type_error,
130 Error => new_error,
131 RangeError => new_range_error,
132 ReferenceError => new_reference_error,
133}
134 
135impl FromJS for Error {
136 type ResultType = Self;
137 
138 /// Creates an Error from a V8 value (typically an exception).
139 ///
140 /// If the value is a native error, extracts the name and message properties.
141 /// Otherwise, converts the value to a string for the message.
142 fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {
143 if value.is_native_error() {
144 let obj: v8::Local<v8::Object> = value.into();
145 
146 let name = obj
147 .get(lock, "name")
148 .and_then(|v| String::from_js(lock, v).ok());
149 
150 let message = obj
151 .get(lock, "message")
152 .and_then(|v| String::from_js(lock, v).ok())
153 .unwrap_or_else(|| "Unknown error".to_owned());
154 
155 Ok(Self {
156 name: name.map_or(ExceptionType::Error, |n| ExceptionType::from(n.as_str())),
157 message,
158 })
159 } else {
160 Err(Self::new_type_error("Unknown error"))
161 }
162 }
163}
164 
165impl Error {
166 pub fn new(name: &str, message: &str) -> Self {
167 Self {
168 name: ExceptionType::from(name),
169 message: message.to_owned(),
170 }
171 }
172 
173 /// Creates a V8 exception from this error.
174 pub fn to_local<'a>(&self, isolate: v8::IsolatePtr) -> v8::Local<'a, v8::Value> {
175 // SAFETY: isolate is valid and locked (guaranteed by caller).
176 unsafe {
177 v8::Local::from_ffi(
178 isolate,
179 v8::ffi::exception_create(isolate.as_ffi(), self.name, &self.message),
180 )
181 }
182 }
183}
184 
185impl From<ParseIntError> for Error {
186 fn from(err: ParseIntError) -> Self {
187 Self::new_range_error(format!("Failed to parse integer: {err}"))
188 }
189}
190 
191/// A wrapper type that prevents automatic type coercion when unwrapping from JavaScript.
192///
193/// JavaScript automatically coerces types in certain contexts. For instance, when a JavaScript
194/// API expects a string, calling it with the value `null` will result in the null being coerced
195/// into the string value `"null"`.
196///
197/// `NonCoercible<T>` can be used to disable automatic type coercion in APIs. For instance,
198/// `NonCoercible<String>` can be used to accept a value only if the input is already a string.
199/// If the input is the value `null`, then an error is thrown rather than silently coercing to
200/// `"null"`.
201///
202/// # Supported Types
203///
204/// Any type implementing the [`Type`] trait can be used with `NonCoercible<T>`. Built-in
205/// implementations include:
206///
207/// - `NonCoercible<String>` - only accepts JavaScript strings
208/// - `NonCoercible<bool>` - only accepts JavaScript booleans
209/// - `NonCoercible<Number>` - only accepts JavaScript numbers
210///
211/// # Example
212///
213/// ```ignore
214/// use jsg::NonCoercible;
215///
216/// // This function will only accept actual strings, not values that can be coerced to strings
217/// #[jsg_method]
218/// pub fn process_string(&self, param: NonCoercible<String>) -> Result<(), Error> {
219/// let s: &String = param.as_ref();
220/// // or use Deref: let s: &str = &*param;
221/// // ...
222/// }
223/// ```
224///
225/// # Important Notes
226///
227/// Using `NonCoercible<T>` runs counter to Web IDL and general JavaScript API conventions.
228/// In nearly all cases, APIs should allow coercion to occur and should deal with the coerced
229/// input accordingly to avoid being a source of user confusion. Only use `NonCoercible` if
230/// you have a good reason to disable coercion.
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct NonCoercible<T> {
233 value: T,
234}
235 
236impl<T> NonCoercible<T> {
237 /// Creates a new `NonCoercible` wrapper around the given value.
238 pub fn new(value: T) -> Self {
239 Self { value }
240 }
241 
242 /// Consumes the wrapper and returns the inner value.
243 pub fn into_inner(self) -> T {
244 self.value
245 }
246}
247 
248impl<T> From<T> for NonCoercible<T> {
249 fn from(value: T) -> Self {
250 Self::new(value)
251 }
252}
253 
254impl<T> AsRef<T> for NonCoercible<T> {
255 fn as_ref(&self) -> &T {
256 &self.value
257 }
258}
259 
260impl<T> Deref for NonCoercible<T> {
261 type Target = T;
262 
263 fn deref(&self) -> &Self::Target {
264 &self.value
265 }
266}
267 
268/// A wrapper type for JavaScript numbers (IEEE 754 double-precision floats).
269///
270/// `Number` represents JavaScript's `number` type, which is always a 64-bit
271/// floating-point value. This wrapper type is used instead of raw `f64` to
272/// distinguish between JavaScript numbers and Rust's `f64` type used for
273/// `Float64Array` elements.
274///
275/// # Usage
276///
277/// Use `Number` when you need to accept or return JavaScript numbers in your API:
278///
279/// ```ignore
280/// use jsg::Number;
281///
282/// #[jsg_method]
283/// pub fn add(&self, a: Number, b: Number) -> Number {
284/// Number::new(a.value() + b.value())
285/// }
286/// ```
287///
288/// # Type Mapping
289///
290/// | Rust Type | JavaScript Type |
291/// |-----------|-----------------|
292/// | `jsg::Number` | `number` |
293/// | `f64` | Used for `Float64Array` elements |
294/// | `Vec<f64>` | `Float64Array` |
295#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
296pub struct Number {
297 value: f64,
298}
299 
300impl Number {
301 /// The largest integer that can be represented exactly in JavaScript (2^53 - 1).
302 ///
303 /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)
304 pub const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991.0; // 2^53 - 1
305 
306 /// The smallest integer that can be represented exactly in JavaScript (-(2^53 - 1)).
307 ///
308 /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER)
309 pub const MIN_SAFE_INTEGER: f64 = -9_007_199_254_740_991.0; // -(2^53 - 1)
310 
311 /// Creates a new `Number` from an `f64` value.
312 #[inline]
313 pub fn new(value: f64) -> Self {
314 Self { value }
315 }
316 
317 /// Returns the underlying `f64` value.
318 #[inline]
319 pub fn value(&self) -> f64 {
320 self.value
321 }
322 
323 /// Consumes the wrapper and returns the inner `f64` value.
324 #[inline]
325 pub fn into_inner(self) -> f64 {
326 self.value
327 }
328 
329 /// Determines whether the value is a finite number.
330 ///
331 /// Returns `true` if the value is finite (not `Infinity`, `-Infinity`, or `NaN`).
332 ///
333 /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite)
334 #[inline]
335 pub fn is_finite(&self) -> bool {
336 self.value.is_finite()
337 }
338 
339 /// Determines whether the value is an integer.
340 ///
341 /// Returns `true` if the value is finite and has no fractional part.
342 ///
343 /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger)
344 #[inline]
345 #[expect(clippy::float_cmp)] // Exact comparison is correct here - we want trunc(x) == x
346 pub fn is_integer(&self) -> bool {
347 self.value.is_finite() && self.value.trunc() == self.value
348 }
349 
350 /// Determines whether the value is `NaN`.
351 ///
352 /// This is more robust than the global `isNaN()` because it doesn't coerce
353 /// the value to a number first.
354 ///
355 /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN)
356 #[inline]
357 pub fn is_nan(&self) -> bool {
358 self.value.is_nan()
359 }
360 
361 /// Determines whether the value is a safe integer.
362 ///
363 /// A safe integer is an integer that:
364 /// - Can be exactly represented as an IEEE-754 double precision number
365 /// - Has an IEEE-754 representation that cannot be the result of rounding any other integer
366 ///
367 /// Safe integers range from -(2^53 - 1) to 2^53 - 1, inclusive.
368 ///
369 /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger)
370 #[inline]
371 pub fn is_safe_integer(&self) -> bool {
372 self.is_integer()
373 && self.value >= Self::MIN_SAFE_INTEGER
374 && self.value <= Self::MAX_SAFE_INTEGER
375 }
376}
377 
378impl From<f64> for Number {
379 fn from(value: f64) -> Self {
380 Self::new(value)
381 }
382}
383 
384impl From<Number> for f64 {
385 fn from(num: Number) -> Self {
386 num.value
387 }
388}
389 
390impl From<i32> for Number {
391 fn from(value: i32) -> Self {
392 Self::new(f64::from(value))
393 }
394}
395 
396impl From<u32> for Number {
397 fn from(value: u32) -> Self {
398 Self::new(f64::from(value))
399 }
400}
401 
402impl std::fmt::Display for Number {
403 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404 write!(f, "{}", self.value)
405 }
406}
407 
408/// A wrapper type that accepts `null`, `undefined`, or a value of type `T`.
409///
410/// `Nullable<T>` is similar to `Option<T>` but also accepts `undefined` as a null-ish value.
411/// This is useful for JavaScript APIs where both `null` and `undefined` represent
412/// the absence of a value.
413///
414/// # Behavior
415///
416/// - `null` → `Nullable::Null`
417/// - `undefined` → `Nullable::Undefined`
418/// - `T` → `Nullable::Some(T)`
419///
420/// # Example
421///
422/// ```ignore
423/// use jsg::Nullable;
424///
425/// #[jsg_method]
426/// pub fn process(&self, value: Nullable<String>) -> Result<(), Error> {
427/// match value {
428/// Nullable::Some(s) => println!("Got value: {}", s),
429/// Nullable::Null => println!("Got null"),
430/// Nullable::Undefined => println!("Got undefined"),
431/// }
432/// Ok(())
433/// }
434/// ```
435#[derive(Debug, Clone, PartialEq, Eq)]
436pub enum Nullable<T> {
437 Some(T),
438 Null,
439 Undefined,
440}
441 
442impl<T> Nullable<T> {
443 /// Returns `true` if the nullable contains a value.
444 pub fn is_some(&self) -> bool {
445 matches!(self, Self::Some(_))
446 }
447 
448 /// Returns `true` if the nullable is `Null`.
449 pub fn is_null(&self) -> bool {
450 matches!(self, Self::Null)
451 }
452 
453 /// Returns `true` if the nullable is `Undefined`.
454 pub fn is_undefined(&self) -> bool {
455 matches!(self, Self::Undefined)
456 }
457 
458 /// Returns `true` if the nullable is `Null` or `Undefined`.
459 pub fn is_null_or_undefined(&self) -> bool {
460 matches!(self, Self::Null | Self::Undefined)
461 }
462 
463 /// Returns a reference to the contained value, or `None` if null or undefined.
464 pub fn as_ref(&self) -> Option<&T> {
465 match self {
466 Self::Some(v) => Some(v),
467 Self::Null | Self::Undefined => None,
468 }
469 }
470}
471 
472impl<T> From<Option<T>> for Nullable<T> {
473 fn from(opt: Option<T>) -> Self {
474 match opt {
475 Some(v) => Self::Some(v),
476 None => Self::Null,
477 }
478 }
479}
480 
481impl<T> From<Nullable<T>> for Option<T> {
482 fn from(nullable: Nullable<T>) -> Self {
483 match nullable {
484 Nullable::Some(v) => Some(v),
485 Nullable::Null | Nullable::Undefined => None,
486 }
487 }
488}
489 
490/// Proof that the V8 isolate is valid and exclusively locked by the current thread.
491///
492/// A `Lock` instance can only be created when the C++ `jsg::Lock` (or equivalent)
493/// is held, meaning:
494/// - The `v8::Isolate` pointer is valid and has not been disposed.
495/// - The current thread holds the isolate lock (`v8::Locker` is active).
496/// - No other thread can enter the isolate concurrently.
497///
498/// `Lock` is passed to resource methods and callbacks to perform V8 operations
499/// like creating objects, wrapping values, and accessing the `Realm`. It is
500/// analogous to `jsg::Lock&` in C++ JSG.
501///
502/// `Lock` is neither `Send` nor `Sync` — it cannot escape the thread that
503/// created it.
504pub struct Lock {
505 isolate: v8::IsolatePtr,
506}
507 
508impl Lock {
509 /// # Safety
510 /// The caller must ensure that `args` is a valid pointer to `FunctionCallbackInfo`.
511 pub unsafe fn from_args(args: *mut v8::ffi::FunctionCallbackInfo) -> Self {
512 // SAFETY: args is a valid FunctionCallbackInfo pointer (guaranteed by caller).
513 unsafe { Self::from_isolate_ptr(v8::ffi::fci_get_isolate(args)) }
514 }
515 
516 /// Creates a Lock from a raw isolate pointer.
517 ///
518 /// # Safety
519 /// The caller must ensure that `isolate` is a valid pointer to an `Isolate`.
520 ///
521 /// # Panics
522 /// Panics if the isolate is not currently locked.
523 pub unsafe fn from_isolate_ptr(isolate: *mut v8::ffi::Isolate) -> Self {
524 // SAFETY: isolate pointer is valid (guaranteed by caller).
525 let isolate = unsafe { v8::IsolatePtr::from_ffi(isolate) };
526 // SAFETY: Lock guarantees the isolate is valid
527 assert!(unsafe { isolate.is_locked() });
528 Self { isolate }
529 }
530 
531 /// Returns the isolate associated with this lock.
532 pub fn isolate(&self) -> v8::IsolatePtr {
533 self.isolate
534 }
535 
536 pub fn new_object<'a>(&mut self) -> v8::Local<'a, v8::Object> {
537 // SAFETY: Lock guarantees the isolate is valid and locked
538 unsafe {
539 v8::Local::from_ffi(
540 self.isolate(),
541 v8::ffi::local_new_object(self.isolate().as_ffi()),
542 )
543 }
544 }
545 
546 pub fn throw_error(&mut self, message: &str) {
547 // SAFETY: Lock guarantees the isolate is valid and locked
548 unsafe { v8::ffi::isolate_throw_error(self.isolate().as_ffi(), message) }
549 }
550 
551 pub fn await_io<F, C, I, R>(self, _fut: F, _callback: C) -> Result<R>
552 where
553 F: Future<Output = I>,
554 C: FnOnce(Self, I) -> Result<R>,
555 {
556 unimplemented!("Lock::await_io is not yet implemented for Rust resources")
557 }
558 
559 pub(crate) fn realm(&mut self) -> &mut Realm {
560 // SAFETY: isolate is valid and locked (guaranteed by Lock); Realm pointer is valid.
561 unsafe { &mut *crate::ffi::realm_from_isolate(self.isolate().as_ffi()) }
562 }
563 
564 /// Returns the current worker's compatibility flags reader.
565 ///
566 /// ```ignore
567 /// if lock.feature_flags().get_node_js_compat() {
568 /// // Node.js compatibility behavior
569 /// }
570 /// ```
571 pub fn feature_flags(&mut self) -> compatibility_date_capnp::compatibility_flags::Reader<'_> {
572 self.realm().feature_flags.reader()
573 }
574 
575 /// Throws an error as a V8 exception.
576 pub fn throw_exception(&mut self, err: &Error) {
577 // SAFETY: isolate is valid and locked (guaranteed by Lock).
578 unsafe {
579 v8::ffi::isolate_throw_exception(
580 self.isolate().as_ffi(),
581 err.to_local(self.isolate()).into_ffi(),
582 );
583 }
584 }
585 
586 /// Throws an internal error as a V8 exception, matching `makeInternalError()` in C++.
587 ///
588 /// Generates a unique error reference ID, logs `internal_message` with the ID via
589 /// `KJ_LOG(ERROR)` (reaching Sentry), and schedules a generic
590 /// `"Error: internal error; reference = <id>"` JS exception on the isolate so the
591 /// internal message is never exposed to JavaScript.
592 pub fn throw_internal_error(&mut self, internal_message: &str) {
593 // SAFETY: isolate is valid and locked (guaranteed by Lock).
594 unsafe {
595 v8::ffi::isolate_throw_internal_error(self.isolate().as_ffi(), internal_message);
596 }
597 }
598 
599 /// Signals that JavaScript execution on this isolate should be terminated immediately.
600 ///
601 /// Calls `IsolateBase::TerminateExecution()`, so V8 raises an
602 /// uncatchable termination exception that unwinds all JS call frames back
603 /// to the top-level C++ entry point.
604 ///
605 /// This mirrors what C++ `KJ_ASSERT` / `KJ_FAIL_ASSERT` effectively do when they fire
606 /// inside an isolate context: they abort further JS execution rather than letting the
607 /// isolate continue in a potentially inconsistent state.
608 pub fn terminate_execution(&mut self) {
609 // SAFETY: isolate is valid and locked (guaranteed by Lock).
610 unsafe {
611 v8::ffi::isolate_terminate_execution(self.isolate().as_ffi());
612 }
613 }
614}
615 
616/// Provides metadata about Rust types exposed to JavaScript.
617///
618/// This trait provides type information used for error messages, memory tracking,
619/// and type validation (for `NonCoercible<T>`). The actual conversion logic is in
620/// `ToJS` (Rust → JS) and `FromJS` (JS → Rust).
621///
622/// TODO: Implement `memory_info(jsg::MemoryTracker)`
623pub trait Type: Sized {
624 /// The JavaScript class name for this type (used in error messages).
625 fn class_name() -> &'static str;
626 
627 /// Same as jsgGetMemorySelfSize
628 fn memory_self_size() -> usize {
629 std::mem::size_of::<Self>()
630 }
631 
632 /// Returns true if the V8 value is exactly this type (no coercion).
633 /// Used by `NonCoercible<T>` to reject values that would require coercion.
634 fn is_exact(value: &v8::Local<v8::Value>) -> bool;
635}
636 
637/// Represents a constant value that can be exposed to JavaScript.
638pub enum ConstantValue {
639 Number(f64),
640}
641 
642macro_rules! impl_constant_value_from_lossless {
643 ($($t:ty),*) => {
644 $(
645 impl From<$t> for ConstantValue {
646 fn from(v: $t) -> Self {
647 Self::Number(f64::from(v))
648 }
649 }
650 )*
651 };
652}
653 
654impl_constant_value_from_lossless!(i8, i16, i32, u8, u16, u32, f32, f64);
655 
656// i64/u64 can lose precision in f64 (52-bit mantissa), but JavaScript numbers are
657// always f64 so this is inherent to the language boundary.
658impl From<i64> for ConstantValue {
659 #[expect(clippy::cast_precision_loss)]
660 fn from(v: i64) -> Self {
661 Self::Number(v as f64)
662 }
663}
664 
665impl From<u64> for ConstantValue {
666 #[expect(clippy::cast_precision_loss)]
667 fn from(v: u64) -> Self {
668 Self::Number(v as f64)
669 }
670}
671 
672/// Where a [`Member::Property`] is attached on the JavaScript object.
673///
674/// This is a re-export of the CXX bridge type in [`v8::ffi`] so that callers
675/// do not need to import `jsg::v8::ffi` directly. The three variants behave
676/// as follows:
677///
678/// - `Prototype` — accessor on the prototype chain; enumerable.
679/// - `Instance` — own accessor on every instance; enumerable.
680/// - `Inspect` — symbol-keyed on the prototype; hidden from normal enumeration,
681/// surfaced by `node:util` `inspect()`.
682pub use crate::v8::ffi::PropertyKind;
683 
684pub enum Member {
685 Constructor {
686 callback: unsafe extern "C" fn(*mut v8::ffi::FunctionCallbackInfo),
687 },
688 Method {
689 name: String,
690 callback: unsafe extern "C" fn(*mut v8::ffi::FunctionCallbackInfo),
691 },
692 /// A property accessor with configurable placement. `setter_callback = None`
693 /// makes the property read-only; `Inspect` properties are always read-only.
694 Property {
695 name: String,
696 kind: PropertyKind,
697 getter_callback: unsafe extern "C" fn(*mut v8::ffi::FunctionCallbackInfo),
698 /// `None` for read-only properties.
699 setter_callback: Option<unsafe extern "C" fn(*mut v8::ffi::FunctionCallbackInfo)>,
700 },
701 StaticMethod {
702 name: String,
703 callback: unsafe extern "C" fn(*mut v8::ffi::FunctionCallbackInfo),
704 },
705 StaticConstant {
706 name: String,
707 value: ConstantValue,
708 },
709}
710 
711/// Trait for types that participate in V8 garbage collection as tracked resources.
712///
713/// Extends [`Traced`] (which provides the `trace` method for visiting nested
714/// GC-visible references) with a class name for heap snapshot tooling.
715///
716/// `#[jsg_resource]` auto-derives both `Traced` and `GarbageCollected`.
717/// Use `#[jsg_resource(custom_trace)]` to suppress the generated `Traced`
718/// impl and provide your own.
719pub trait GarbageCollected: Traced {
720 /// Class name for heap snapshots / debugging.
721 ///
722 /// Returns a `&'static CStr` — always a compile-time literal. This lets the C++ side
723 /// construct a `kj::StringPtr` directly from the NUL-terminated pointer without any
724 /// allocation or caching. Implementations must not access `self`.
725 fn memory_name(&self) -> &'static std::ffi::CStr;
726}
727 
728/// Rust types that are deep-copied into JavaScript as value types.
729///
730/// Unlike resource types, struct types are copied entirely into JavaScript objects with no
731/// further Rust involvement after wrapping. This is analogous to `JSG_STRUCT` in C++ JSG.
732pub trait Struct: Type {}
733 
734/// Per-isolate state for Rust resources exposed to JavaScript.
735///
736/// A Realm is created for each V8 isolate and stored in the isolate's data slot. It holds
737/// cached function templates and tracks resource instances. When all Rust `Ref` handles to a
738/// resource are dropped and no JS references remain, V8 GC collects the wrapper and the
739/// `CppgcShim` destruction triggers cleanup of the underlying `Wrappable`.
740pub struct Realm {
741 isolate: v8::IsolatePtr,
742 pub(crate) resources: resource::Resources,
743 /// Parsed `CompatibilityFlags` capnp message, initialized at construction.
744 feature_flags: FeatureFlags,
745}
746 
747impl Realm {
748 /// Creates a new Realm with its feature flags.
749 pub fn new(isolate: v8::IsolatePtr, feature_flags: FeatureFlags) -> Self {
750 Self {
751 isolate,
752 resources: resource::Resources::default(),
753 feature_flags,
754 }
755 }
756 
757 pub fn isolate(&self) -> v8::IsolatePtr {
758 self.isolate
759 }
760}
761 
762impl Drop for Realm {
763 fn drop(&mut self) {
764 debug_assert!(
765 // SAFETY: isolate pointer is valid (guaranteed by Realm construction).
766 unsafe { self.isolate.is_locked() },
767 "Realm must be dropped while holding the isolate lock"
768 );
769 }
770}
771 
772#[expect(clippy::unnecessary_box_returns)]
773unsafe fn realm_create(isolate: *mut v8::ffi::Isolate, feature_flags_data: &[u8]) -> Box<Realm> {
774 let feature_flags = FeatureFlags::from_bytes(feature_flags_data);
775 // SAFETY: isolate pointer is valid (guaranteed by C++ caller).
776 unsafe { Box::new(Realm::new(v8::IsolatePtr::from_ffi(isolate), feature_flags)) }
777}
778 
779/// Executes `f`, catching any panic and converting it to a JS internal error.
780///
781/// `extern "C"` V8 callbacks generated by `jsg-macros` call this so that a
782/// Rust panic is handled the same way as `KJ_ASSERT(false)` in C++ JSG
783/// handlers: the internal panic message is logged via `KJ_LOG(ERROR)` (reaching
784/// Sentry) and the JS caller receives a generic
785/// `"Error: internal error; reference = <id>"` exception with a unique
786/// reference ID, keeping the internal message out of JS-visible output.
787///
788/// After logging the error, `terminate_execution()` is called so that V8 raises
789/// an uncatchable termination exception and unwinds all JS call frames. This
790/// mirrors what C++ `KJ_ASSERT` effectively does inside an isolate context and
791/// closes the window where a panicked resource with partially-mutated `jsg::Rc`
792/// fields could be observed by a subsequent GC trace before isolate teardown.
793#[doc(hidden)]
794pub fn catch_panic<F: FnOnce()>(lock: &mut Lock, f: F) {
795 // SAFETY: `f` is called from an `extern "C"` V8 callback generated by
796 // jsg-macros. Raw pointers captured by `f` are valid for the entire
797 // duration of the callback (V8 guarantees this), and V8 is
798 // single-threaded, so there is no aliasing hazard on unwind.
799 // `AssertUnwindSafe` is therefore correct here.
800 if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
801 let msg = if let Some(s) = payload.downcast_ref::<&str>() {
802 *s
803 } else if let Some(s) = payload.downcast_ref::<String>() {
804 s.as_str()
805 } else {
806 "<non-string panic payload>"
807 };
808 lock.throw_internal_error(msg);
809 lock.terminate_execution();
810 }
811}