{"id":18104959,"url":"https://github.com/mtgrosser/nodo","last_synced_at":"2025-04-13T19:31:33.137Z","repository":{"id":46178947,"uuid":"406424054","full_name":"mtgrosser/nodo","owner":"mtgrosser","description":"Call node.js from Ruby","archived":false,"fork":false,"pushed_at":"2023-11-21T21:47:21.000Z","size":76,"stargazers_count":26,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-04-23T11:03:10.977Z","etag":null,"topics":["bridge","javascript","node","nodejs","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mtgrosser.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-14T15:30:47.000Z","updated_at":"2024-02-23T18:59:08.000Z","dependencies_parsed_at":"2023-11-21T22:43:35.690Z","dependency_job_id":null,"html_url":"https://github.com/mtgrosser/nodo","commit_stats":{"total_commits":73,"total_committers":2,"mean_commits":36.5,"dds":0.2191780821917808,"last_synced_commit":"003d98f589cc9fa07e73c338013e3a308e4586bb"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtgrosser%2Fnodo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtgrosser%2Fnodo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtgrosser%2Fnodo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtgrosser%2Fnodo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mtgrosser","download_url":"https://codeload.github.com/mtgrosser/nodo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248767910,"owners_count":21158558,"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":["bridge","javascript","node","nodejs","ruby"],"created_at":"2024-10-31T22:16:36.952Z","updated_at":"2025-04-13T19:31:33.126Z","avatar_url":"https://github.com/mtgrosser.png","language":"Ruby","readme":"[![Gem Version](https://badge.fury.io/rb/nodo.svg)](https://badge.fury.io/rb/nodo)\n[![build](https://github.com/mtgrosser/nodo/actions/workflows/build.yml/badge.svg)](https://github.com/mtgrosser/nodo/actions/workflows/build.yml)\n\n# Nōdo – call Node.js from Ruby\n\n`Nodo` provides a Ruby environment to interact with JavaScript running inside a Node process.\n\nノード means \"node\" in Japanese.\n\n## Why Nodo?\n\nNodo will dispatch all JS function calls to a single long-running Node process.\n\nJavaScript code is run in a namespaced environment, where you can access your initialized\nJS objects during sequential function calls without having to re-initialize them.\n\nIPC is done via unix sockets, greatly improving performance over classic process/eval solutions.\n\n## Installation\n\nIn your Gemfile:\n\n```ruby\ngem 'nodo'\n```\n\n### Node.js\n\nNodo requires a working installation of Node.js.\n\nIf the executable is located in your `PATH`, no configuration is required. Otherwise, the path to the binary can be set using:\n\n```ruby\nNodo.binary = '/usr/local/bin/node'\n```\n\n## Usage\n\nIn Nodo, you define JS functions as you would define Ruby methods:\n\n```ruby\nclass Foo \u003c Nodo::Core\n\n  function :say_hi, \u003c\u003c~JS\n    (name) =\u003e {\n      return `Hello ${name}!`;\n    }\n  JS\n\nend\n\nfoo = Foo.new\nfoo.say_hi('Nodo')\n=\u003e \"Hello Nodo!\"\n```\n\nJS code can also be supplied using the `code:` keyword argument:\n\n```ruby\nfunction :hello, code: \"() =\u003e 'world'\"\n```\n\n### Async functions\n\n`Nodo` supports calling `async` functions from Ruby.\nThe Ruby call will happen synchronously, i.e. it will block until the JS\nfunction resolves:\n\n```ruby\nclass SyncFoo \u003c Nodo::Core\n  function :do_something, \u003c\u003c~JS\n    async () =\u003e { return await asyncFunc(); }\n  JS\nend\n```\n\n### Using npm modules\n\nInstall your modules to `node_modules`:\n\n```shell\n$ yarn add uuid\n```\n\n`require`ing your dependencies will make the library available as a `const` with the same name:\n\n```ruby\nclass Bar \u003c Nodo::Core\n  require :uuid\n\n  function :v4, \u003c\u003c~JS\n    () =\u003e {\n      return uuid.v4();\n    }\n  JS\nend\n\nbar = Bar.new\nbar.v4 =\u003e \"b305f5c4-db9a-4504-b0c3-4e097a5ec8b9\"\n```\n\n`import` is also supported for loading ESM packages:\n\n```ruby\nclass Bar \u003c Nodo::Core\n  import :uuid\n\n  function :v4, \u003c\u003c~JS\n    () =\u003e {\n      return uuid.v4();\n    }\n  JS\nend\n\nbar = Bar.new\nbar.v4 =\u003e \"b305f5c4-db9a-4504-b0c3-4e097a5ec8b9\"\n```\n\n### Aliasing requires\n\nIf the library name cannot be used as name of the constant, the `const` name\ncan be given using hash syntax:\n\n```ruby\nclass FooBar \u003c Nodo::Core\n  require commonjs: '@rollup/plugin-commonjs'\nend\n```\n\n### Dynamic ESM imports\n\nES modules can be imported dynamically using `nodo.import()`:\n\n```ruby\nclass DynamicFoo \u003c Nodo::Core\n  function :v4, \u003c\u003c~JS\n    async () =\u003e {\n      const uuid = await nodo.import('uuid');\n      return await uuid.v4()\n    }\n  JS\nend\n```\n\nNote that the availability of dynamic imports depends on your Node version.\n\n### Defining JS constants\n\n```ruby\nclass BarFoo \u003c Nodo::Core\n  const :HELLO, \"World\"\nend\n```\n\n### Execute some custom JS during initialization\n\n```ruby\nclass BarFoo \u003c Nodo::Core\n\n  script \u003c\u003c~JS\n    // custom JS to be executed during initialization\n    // things defined here can later be used inside functions\n    const bigThing = someLib.init();\n  JS\nend\n```\n\nWith the above syntax, the script code will be generated during class definition\ntime. In order to have the code generated when the first instance is created, the\ncode can be defined inside a block:\n\n```ruby\nclass Foo \u003c Nodo::Core\n  script do\n    \u003c\u003c~JS\n      var definitionTime = #{Time.now.to_json};\n    JS\n  end\nend\n```\n\nNote that the script will still be executed only once, when the first instance\nof class is created.\n\n### Inheritance\n\nSubclasses will inherit functions, constants, dependencies and scripts from\ntheir superclasses, while only functions can be overwritten.\n\n```ruby\nclass Foo \u003c Nodo::Core\n  function :foo, \"() =\u003e 'superclass'\"\nend\n\nclass SubFoo \u003c Foo\n  function :bar, \"() =\u003e { return 'calling' + foo() }\"\nend\n\nclass SubSubFoo \u003c SubFoo\n  function :foo, \"() =\u003e 'subsubclass'\"\nend\n\nFoo.new.foo =\u003e \"superclass\"\nSubFoo.new.bar =\u003e \"callingsuperclass\"\nSubSubFoo.new.bar =\u003e \"callingsubsubclass\"\n```\n\n### Deferred function definition\n\nBy default, the function code string literal is created when the class\nis defined. Therefore any string interpolation inside the code will take\nplace at definition time.\n\nIn order to defer the code generation until the first object instantiation,\nthe function code can be given inside a block:\n\n```ruby\nclass Deferred \u003c Nodo::Core\n  function :now, \u003c\u003c~JS\n    () =\u003e { return #{Time.now.to_json}; }\n  JS\n\n  function :later do\n    \u003c\u003c~JS\n      () =\u003e { return #{Time.now.to_json}; }\n    JS\n  end\nend\n\ninstance = Deferred.new\nsleep 5\ninstance.now =\u003e \"2021-10-28 20:30:00 +0200\"\ninstance.later =\u003e \"2021-10-28 20:30:05 +0200\"\n```\n\nThe block will be invoked when the first instance is created. As with deferred\nscripts, it will only be invoked once.\n\n### Limiting function execution time\n\nThe default timeout for a single JS function call is 60 seconds and can be\nset globally:\n\n```ruby\nNodo.timeout = 5\n```\n\nIf the execution of a single function call exceeds the timeout, `Nodo::TimeoutError`\nis raised.\n\nThe timeout can also be set on a per-function basis:\n\n```ruby\nclass Foo \u003c Nodo::Core\n  function :sleep, timeout: 1, code: \u003c\u003c~'JS'\n    async (sec) =\u003e await new Promise(resolve =\u003e setTimeout(resolve, sec * 1000))\n  JS\nend\n\nFoo.new.sleep(2)\n=\u003e  Nodo::TimeoutError raised\n```\n\n### Setting NODE_PATH\n\nBy default, `./node_modules` is used as the `NODE_PATH`.\n\nTo set a custom path:\n```ruby\nNodo.modules_root = 'path/to/node_modules'\n```\n\nAlso see: [Clean your Rails root](#Clean-your-Rails-root)\n\n### Logging\n\nBy default, JS errors will be logged to `STDOUT`.\n\nTo set a custom logger:\n\n```ruby\nNodo.logger = Logger.new('nodo.log')\n```\n\nIn Rails applications, `Rails.logger` will automatically be set.\n\n### Debugging\n\nTo get verbose debug output, set\n\n```ruby\nNodo.debug = true\n```\n\nbefore instantiating any worker instances. The debug mode will be active during\nthe current process run.\n\nTo print a debug message from JS code:\n\n```js\nnodo.debug(\"Debug message\");\n```\n\n### Evaluation\n\nWhile `Nodo` is mainly function-based, it is possible to evaluate JS code in the\ncontext of the defined object.\n\n```ruby\nfoo = Foo.new.evaluate(\"3 + 5\")\n=\u003e 8\n```\n\nEvaluated code can access functions, required dependencies and constants:\n\n```ruby\nclass Foo \u003c Nodo::Core\n  const :BAR, 'bar'\n  require :uuid\n  function :hello, code: '() =\u003e \"world\"'\nend\n\nfoo = Foo.new\n\nfoo.evaluate('BAR')\n=\u003e \"bar\"\n\nfoo.evaluate('uuid.v4()')\n=\u003e \"f258bef3-0d6f-4566-ad39-d8dec973ef6b\"\n\nfoo.evaluate('hello()')\n=\u003e \"world\"\n```\n\nVariables defined by evaluation are local to the current instance:\n\n```ruby\none = Foo.new\none.evaluate('a = 1')\ntwo = Foo.new\ntwo.evaluate('a = 2')\none.evaluate('a') =\u003e 1\ntwo.evaluate('a') =\u003e 2\n```\n\n⚠️ Evaluation comes with the usual caveats:\n\n- Avoid modifying any of your predefined identifiers. Remember that in JS,\n  as in Ruby, constants are not necessarily constant.\n- Never evaluate any code which includes un-checked user data. The Node.js process\n  has full read/write access to your filesystem! 💥\n\n\n## Clean your Rails root\n\nFor Rails applications, Nodo enables you to move `node_modules`, `package.json` and\n`yarn.lock` into your application's `vendor` folder by setting the `NODE_PATH` in\nan initializer:\n\n```ruby\n# config/initializers/nodo.rb\nNodo.modules_root = Rails.root.join('vendor', 'node_modules')\n```\n\nThe rationale for this is NPM modules being external vendor dependencies, which\nshould not clutter the application root directory.\n\nWith this new default, all `yarn` operations should be done after `cd`ing to `vendor`.\n\nThis repo provides an [adapted version](https://github.com/mtgrosser/nodo/blob/master/install/yarn.rake)\nof the `yarn:install` rake task which will automatically take care of the vendored module location.\n\n\n## Working with web mocking frameworks like WebMock\n\nNodo uses HTTP via UNIX sockets to connect to its Node process. This may lead to\nconflicts during tests when using `WebMock` or other tools which interfere with\n`Net::HTTP`. In order to work with WebMock, you need to enable its `allow_localhost`\noption:\n\n```ruby\nWebMock.disable_net_connect!(allow_localhost: true)\n```\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtgrosser%2Fnodo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmtgrosser%2Fnodo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtgrosser%2Fnodo/lists"}