{"id":49664430,"url":"https://github.com/benoitc/erlang-quickjs","last_synced_at":"2026-05-30T12:30:38.533Z","repository":{"id":355111748,"uuid":"1226818524","full_name":"benoitc/erlang-quickjs","owner":"benoitc","description":"QuickJS JavaScript engine for Erlang (powered by quickjs-ng). API-compatible replacement for erlang-duktape.","archived":false,"fork":false,"pushed_at":"2026-05-01T22:08:03.000Z","size":60,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-23T05:05:02.426Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Erlang","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/benoitc.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-01T21:44:25.000Z","updated_at":"2026-05-05T02:40:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/benoitc/erlang-quickjs","commit_stats":null,"previous_names":["benoitc/erlang-quickjs"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/benoitc/erlang-quickjs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang-quickjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang-quickjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang-quickjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang-quickjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benoitc","download_url":"https://codeload.github.com/benoitc/erlang-quickjs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang-quickjs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33692997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2026-05-06T15:00:29.915Z","updated_at":"2026-05-30T12:30:38.527Z","avatar_url":"https://github.com/benoitc.png","language":"Erlang","funding_links":[],"categories":["Erlang"],"sub_categories":[],"readme":"# erlang-quickjs\n\n[![CI](https://github.com/benoitc/erlang-quickjs/actions/workflows/ci.yml/badge.svg)](https://github.com/benoitc/erlang-quickjs/actions/workflows/ci.yml)\n[![Hex.pm](https://img.shields.io/hexpm/v/quickjs.svg)](https://hex.pm/packages/quickjs)\n\nQuickJS JavaScript engine for Erlang.\n\nThis library embeds the [QuickJS-NG](https://github.com/quickjs-ng/quickjs) JavaScript engine (v0.14.0) as an Erlang NIF, allowing you to evaluate JavaScript code directly from Erlang.\n\n## Features\n\n- Execute JavaScript code from Erlang\n- Bidirectional type conversion between Erlang and JavaScript\n- Multiple isolated JavaScript contexts\n- CommonJS module support\n- **Execution timeouts to prevent infinite loops**\n- **Event framework for JS ↔ Erlang communication**\n- **Register Erlang functions callable from JavaScript**\n- **console.log/info/warn/error/debug support**\n- **Memory metrics and manual garbage collection**\n- Thread-safe with automatic resource cleanup\n- No external dependencies - QuickJS is embedded\n\n## Requirements\n\n- Erlang/OTP 24 or later\n- CMake 3.10 or later\n- C compiler (gcc, clang, or MSVC)\n\n## Installation\n\nAdd to your `rebar.config`:\n\n```erlang\n{deps, [\n    {quickjs, {git, \"https://github.com/benoitc/erlang-quickjs.git\", {branch, \"main\"}}}\n]}.\n```\n\nThen run:\n\n```bash\nrebar3 compile\n```\n\n## Quick Start\n\n```erlang\n%% Create a JavaScript context\n{ok, Ctx} = quickjs:new_context().\n\n%% Evaluate JavaScript code\n{ok, 42} = quickjs:eval(Ctx, \u003c\u003c\"21 * 2\"\u003e\u003e).\n{ok, \u003c\u003c\"hello\"\u003e\u003e} = quickjs:eval(Ctx, \u003c\u003c\"'hello'\"\u003e\u003e).\n\n%% Evaluate with variable bindings\n{ok, 30} = quickjs:eval(Ctx, \u003c\u003c\"x * y\"\u003e\u003e, #{x =\u003e 5, y =\u003e 6}).\n\n%% Define and call functions\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"function add(a, b) { return a + b; }\"\u003e\u003e).\n{ok, 7} = quickjs:call(Ctx, add, [3, 4]).\n\n%% CommonJS modules\nok = quickjs:register_module(Ctx, \u003c\u003c\"utils\"\u003e\u003e, \u003c\u003c\"\n    exports.greet = function(name) {\n        return 'Hello, ' + name + '!';\n    };\n\"\u003e\u003e).\n{ok, \u003c\u003c\"Hello, World!\"\u003e\u003e} = quickjs:eval(Ctx, \u003c\u003c\"require('utils').greet('World')\"\u003e\u003e).\n```\n\n## API Reference\n\n- [Context Management](#context-management) | [Evaluation](#evaluation) | [Function Calls](#function-calls) | [CommonJS Modules](#commonjs-modules)\n- [Event Framework](#event-framework) | [Erlang Functions](#erlang-functions) | [CBOR Encoding/Decoding](#cbor-encodingdecoding)\n- [Utility](#utility) | [Metrics](#metrics)\n\n### Context Management\n\n#### `new_context() -\u003e {ok, context()} | {error, term()}`\n\nCreate a new JavaScript context. Contexts are isolated - variables and functions defined in one context are not visible in others.\n\nContexts are automatically cleaned up when garbage collected, but you can also explicitly destroy them with `destroy_context/1`.\n\n#### `new_context(Opts) -\u003e {ok, context()} | {error, term()}`\n\nCreate a new JavaScript context with options.\n\nOptions:\n- `handler =\u003e pid()`: Process to receive events from JavaScript. The handler will receive messages of the form `{quickjs, Type, Data}` where Type is a binary (e.g., `\u003c\u003c\"custom\"\u003e\u003e`) or atom (for log events: `log`) and Data is the event payload.\n\n```erlang\n{ok, Ctx} = quickjs:new_context(#{handler =\u003e self()}),\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"console.log('hello')\"\u003e\u003e),\nreceive\n    {quickjs, log, #{level := info, message := \u003c\u003c\"hello\"\u003e\u003e}} -\u003e\n        io:format(\"Got log message~n\")\nend.\n```\n\n#### `destroy_context(Ctx) -\u003e ok | {error, term()}`\n\nExplicitly destroy a JavaScript context. This is optional - contexts are automatically cleaned up on garbage collection. Calling destroy on an already-destroyed context is safe (idempotent).\n\n### Evaluation\n\n#### `eval(Ctx, Code) -\u003e {ok, Value} | {error, term()}`\n\nEvaluate JavaScript code and return the result of the last expression. Uses default timeout of 5000ms.\n\n```erlang\n{ok, 3} = quickjs:eval(Ctx, \u003c\u003c\"1 + 2\"\u003e\u003e).\n{ok, \u003c\u003c\"hello\"\u003e\u003e} = quickjs:eval(Ctx, \u003c\u003c\"'hello'\"\u003e\u003e).\n{error, {js_error, _}} = quickjs:eval(Ctx, \u003c\u003c\"throw 'oops'\"\u003e\u003e).\n```\n\n#### `eval(Ctx, Code, Timeout) -\u003e {ok, Value} | {error, term()}`\n#### `eval(Ctx, Code, Bindings) -\u003e {ok, Value} | {error, term()}`\n\nWith an integer or `infinity` as third argument, sets execution timeout in milliseconds. With a map, sets variable bindings.\n\n```erlang\n%% With timeout (100ms)\n{error, timeout} = quickjs:eval(Ctx, \u003c\u003c\"while(true){}\"\u003e\u003e, 100).\n{ok, 42} = quickjs:eval(Ctx, \u003c\u003c\"21 * 2\"\u003e\u003e, 1000).\n{ok, 42} = quickjs:eval(Ctx, \u003c\u003c\"21 * 2\"\u003e\u003e, infinity).  %% No timeout\n\n%% With bindings (uses default 5000ms timeout)\n{ok, 30} = quickjs:eval(Ctx, \u003c\u003c\"x * y\"\u003e\u003e, #{x =\u003e 5, y =\u003e 6}).\n```\n\n#### `eval(Ctx, Code, Bindings, Timeout) -\u003e {ok, Value} | {error, term()}`\n\nEvaluate with both variable bindings and explicit timeout.\n\n```erlang\n{ok, 30} = quickjs:eval(Ctx, \u003c\u003c\"x * y\"\u003e\u003e, #{x =\u003e 5, y =\u003e 6}, 1000).\n{error, timeout} = quickjs:eval(Ctx, \u003c\u003c\"while(x){}\"\u003e\u003e, #{x =\u003e true}, 100).\n```\n\n### Function Calls\n\n#### `call(Ctx, FunctionName) -\u003e {ok, Value} | {error, term()}`\n\nCall a global JavaScript function with no arguments. Uses default timeout of 5000ms.\n\n```erlang\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"function getTime() { return Date.now(); }\"\u003e\u003e).\n{ok, Timestamp} = quickjs:call(Ctx, \u003c\u003c\"getTime\"\u003e\u003e).\n```\n\n#### `call(Ctx, FunctionName, Timeout) -\u003e {ok, Value} | {error, term()}`\n#### `call(Ctx, FunctionName, Args) -\u003e {ok, Value} | {error, term()}`\n\nWith an integer or `infinity` as third argument, sets execution timeout. With a list, passes arguments to the function.\n\n```erlang\n%% With timeout\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"function slow() { while(true){} }\"\u003e\u003e).\n{error, timeout} = quickjs:call(Ctx, slow, 100).\n\n%% With args (uses default 5000ms timeout)\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"function add(a, b) { return a + b; }\"\u003e\u003e).\n{ok, 7} = quickjs:call(Ctx, \u003c\u003c\"add\"\u003e\u003e, [3, 4]).\n{ok, 7} = quickjs:call(Ctx, add, [3, 4]).\n```\n\n#### `call(Ctx, FunctionName, Args, Timeout) -\u003e {ok, Value} | {error, term()}`\n\nCall a function with both arguments and explicit timeout.\n\n```erlang\n{ok, 7} = quickjs:call(Ctx, add, [3, 4], 1000).\n{ok, 7} = quickjs:call(Ctx, add, [3, 4], infinity).  %% No timeout\n```\n\n### CommonJS Modules\n\n#### `register_module(Ctx, ModuleId, Source) -\u003e ok | {error, term()}`\n\nRegister a CommonJS module with source code. The module can then be loaded with `require/2` or via `require()` in JavaScript.\n\n```erlang\nok = quickjs:register_module(Ctx, \u003c\u003c\"math\"\u003e\u003e, \u003c\u003c\"\n    exports.add = function(a, b) { return a + b; };\n    exports.multiply = function(a, b) { return a * b; };\n\"\u003e\u003e).\n```\n\n#### `require(Ctx, ModuleId) -\u003e {ok, Exports} | {error, term()}`\n\nLoad a CommonJS module and return its exports. Modules are cached - subsequent requires return the same exports object.\n\n```erlang\n{ok, Exports} = quickjs:require(Ctx, \u003c\u003c\"math\"\u003e\u003e).\n```\n\n### Event Framework\n\nThe event framework enables bidirectional communication between JavaScript and Erlang.\n\n#### `send(Ctx, Event, Data) -\u003e {ok, Value} | ok | {error, term()}`\n\nSend data to a registered JavaScript callback. If JavaScript code has registered a callback using `Erlang.on(event, fn)`, this function will call that callback with the provided data.\n\nReturns `{ok, Result}` where Result is the return value of the callback, or `ok` if no callback is registered for the event.\n\n```erlang\n{ok, Ctx} = quickjs:new_context(),\n%% JavaScript registers a callback\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"\n    var received = null;\n    Erlang.on('data', function(d) { received = d; return 'got it'; });\n\"\u003e\u003e),\n%% Erlang sends data to the callback\n{ok, \u003c\u003c\"got it\"\u003e\u003e} = quickjs:send(Ctx, data, #{value =\u003e 42}),\n{ok, #{\u003c\u003c\"value\"\u003e\u003e := 42}} = quickjs:eval(Ctx, \u003c\u003c\"received\"\u003e\u003e).\n```\n\n#### JavaScript API\n\nThe `Erlang` global object provides the following methods:\n\n**`Erlang.emit(type, data)`** - Send an event to the Erlang handler process.\n\n```javascript\nErlang.emit('custom_event', {key: 'value', count: 42});\n```\n\nThe handler receives: `{quickjs, \u003c\u003c\"custom_event\"\u003e\u003e, #{\u003c\u003c\"key\"\u003e\u003e =\u003e \u003c\u003c\"value\"\u003e\u003e, \u003c\u003c\"count\"\u003e\u003e =\u003e 42}}`\n\n**`Erlang.log(level, ...args)`** - Send a log message to the Erlang handler.\n\n```javascript\nErlang.log('info', 'User logged in:', userId);\nErlang.log('warning', 'Rate limit exceeded');\nErlang.log('error', 'Connection failed:', error);\nErlang.log('debug', 'Request details:', request);\n```\n\nThe handler receives: `{quickjs, log, #{level =\u003e info, message =\u003e \u003c\u003c\"User logged in: 123\"\u003e\u003e}}`\n\n**`Erlang.on(event, callback)`** - Register a callback for events from Erlang.\n\n```javascript\nErlang.on('config_update', function(config) {\n    applyConfig(config);\n    return 'applied';\n});\n```\n\n**`Erlang.off(event)`** - Unregister a callback.\n\n```javascript\nErlang.off('config_update');\n```\n\n#### Console Object\n\nA standard `console` object is available that wraps `Erlang.log`:\n\n```javascript\nconsole.log('Hello, world!');      // level: info\nconsole.info('Information');        // level: info\nconsole.warn('Warning message');    // level: warning\nconsole.error('Error occurred');    // level: error\nconsole.debug('Debug info');        // level: debug\n```\n\n#### Complete Example\n\n```erlang\n%% Create context with event handler\n{ok, Ctx} = quickjs:new_context(#{handler =\u003e self()}),\n\n%% Set up JavaScript callback\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"\n    var messages = [];\n    Erlang.on('message', function(msg) {\n        messages.push(msg);\n        console.log('Received:', msg.text);\n        return messages.length;\n    });\n\"\u003e\u003e),\n\n%% Send from Erlang\n{ok, 1} = quickjs:send(Ctx, message, #{text =\u003e \u003c\u003c\"Hello\"\u003e\u003e}),\n{ok, 2} = quickjs:send(Ctx, message, #{text =\u003e \u003c\u003c\"World\"\u003e\u003e}),\n\n%% Receive console.log events\nreceive {quickjs, log, #{message := \u003c\u003c\"Received: Hello\"\u003e\u003e}} -\u003e ok end,\nreceive {quickjs, log, #{message := \u003c\u003c\"Received: World\"\u003e\u003e}} -\u003e ok end,\n\n%% Verify messages were stored\n{ok, [#{\u003c\u003c\"text\"\u003e\u003e := \u003c\u003c\"Hello\"\u003e\u003e}, #{\u003c\u003c\"text\"\u003e\u003e := \u003c\u003c\"World\"\u003e\u003e}]} =\n    quickjs:eval(Ctx, \u003c\u003c\"messages\"\u003e\u003e).\n```\n\n### Erlang Functions\n\nRegister Erlang functions that can be called synchronously from JavaScript.\n\n#### `register_function(Ctx, Name, Fun) -\u003e ok | {error, term()}`\n\nRegister an Erlang function callable from JavaScript. The function receives a list of arguments passed from JavaScript.\n\nSupports both anonymous functions and `{Module, Function}` tuples. The function must accept a single argument (the list of JS arguments).\n\n```erlang\n{ok, Ctx} = quickjs:new_context(),\n\n%% Register with anonymous function\nok = quickjs:register_function(Ctx, greet, fun([Name]) -\u003e\n    \u003c\u003c\"Hello, \", Name/binary, \"!\"\u003e\u003e\nend),\n{ok, \u003c\u003c\"Hello, World!\"\u003e\u003e} = quickjs:eval(Ctx, \u003c\u003c\"greet('World')\"\u003e\u003e).\n\n%% Register with {Module, Function} tuple\nok = quickjs:register_function(Ctx, my_func, {my_module, my_function}).\n```\n\n**Multiple Arguments:**\n\n```erlang\nok = quickjs:register_function(Ctx, add, fun(Args) -\u003e\n    lists:sum(Args)\nend),\n{ok, 10} = quickjs:eval(Ctx, \u003c\u003c\"add(1, 2, 3, 4)\"\u003e\u003e).\n```\n\n**Nested Calls (Erlang functions calling each other):**\n\n```erlang\nok = quickjs:register_function(Ctx, double, fun([N]) -\u003e N * 2 end),\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"function quadruple(n) { return double(double(n)); }\"\u003e\u003e),\n{ok, 20} = quickjs:eval(Ctx, \u003c\u003c\"quadruple(5)\"\u003e\u003e).\n```\n\n**Error Handling:**\n\nErlang exceptions are converted to JavaScript errors:\n\n```erlang\nok = quickjs:register_function(Ctx, fail, fun(_) -\u003e\n    error(something_bad)\nend),\n%% JavaScript can catch the error\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"\n    try {\n        fail();\n    } catch (e) {\n        console.log('Caught:', e.message);\n    }\n\"\u003e\u003e).\n```\n\n**Note:** Registered functions are stored in the calling process's dictionary. The process that registers the function must also be the one that calls `eval/call`.\n\n### CBOR Encoding/Decoding\n\nQuickJS has built-in CBOR (Concise Binary Object Representation) support.\n\n#### `cbor_encode(Ctx, Value) -\u003e {ok, binary()} | {error, term()}`\n\nEncode an Erlang value to CBOR binary. The value is first converted to a JavaScript value, then encoded to CBOR.\n\n```erlang\n{ok, Ctx} = quickjs:new_context(),\n{ok, Bin} = quickjs:cbor_encode(Ctx, #{name =\u003e \u003c\u003c\"Alice\"\u003e\u003e, age =\u003e 30}).\n```\n\n#### `cbor_decode(Ctx, Binary) -\u003e {ok, Value} | {error, term()}`\n\nDecode a CBOR binary to an Erlang value. The CBOR is decoded to a JavaScript value, then converted to Erlang.\n\n```erlang\n{ok, Decoded} = quickjs:cbor_decode(Ctx, Bin),\n%% #{\u003c\u003c\"name\"\u003e\u003e =\u003e \u003c\u003c\"Alice\"\u003e\u003e, \u003c\u003c\"age\"\u003e\u003e =\u003e 30}\n```\n\nCBOR type mappings follow the same rules as regular Erlang ↔ JavaScript type conversions.\n\n### Utility\n\n#### `info() -\u003e {ok, string()}`\n\nGet NIF information. Used to verify the NIF is loaded correctly.\n\n### Metrics\n\n#### `get_memory_stats(Ctx) -\u003e {ok, Stats} | {error, term()}`\n\nGet memory statistics for a JavaScript context. Returns a map with:\n\n| Key | Description |\n|-----|-------------|\n| `heap_bytes` | Current allocated bytes in the QuickJS heap |\n| `heap_peak` | Peak memory usage since context creation |\n| `alloc_count` | Total number of allocations |\n| `realloc_count` | Total number of reallocations |\n| `free_count` | Total number of frees |\n| `gc_runs` | Number of garbage collection runs triggered |\n\n```erlang\n{ok, Ctx} = quickjs:new_context(),\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"var x = []; for(var i=0; i\u003c1000; i++) x.push(i);\"\u003e\u003e),\n{ok, Stats} = quickjs:get_memory_stats(Ctx),\nio:format(\"Heap: ~p bytes, Peak: ~p bytes~n\",\n          [maps:get(heap_bytes, Stats), maps:get(heap_peak, Stats)]).\n```\n\n#### `gc(Ctx) -\u003e ok | {error, term()}`\n\nTrigger garbage collection on a JavaScript context. Forces QuickJS's mark-and-sweep garbage collector to run.\n\n```erlang\n{ok, Ctx} = quickjs:new_context(),\n{ok, _} = quickjs:eval(Ctx, \u003c\u003c\"var x = {}; x = null;\"\u003e\u003e),\nok = quickjs:gc(Ctx),\n{ok, #{gc_runs := 1}} = quickjs:get_memory_stats(Ctx).\n```\n\n## Type Conversions\n\n### Erlang to JavaScript\n\n| Erlang | JavaScript |\n|--------|------------|\n| `integer()` | number |\n| `float()` | number |\n| `binary()` | string |\n| `true` | true |\n| `false` | false |\n| `null` | null |\n| `undefined` | undefined |\n| other atoms | string |\n| `list()` | array (or string if iolist) |\n| `map()` | object |\n| `tuple()` | array |\n\n### JavaScript to Erlang\n\n| JavaScript | Erlang |\n|------------|--------|\n| number (integer) | `integer()` |\n| number (float) | `float()` |\n| NaN | `nan` (atom) |\n| Infinity | `infinity` (atom) |\n| -Infinity | `neg_infinity` (atom) |\n| string | `binary()` |\n| true | `true` |\n| false | `false` |\n| null | `null` |\n| undefined | `undefined` |\n| array | `list()` |\n| object | `map()` |\n\n## Error Handling\n\nJavaScript errors are returned as `{error, {js_error, Message}}` where `Message` is a binary containing the error message and stack trace.\n\n```erlang\n{error, {js_error, \u003c\u003c\"ReferenceError: x is not defined\", _/binary\u003e\u003e}} =\n    quickjs:eval(Ctx, \u003c\u003c\"x + 1\"\u003e\u003e).\n```\n\nContexts remain usable after errors - you can continue to evaluate code in the same context.\n\n## Thread Safety\n\nAll context operations are thread-safe. Multiple Erlang processes can share a context, though operations are serialized via a mutex. For maximum parallelism, create separate contexts for concurrent workloads.\n\n## Resource Management\n\nContexts are managed as Erlang NIF resources with automatic cleanup:\n\n- Contexts are garbage collected when no Erlang process holds a reference\n- Multiple processes can share a context safely\n- Explicit `destroy_context/1` is optional but can be used for immediate cleanup\n- Reference counting ensures contexts are not destroyed while in use\n\n## Benchmarks\n\nApple M4 Pro, Erlang/OTP 28, quickjs-ng v0.14.0, 1000 iterations per benchmark after a 100-iteration warmup.\n\n### Core operations\n\n| Benchmark | Ops/sec | Mean (ms) | P95 (ms) | P99 (ms) |\n|---|--:|--:|--:|--:|\n| eval_simple | 1,445 | 0.692 | 0.741 | 0.809 |\n| eval_complex | 1,412 | 0.708 | 0.756 | 0.810 |\n| eval_bindings_small (5 vars) | 1,445 | 0.692 | 0.757 | 0.816 |\n| eval_bindings_large (50 vars) | 1,279 | 0.782 | 0.846 | 0.939 |\n| call_no_args | 1,424 | 0.702 | 0.757 | 0.816 |\n| call_with_args (5 args) | 1,429 | 0.700 | 0.733 | 0.783 |\n| call_many_args (20 args) | 1,391 | 0.719 | 0.754 | 0.804 |\n| type_convert_simple | 1,450 | 0.689 | 0.710 | 0.739 |\n| type_convert_array (1000 elem) | 1,403 | 0.713 | 0.741 | 0.773 |\n| type_convert_nested | 1,388 | 0.721 | 0.781 | 0.864 |\n| context_create | 1,442 | 0.694 | 0.745 | 0.810 |\n| module_require_cached | 1,399 | 0.715 | 0.761 | 0.802 |\n\n### Erlang function registration\n\n| Benchmark | Ops/sec | Mean (ms) | P95 (ms) | P99 (ms) |\n|---|--:|--:|--:|--:|\n| register_function_simple | 1,402 | 0.713 | 0.752 | 0.845 |\n| register_function_complex_args | 1,373 | 0.728 | 0.773 | 1.180 |\n| register_function_nested (5 calls) | 1,350 | 0.741 | 0.790 | 0.854 |\n| register_function_many_calls (10) | 11,969 | 0.835 | 0.926 | 1.163 |\n\n### Event framework\n\n| Benchmark | Ops/sec | Mean (ms) | P95 (ms) | P99 (ms) |\n|---|--:|--:|--:|--:|\n| event_emit | 1,247 | 0.802 | 0.860 | 0.921 |\n| event_send | 1,424 | 0.702 | 0.738 | 0.786 |\n| console_log | 1,242 | 0.805 | 0.856 | 0.914 |\n\n### CBOR\n\n| Benchmark | Ops/sec | Mean (ms) | P95 (ms) | P99 (ms) |\n|---|--:|--:|--:|--:|\n| cbor_encode_simple | 1,353 | 0.739 | 0.783 | 0.837 |\n| cbor_encode_complex | 1,175 | 0.851 | 0.901 | 0.993 |\n| cbor_decode_simple | 1,187 | 0.843 | 1.029 | 1.100 |\n| cbor_roundtrip | 1,139 | 0.878 | 1.107 | 1.332 |\n\nThe CBOR codec is a JS shim, not a native C path; it lags duktape's built-in CBOR by roughly 35% on this micro-bench. The other operations sit ~15-25% behind duktape on per-call latency in exchange for full ES2023 support.\n\n### Concurrency\n\n| Benchmark | Ops/sec | Mean (ms) | P95 (ms) | P99 (ms) |\n|---|--:|--:|--:|--:|\n| concurrent_same_context (10 procs) | 98,859 | 1.012 | 1.234 | 1.334 |\n| concurrent_many_contexts (10 procs) | 22,321 | 4.480 | 4.763 | 5.111 |\n\nRun benchmarks yourself:\n\n```bash\n./run_bench.sh                  # all\n./run_bench.sh eval_simple      # one\n./run_bench.sh --smoke          # quick validation\n```\n\n## Security Considerations\n\nWhen running untrusted JavaScript code, be aware of these limitations:\n\n### Execution Timeouts\n\nAll `eval` and `call` functions support execution timeouts to prevent infinite loops:\n\n```erlang\n%% Default timeout is 5000ms\n{error, timeout} = quickjs:eval(Ctx, \u003c\u003c\"while(true){}\"\u003e\u003e, 100).\n\n%% Use infinity for no timeout (only for trusted code)\n{ok, _} = quickjs:eval(Ctx, Code, infinity).\n```\n\nAfter a timeout, the context remains valid and can be reused for subsequent calls.\n\n### Memory Limits\n\nQuickJS does not have built-in memory limits. JavaScript code can allocate unbounded memory.\n\n**Recommendation**: For untrusted code, monitor memory usage via `get_memory_stats/1` and destroy contexts that exceed limits.\n\n### Event Types\n\nEvent types from `Erlang.emit()` are returned as binaries to prevent atom table exhaustion. Known log levels (`debug`, `info`, `warning`, `error`) remain atoms for ergonomics.\n\n## License\n\nThis project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.\n\nQuickJS-NG is licensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenoitc%2Ferlang-quickjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenoitc%2Ferlang-quickjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenoitc%2Ferlang-quickjs/lists"}