{"id":26130585,"url":"https://github.com/emeraldpay/neon-frame","last_synced_at":"2025-04-13T19:33:58.881Z","repository":{"id":57210130,"uuid":"479593582","full_name":"emeraldpay/neon-frame","owner":"emeraldpay","description":null,"archived":false,"fork":false,"pushed_at":"2025-03-05T19:25:34.000Z","size":107,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-27T10:13:00.900Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emeraldpay.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-04-09T02:14:32.000Z","updated_at":"2025-03-05T19:25:38.000Z","dependencies_parsed_at":"2022-09-01T08:10:36.867Z","dependency_job_id":null,"html_url":"https://github.com/emeraldpay/neon-frame","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emeraldpay%2Fneon-frame","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emeraldpay%2Fneon-frame/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emeraldpay%2Fneon-frame/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emeraldpay%2Fneon-frame/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emeraldpay","download_url":"https://codeload.github.com/emeraldpay/neon-frame/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248768286,"owners_count":21158608,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-10T20:51:53.902Z","updated_at":"2025-04-13T19:33:58.858Z","avatar_url":"https://github.com/emeraldpay.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Neon Frame\n\nimage:https://img.shields.io/crates/v/neon-frame.svg?style=flat-square[\"Crates\",link=\"https://crates.io/crates/neon-frame\"]\nimage:https://img.shields.io/crates/v/neon-frame-macro.svg?style=flat-square[\"Crates\",link=\"https://crates.io/crates/neon-frame-macro\"]\nimage:https://img.shields.io/npm/v/@emeraldpay/neon-frame.svg[\"npm (scoped)\",link=\"https://www.npmjs.com/package/@emeraldpay/neon-frame\"]\nimage:https://img.shields.io/badge/License-Apache%202.0-blue.svg[\"License\"]\n\nA _highly opinionated_ framework to build Neon-based Rust libs for Node.\n_Neon Frame_ makes a standard interface for a Rust lib to respond back to Javascript.\nIt takes care about handling errors and converting the response back to JS.\n\nNeon Frame simplifies the logic behind handlers by unifying how the result and errors are processed and converted.\nNow you can just write a usual method that returns a `Result\u003cT, E\u003e` and it automatically converted and returned to Node.\n\nYou 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\u003cT\u003e`.\n\n== Install\n\n.Cargo.toml\n[source, toml]\n----\n[dependencies]\nneon-frame = \"0.1\"\nneon-frame-macro = \"0.1\"\n----\n\n.package.json\n[source, json]\n----\n\"dependencies\": {\n    \"@emeraldpay/neon-frame\": \"^0.1.1\"\n}\n----\n\n.JS import\n[source, typescript]\n----\nimport {neonFrameCall} from \"@emeraldpay/neon-frame\";\n----\n\n== Short intro to its usage\n\n=== Rust side\n\n.Now you can mark a function with `#[neon_frame_fn]` macro:\n[source, rust]\n----\n#[macro_use]\nextern crate neon_frame_macro;\n\n#[neon_frame_fn]\npub fn function_hello_world(_cx: \u0026mut FunctionContext) -\u003e Result\u003cString, Errors\u003e {\n    Ok(\"Hello World\".to_string())\n}\n----\n\nThe macro does automatic conversion from Rust to Node types.\nInstead of `String` it can be any `Serializable` structure.\nInternally it just wraps it into a JSON that can be easily handled on the Node side.\n\n=== Javascript side\n\n[source, typescript]\n----\nimport {neonFrameDirectCall} from \"@emeraldpay/neon-frame\";\n\n// eslint-disable-next-line @typescript-eslint/no-var-requires\nconst addon = require('path/to/index.node');\n\nexport function helloWorld(): string {\n    return neonFrameDirectCall\u003cstring\u003e(addon, \"function_hello_world\", []);\n}\n----\n\nWhat if you want to process something in a separate thread?\nI.e., get it asynchronously as a Promise in Javascript?\n\n=== Rust side using Channels\n\n.Use `#[neon_frame_fn(channel)]` macro which provides you with a callback function:\n[source, rust]\n----\n#[neon_frame_fn(channel)]\npub fn function_default_channel\u003cH\u003e(_cx: \u0026mut FunctionContext, handler: H) -\u003e Result\u003c(), Errors\u003e\n    where\n        H: FnOnce(Result\u003cString, Errors\u003e) + Send + 'static {\n\n    std::thread::spawn(move || {\n        let result = \"Hello World\".to_string();\n        handler(Ok(result));\n    });\n\n    Ok(())\n}\n----\n\n=== Javascript side using Promise\n\n[source, typescript]\n----\nimport {neonFrameHandlerCall} from \"@emeraldpay/neon-frame\";\n\n// eslint-disable-next-line @typescript-eslint/no-var-requires\nconst addon = require('path/to/index.node');\n\nexport function default_channel(): Promise\u003cstring\u003e {\n    return neonFrameHandlerCall\u003cstring\u003e(addon, \"function_default_channel\", []);\n}\n----\n\n== Examples\n\nIn the `/integration-tests` directory you can find Interation Tests that can also server as implementation examples.\n\n== How it works with details\n\nAs a response it uses a JSON like:\n[source, json]\n----\n{\n  \"succeeded\": true,\n  \"result\": {\n    \"foo\": \"bar\"\n  }\n}\n----\n\nWhere `{\"foo\": \"bar\"}` is an actual result.\n\nIf the method failed is returns something like:\n\n[source, json]\n----\n{\n  \"succeeded\": false,\n  \"error\": {\n    \"code\": 100,\n    \"message\": \"Invalid input value for #0\"\n  }\n}\n----\n\nAs a Typescript type it's:\n\n[source, typescript]\n----\ntype Status\u003cT\u003e = {\n    succeeded: boolean,\n    result: T | undefined,\n    error: {\n        code: number,\n        message: string\n    } | undefined\n}\n----\n\nTo use Neon Frame simply annotate your method with `#[neon_frame_fn]`:\n\n[source, rust]\n----\n#[neon_frame_fn]\npub fn hello_world(cx: \u0026mut FunctionContext) -\u003e Result\u003cString, MyError\u003e {\n    Ok(\"Hello World\".to_string())\n}\n----\n\nNOTE: The method is expected to have `cx: \u0026mut FunctionContext` instead of `mut cx: FunctionContext`.\n\nAlso, you need to write a converter from `MyError` to `(usize, String)`.\nI.e. implement the trait `impl From\u003cMyError\u003e for (usize, String)`:\n\n[source, rust]\n----\nimpl From\u003cMyError\u003e for (usize, String) {\n    fn from(err: MyError) -\u003e Self {\n        todo!()\n    }\n}\n----\n\nIn addition to the standard synchronous call the library provides same simplification for Channel handlers.\nAt that case you use `#[neon_frame_fn(channel)]` macro, and use additional parameter to your function for the `FnOnce` that handles the response:\n\n[source, rust]\n----\n// function called from JS as:\n//\n// hello_world((x) =\u003e { ... });\n//\n#[neon_frame_fn(channel)]\npub fn hello_world\u003cH\u003e(cx: \u0026mut FunctionContext, handler: H) -\u003e Result\u003c(), MyError\u003e\n    where\n        H: FnOnce(Result\u003cString, MyError\u003e) + Send + 'static {\n\n    std::thread::spawn(move || {\n        handler(Ok(\"Hello World\".to_string()));\n    });\n    Ok(())\n}\n----\n\nBy default, it uses the first JS argument as a handler function.\nBuf if you need to use it at a different position you can specify it as parameter like `#[neon_frame_fn(channel=2)]`\n\n[source, rust]\n----\n// function called from JS as:\n//\n// hello_world(\"hi\", \"there\", (x) =\u003e { ... });\n//\n// i.e. with handler at the 3rd position, which is 2 starting from zero\n//\n#[neon_frame_fn(channel=2)]\npub fn hello_world\u003cH\u003e(cx: \u0026mut FunctionContext, handler: H) -\u003e Result\u003c(), MyError\u003e\n    where\n        H: FnOnce(Result\u003cString, MyError\u003e) + Send + 'static {\n\n    todo!()\n}\n----\n\n== License\n\nCopyright 2022 EmeraldPay, Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\nSee the License for the specific language governing permissions and limitations under the License.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femeraldpay%2Fneon-frame","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femeraldpay%2Fneon-frame","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femeraldpay%2Fneon-frame/lists"}