Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(neon): Node stdlib #1074

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/neon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub mod event;
pub mod handle;
mod macros;
pub mod meta;
pub mod node;
pub mod object;
pub mod prelude;
pub mod reflect;
Expand Down
89 changes: 89 additions & 0 deletions crates/neon/src/node/console.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::marker::PhantomData;

use crate::{
context::Context,
handle::{Handle, Root},
object::Object,
result::{JsResult, NeonResult},
thread::LocalKey,
types::{extract::TryIntoJs, function::BindOptions, JsFunction, JsObject},
};

static CONSOLE: LocalKey<Root<JsObject>> = LocalKey::new();
static LOG: LocalKey<Root<JsFunction>> = LocalKey::new();
static ERROR: LocalKey<Root<JsFunction>> = LocalKey::new();
static INFO: LocalKey<Root<JsFunction>> = LocalKey::new();
static WARN: LocalKey<Root<JsFunction>> = LocalKey::new();
static CLEAR: LocalKey<Root<JsFunction>> = LocalKey::new();

pub struct Console<'a, 'cx: 'a, C: Context<'cx>> {
pub(crate) cx: &'a mut C,
pub(crate) marker: PhantomData<&'cx ()>,
}

impl<'a, 'cx: 'a, C: Context<'cx>> Console<'a, 'cx, C> {
fn memo<T, F>(
&mut self,
cache: &'static LocalKey<Root<T>>,
get_container: F,
key: &str,
) -> JsResult<'cx, T>
where
T: Object,
F: FnOnce(&mut Self) -> JsResult<'cx, JsObject>,
{
let container = get_container(self)?;
let v = cache.get_or_try_init(self.cx, |cx| {
let v: Handle<T> = container.get(cx, key)?;
Ok(v.root(cx))
})?;
Ok(v.to_inner(self.cx))
}

fn memo_method<F>(
&mut self,
cache: &'static LocalKey<Root<JsFunction>>,
get_container: F,
key: &str,
) -> NeonResult<BindOptions<'_, 'cx>>
where
F: FnOnce(&mut Self) -> JsResult<'cx, JsObject>,
{
let container = get_container(self)?;
let function = self.memo(cache, |_| Ok(container), key)?;
let mut method = function.bind(self.cx.cx_mut());
method.this(container)?;
Ok(method)
}

pub(crate) fn new(cx: &'a mut C) -> Self {
Self {
cx,
marker: PhantomData,
}
}

fn console_object(&mut self) -> JsResult<'cx, JsObject> {
self.memo(&CONSOLE, |c| Ok(c.cx.global_object()), "console")
}

pub fn log<T: TryIntoJs<'cx>>(&mut self, msg: T) -> NeonResult<()> {
self.memo_method(&LOG, |c| c.console_object(), "log")?.arg(msg)?.exec()
}

pub fn error<T: TryIntoJs<'cx>>(&mut self, msg: T) -> NeonResult<()> {
self.memo_method(&ERROR, |c| c.console_object(), "error")?.arg(msg)?.exec()
}

pub fn info<T: TryIntoJs<'cx>>(&mut self, msg: T) -> NeonResult<()> {
self.memo_method(&INFO, |c| c.console_object(), "info")?.arg(msg)?.exec()
}

pub fn warn<T: TryIntoJs<'cx>>(&mut self, msg: T) -> NeonResult<()> {
self.memo_method(&WARN, |c| c.console_object(), "warn")?.arg(msg)?.exec()
}

pub fn clear<T: TryIntoJs<'cx>>(&mut self) -> NeonResult<()> {
self.memo_method(&CLEAR, |c| c.console_object(), "clear")?.exec()
}
}
22 changes: 22 additions & 0 deletions crates/neon/src/node/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
mod console;
mod process;
use crate::context::{Context, Cx, FunctionContext, ModuleContext};

pub use console::Console;
pub use process::Process;

pub trait Node<'cx>: Context<'cx> {
fn console<'a>(&'a mut self) -> Console<'a, 'cx, Self> {
Console::new(self)
}

fn process<'a>(&'a mut self) -> Process<'a, 'cx, Self> {
Process::new(self)
}
}

impl<'cx> Node<'cx> for ModuleContext<'cx> {}

impl<'cx> Node<'cx> for FunctionContext<'cx> {}

impl<'cx> Node<'cx> for Cx<'cx> {}
106 changes: 106 additions & 0 deletions crates/neon/src/node/process.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::marker::PhantomData;

use crate::{
context::Context,
handle::{Handle, Root},
object::Object,
result::{JsResult, NeonResult},
thread::LocalKey,
types::{JsObject, JsString},
};

static PROCESS: LocalKey<Root<JsObject>> = LocalKey::new();
static VERSIONS: LocalKey<Root<JsObject>> = LocalKey::new();
static VERSION_DATA: LocalKey<VersionData> = LocalKey::new();

pub struct Process<'a, 'cx: 'a, C: Context<'cx>> {
pub(crate) cx: &'a mut C,
pub(crate) marker: PhantomData<&'cx ()>,
}

impl<'a, 'cx: 'a, C: Context<'cx>> Process<'a, 'cx, C> {
// FIXME: this can be abstracted in a private super-trait
fn memo<T, F>(
&mut self,
cache: &'static LocalKey<Root<T>>,
get_container: F,
key: &str,
) -> JsResult<'cx, T>
where
T: Object,
F: FnOnce(&mut Self) -> JsResult<'cx, JsObject>,
{
let container = get_container(self)?;
let v = cache.get_or_try_init(self.cx, |cx| {
let v: Handle<T> = container.get(cx, key)?;
Ok(v.root(cx))
})?;
Ok(v.to_inner(self.cx))
}

pub(crate) fn new(cx: &'a mut C) -> Self {
Self {
cx,
marker: PhantomData,
}
}

fn process_object(&mut self) -> JsResult<'cx, JsObject> {
self.memo(&PROCESS, |c| Ok(c.cx.global_object()), "process")
}

fn versions_object(&mut self) -> JsResult<'cx, JsObject> {
self.memo(&VERSIONS, |c| c.process_object(), "versions")
}

pub fn versions(&mut self) -> NeonResult<Versions<'cx>> {
let object = self.versions_object()?;

Versions::new(self.cx, object)
}
}

pub struct Versions<'cx> {
pub(crate) object: Handle<'cx, JsObject>,
pub(crate) data: &'cx VersionData,
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub struct VersionData {
pub node: String,
pub modules: String,
pub napi: String,
pub unicode: String,
pub uv: String,
}

impl<'cx> Versions<'cx> {
fn new<C: Context<'cx>>(cx: &mut C, object: Handle<'cx, JsObject>) -> NeonResult<Self> {
Ok(Self {
object,
data: VERSION_DATA.get_or_try_init(cx, |cx| {
let node = object.get::<JsString, _, _>(cx, "node")?.value(cx);
let modules = object.get::<JsString, _, _>(cx, "modules")?.value(cx);
let napi = object.get::<JsString, _, _>(cx, "napi")?.value(cx);
let unicode = object.get::<JsString, _, _>(cx, "unicode")?.value(cx);
let uv = object.get::<JsString, _, _>(cx, "uv")?.value(cx);
Ok(VersionData {
node,
modules,
napi,
unicode,
uv,
})
})?,
})
}

pub fn object(&self) -> Handle<'cx, JsObject> {
self.object
}

pub fn data(&self) -> &VersionData {
&self.data
}
}
12 changes: 12 additions & 0 deletions test/napi/lib/node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var addon = require("..");
var assert = require("chai").assert;

describe("Node Standard Library", function () {
it("should print to console", function () {
addon.call_console_log_and_error();
});

it("process.versions.node", function () {
assert.strictEqual(addon.get_node_version(), process.versions.node);
});
});
14 changes: 14 additions & 0 deletions test/napi/src/js/node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use neon::node::Node;
use neon::prelude::*;

pub fn call_console_log_and_error(mut cx: FunctionContext) -> JsResult<JsUndefined> {
cx.console().log("This is cx.console().log()!")?;
cx.console().error("This is cx.console().error()!")?;
Ok(cx.undefined())
}

pub fn get_node_version(mut cx: FunctionContext) -> JsResult<JsString> {
let versions = cx.process().versions()?;
let data = versions.data();
Ok(cx.string(&data.node))
}
8 changes: 6 additions & 2 deletions test/napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use once_cell::sync::OnceCell;
use tokio::runtime::Runtime;

use crate::js::{
arrays::*, boxed::*, coercions::*, date::*, errors::*, functions::*, numbers::*, objects::*,
strings::*, threads::*, typedarrays::*, types::*,
arrays::*, boxed::*, coercions::*, date::*, errors::*, functions::*, node::*, numbers::*,
objects::*, strings::*, threads::*, typedarrays::*, types::*,
};

mod js {
Expand All @@ -18,6 +18,7 @@ mod js {
pub mod extract;
pub mod functions;
pub mod futures;
pub mod node;
pub mod numbers;
pub mod objects;
pub mod strings;
Expand Down Expand Up @@ -444,6 +445,9 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
js::extract::extract_single_add_one,
)?;

cx.export_function("call_console_log_and_error", call_console_log_and_error)?;
cx.export_function("get_node_version", get_node_version)?;

Ok(())
}

Expand Down
Loading