https://github.com/emeraldpay/neon-frame
https://github.com/emeraldpay/neon-frame
Last synced: about 1 year ago
JSON representation
- Host: GitHub
- URL: https://github.com/emeraldpay/neon-frame
- Owner: emeraldpay
- License: apache-2.0
- Created: 2022-04-09T02:14:32.000Z (about 4 years ago)
- Default Branch: master
- Last Pushed: 2025-03-05T19:25:34.000Z (over 1 year ago)
- Last Synced: 2025-03-27T10:13:00.900Z (over 1 year ago)
- Language: Rust
- Size: 104 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.adoc
- License: LICENSE
Awesome Lists containing this project
README
= Neon Frame
image:https://img.shields.io/crates/v/neon-frame.svg?style=flat-square["Crates",link="https://crates.io/crates/neon-frame"]
image:https://img.shields.io/crates/v/neon-frame-macro.svg?style=flat-square["Crates",link="https://crates.io/crates/neon-frame-macro"]
image:https://img.shields.io/npm/v/@emeraldpay/neon-frame.svg["npm (scoped)",link="https://www.npmjs.com/package/@emeraldpay/neon-frame"]
image:https://img.shields.io/badge/License-Apache%202.0-blue.svg["License"]
A _highly opinionated_ framework to build Neon-based Rust libs for Node.
_Neon Frame_ makes a standard interface for a Rust lib to respond back to Javascript.
It takes care about handling errors and converting the response back to JS.
Neon Frame simplifies the logic behind handlers by unifying how the result and errors are processed and converted.
Now you can just write a usual method that returns a `Result` and it automatically converted and returned to Node.
You can _optionally_ use the Channels, and in this case on the Javascript side of the Neon Frame wraps those calls to get just a plain `Promise`.
== Install
.Cargo.toml
[source, toml]
----
[dependencies]
neon-frame = "0.1"
neon-frame-macro = "0.1"
----
.package.json
[source, json]
----
"dependencies": {
"@emeraldpay/neon-frame": "^0.1.1"
}
----
.JS import
[source, typescript]
----
import {neonFrameCall} from "@emeraldpay/neon-frame";
----
== Short intro to its usage
=== Rust side
.Now you can mark a function with `#[neon_frame_fn]` macro:
[source, rust]
----
#[macro_use]
extern crate neon_frame_macro;
#[neon_frame_fn]
pub fn function_hello_world(_cx: &mut FunctionContext) -> Result {
Ok("Hello World".to_string())
}
----
The macro does automatic conversion from Rust to Node types.
Instead of `String` it can be any `Serializable` structure.
Internally it just wraps it into a JSON that can be easily handled on the Node side.
=== Javascript side
[source, typescript]
----
import {neonFrameDirectCall} from "@emeraldpay/neon-frame";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const addon = require('path/to/index.node');
export function helloWorld(): string {
return neonFrameDirectCall(addon, "function_hello_world", []);
}
----
What if you want to process something in a separate thread?
I.e., get it asynchronously as a Promise in Javascript?
=== Rust side using Channels
.Use `#[neon_frame_fn(channel)]` macro which provides you with a callback function:
[source, rust]
----
#[neon_frame_fn(channel)]
pub fn function_default_channel(_cx: &mut FunctionContext, handler: H) -> Result<(), Errors>
where
H: FnOnce(Result) + Send + 'static {
std::thread::spawn(move || {
let result = "Hello World".to_string();
handler(Ok(result));
});
Ok(())
}
----
=== Javascript side using Promise
[source, typescript]
----
import {neonFrameHandlerCall} from "@emeraldpay/neon-frame";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const addon = require('path/to/index.node');
export function default_channel(): Promise {
return neonFrameHandlerCall(addon, "function_default_channel", []);
}
----
== Examples
In the `/integration-tests` directory you can find Interation Tests that can also server as implementation examples.
== How it works with details
As a response it uses a JSON like:
[source, json]
----
{
"succeeded": true,
"result": {
"foo": "bar"
}
}
----
Where `{"foo": "bar"}` is an actual result.
If the method failed is returns something like:
[source, json]
----
{
"succeeded": false,
"error": {
"code": 100,
"message": "Invalid input value for #0"
}
}
----
As a Typescript type it's:
[source, typescript]
----
type Status = {
succeeded: boolean,
result: T | undefined,
error: {
code: number,
message: string
} | undefined
}
----
To use Neon Frame simply annotate your method with `#[neon_frame_fn]`:
[source, rust]
----
#[neon_frame_fn]
pub fn hello_world(cx: &mut FunctionContext) -> Result {
Ok("Hello World".to_string())
}
----
NOTE: The method is expected to have `cx: &mut FunctionContext` instead of `mut cx: FunctionContext`.
Also, you need to write a converter from `MyError` to `(usize, String)`.
I.e. implement the trait `impl From for (usize, String)`:
[source, rust]
----
impl From for (usize, String) {
fn from(err: MyError) -> Self {
todo!()
}
}
----
In addition to the standard synchronous call the library provides same simplification for Channel handlers.
At that case you use `#[neon_frame_fn(channel)]` macro, and use additional parameter to your function for the `FnOnce` that handles the response:
[source, rust]
----
// function called from JS as:
//
// hello_world((x) => { ... });
//
#[neon_frame_fn(channel)]
pub fn hello_world(cx: &mut FunctionContext, handler: H) -> Result<(), MyError>
where
H: FnOnce(Result) + Send + 'static {
std::thread::spawn(move || {
handler(Ok("Hello World".to_string()));
});
Ok(())
}
----
By default, it uses the first JS argument as a handler function.
Buf if you need to use it at a different position you can specify it as parameter like `#[neon_frame_fn(channel=2)]`
[source, rust]
----
// function called from JS as:
//
// hello_world("hi", "there", (x) => { ... });
//
// i.e. with handler at the 3rd position, which is 2 starting from zero
//
#[neon_frame_fn(channel=2)]
pub fn hello_world(cx: &mut FunctionContext, handler: H) -> Result<(), MyError>
where
H: FnOnce(Result) + Send + 'static {
todo!()
}
----
== License
Copyright 2022 EmeraldPay, Inc
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.