{"id":15801495,"url":"https://github.com/scriptedalchemy/quick-worker","last_synced_at":"2025-03-31T21:35:08.601Z","repository":{"id":210160175,"uuid":"725884146","full_name":"ScriptedAlchemy/quick-worker","owner":"ScriptedAlchemy","description":null,"archived":false,"fork":false,"pushed_at":"2023-12-15T05:27:05.000Z","size":11465,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-06T01:22:51.084Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ScriptedAlchemy.png","metadata":{"files":{"readme":"README.md","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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-01T04:19:21.000Z","updated_at":"2023-12-01T04:21:52.000Z","dependencies_parsed_at":"2024-10-26T07:59:09.071Z","dependency_job_id":"49100111-2d94-41f8-9d00-fb598542f2cb","html_url":"https://github.com/ScriptedAlchemy/quick-worker","commit_stats":null,"previous_names":["scriptedalchemy/quick-worker"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptedAlchemy%2Fquick-worker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptedAlchemy%2Fquick-worker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptedAlchemy%2Fquick-worker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptedAlchemy%2Fquick-worker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ScriptedAlchemy","download_url":"https://codeload.github.com/ScriptedAlchemy/quick-worker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246544718,"owners_count":20794555,"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":"2024-10-05T01:22:57.255Z","updated_at":"2025-03-31T21:35:08.570Z","avatar_url":"https://github.com/ScriptedAlchemy.png","language":"C","readme":"# quickjs-emscripten\n\nJavascript/Typescript bindings for [QuickJS, a modern Javascript interpreter written in\nC by Fabrice Bellard](https://bellard.org/quickjs/) compiled to WebAssembly.\n\n- Safely evaluate untrusted Javascript (up to ES2020).\n- Create and manipulate values inside the QuickJS runtime.\n- Expose host functions to the QuickJS runtime.\n\n```typescript\nimport { getQuickJS } from 'quickjs-emscripten'\n\nasync function main() {\n  const QuickJS = await getQuickJS()\n  const vm = QuickJS.createVm()\n\n  const world = vm.newString('world')\n  vm.setProp(vm.global, 'NAME', world)\n  world.dispose()\n\n  const result = vm.evalCode(`\"Hello \" + NAME + \"!\"`)\n  if (result.error) {\n    console.log('Execution failed:', vm.dump(result.error))\n    result.error.dispose()\n  } else {\n    console.log('Success:', vm.dump(result.value))\n    result.value.dispose()\n  }\n\n  vm.dispose()\n}\n\nmain()\n```\n\n## Usage\n\nInstall from `npm`: `npm install --save quickjs-emscripten` or `yarn add quickjs-emscripten`.\n\nThe root entrypoint of this library is the `getQuickJS` function, which returns\na promise that resolves to a [QuickJS singleton](doc/classes/quickjs.md) when\nthe Emscripten WASM module is ready.\n\nOnce `getQuickJS` has been awaited at least once, you also can use the `getQuickJSSync`\nfunction to directly access the singleton engine in your synchronous code.\n\n### Safely evaluate Javascript code\n\nSee [QuickJS.evalCode](https://github.com/justjake/quickjs-emscripten/blob/master/doc/classes/quickjs.md#evalcode)\n\n```typescript\nimport { getQuickJS, shouldInterruptAfterDeadline } from 'quickjs-emscripten'\n\ngetQuickJS().then(QuickJS =\u003e {\n  const result = QuickJS.evalCode('1 + 1', {\n    shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 1000),\n    memoryLimitBytes: 1024 * 1024,\n  })\n  console.log(result)\n})\n```\n\n### Interfacing with the interpreter\n\nYou can use [QuickJSVm](https://github.com/justjake/quickjs-emscripten/blob/master/doc/classes/quickjsvm.md)\nto build a scripting environment by modifying globals and exposing functions\ninto the QuickJS interpreter.\n\nEach `QuickJSVm` instance has its own environment, CPU limit, and memory\nlimit. See the documentation for details.\n\n```typescript\nconst vm = QuickJS.createVm()\nlet state = 0\n\nconst fnHandle = vm.newFunction('nextId', () =\u003e {\n  return vm.newNumber(++state)\n})\n\nvm.setProp(vm.global, 'nextId', fnHandle)\nfnHandle.dispose()\n\nconst nextId = vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`))\nconsole.log('vm result:', vm.getNumber(nextId), 'native state:', state)\n\nnextId.dispose()\nvm.dispose()\n```\n\n### Memory Management\n\nMany methods in this library return handles to memory allocated inside the\nWebAssembly heap. These types cannot be garbage-collected as usual in\nJavascript. Instead, you must manually manage their memory by calling a\n`.dispose()` method to free the underlying resources. Once a handle has been\ndisposed, it cannot be used anymore. Note that in the example above, we call\n`.dispose()` on each handle once it is no longer needed.\n\nCalling `QuickJSVm.dispose()` will throw a RuntimeError if you've forgotten to\ndispose any handles associated with that VM, so it's good practice to create a\nnew VM instance for each of your tests, and to call `vm.dispose()` at the end\nof every test.\n\n```typescript\nconst vm = QuickJS.createVm()\nconst numberHandle = vm.newNumber(42)\n// Note: numberHandle not disposed, so it leaks memory.\nvm.dispose()\n// throws RuntimeError: abort(Assertion failed: list_empty(\u0026rt-\u003egc_obj_list), at: quickjs/quickjs.c,1963,JS_FreeRuntime)\n```\n\nHere are some strategies to reduce the toil of calling `.dispose()` on each\nhandle you create:\n\n#### Scope\n\nA\n[`Scope`](https://github.com/justjake/quickjs-emscripten/blob/master/doc/classes/scope.md#class-scope)\ninstance manages a set of disposables and calls their `.dispose()`\nmethod in the reverse order in which they're added to the scope. Here's the\n\"Interfacing with the interpreter\" example re-written using `Scope`:\n\n```typescript\nScope.withScope(scope =\u003e {\n  const vm = scope.manage(QuickJS.createVm())\n  let state = 0\n\n  const fnHandle = scope.manage(\n    vm.newFunction('nextId', () =\u003e {\n      return vm.newNumber(++state)\n    })\n  )\n\n  vm.setProp(vm.global, 'nextId', fnHandle)\n\n  const nextId = scope.manage(vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`)))\n  console.log('vm result:', vm.getNumber(nextId), 'native state:', state)\n\n  // When the withScope block exits, it calls scope.dispose(), which in turn calls\n  // the .dispose() methods of all the disposables managed by the scope.\n})\n```\n\nYou can also create `Scope` instances with `new Scope()` if you want to manage\ncalling `scope.dispose()` yourself.\n\n#### `Lifetime.consume(fn)`\n\n[`Lifetime.consume`](https://github.com/justjake/quickjs-emscripten/blob/master/doc/classes/lifetime.md#consume)\nis sugar for the common pattern of using a handle and then\nimmediately disposing of it. `Lifetime.consume` takes a `map` function that\nproduces a result of any type. The `map` fuction is called with the handle,\nthen the handle is disposed, then the result is returned.\n\nHere's the \"Interfacing with interpreter\" example re-written using `.consume()`:\n\n```typescript\nconst vm = QuickJS.createVm()\nlet state = 0\n\nvm.newFunction('nextId', () =\u003e {\n  return vm.newNumber(++state)\n}).consume(fnHandle =\u003e vm.setProp(vm.global, 'nextId', fnHandle))\n\nvm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`)).consume(nextId =\u003e\n  console.log('vm result:', vm.getNumber(nextId), 'native state:', state)\n)\n\nvm.dispose()\n```\n\nGenerally working with `Scope` leads to more straight-forward code, but\n`Lifetime.consume` can be handy sugar as part of a method call chain.\n\n### More Documentation\n\n- [API Documentation](https://github.com/justjake/quickjs-emscripten/blob/master/doc/globals.md)\n- [Examples](https://github.com/justjake/quickjs-emscripten/blob/master/ts/quickjs.test.ts)\n\n## Background\n\nThis was inspired by seeing https://github.com/maple3142/duktape-eval\n[on Hacker News](https://news.ycombinator.com/item?id=21946565) and Figma's\nblogposts about using building a Javascript plugin runtime:\n\n- [How Figma built the Figma plugin system](https://www.figma.com/blog/how-we-built-the-figma-plugin-system/): Describes the LowLevelJavascriptVm interface.\n- [An update on plugin security](https://www.figma.com/blog/an-update-on-plugin-security/): Figma switches to QuickJS.\n\n## Status \u0026 TODOs\n\nBoth the original project quickjs and this project are still in the early stage\nof development.\nThere [are tests](https://github.com/justjake/quickjs-emscripten/blob/master/ts/quickjs.test.ts), but I haven't built anything\non top of this. Please use this project carefully in a production\nenvironment.\n\nBecause the version number of this project is below `1.0.0`, expect occasional\nbreaking API changes.\n\nIdeas for future work:\n\n- quickjs-emscripten only exposes a small subset of the QuickJS APIs. Add more QuickJS bindings!\n  - Expose tools for object and array iteration and creation.\n  - Stretch goals: class support, an event emitter bridge implementation\n- Higher-level abstractions for translating values into (and out of) QuickJS.\n- Remove the singleton limitations. Each QuickJS class instance could create\n  its own copy of the emscripten module.\n- Run quickjs-emscripten inside quickjs-emscripten.\n- Remove the `LowLevelJavascriptVm` interface and definition. Those types\n  provide no value, since there is no other implementations, and complicate the\n  types and documentation for quickjs-emscripten.\n- Improve our testing strategy by running the tests with each of the Emscripten santizers, as well as with the SAFE_HEAP. This should catch more bugs in the C code.\n  [See the Emscripten docs for more details](https://emscripten.org/docs/debugging/Sanitizers.html#comparison-to-safe-heap)\n\n## Related\n\n- Duktape wrapped in Wasm: https://github.com/maple3142/duktape-eval/blob/master/src/Makefile\n- QuickJS wrapped in C++: https://github.com/ftk/quickjspp\n\n## Developing\n\nThis library is implemented in two languages: C (compiled to WASM with\nEmscripten), and Typescript.\n\n### The C parts\n\nThe ./c directory contains C code that wraps the QuickJS C library (in ./quickjs).\nPublic functions (those starting with `QTS_`) in ./c/interface.c are\nautomatically exported to native code (via a generated header) and to\nTypescript (via a generated FFI class). See ./generate.ts for how this works.\n\nThe C code builds as both with `emscripten` (using `emcc`), to produce WASM (or\nASM.js) and with `clang`. Build outputs are checked in, so\nIntermediate object files from QuickJS end up in ./build/quickjs/{wasm,native}.\n\nThis project uses `emscripten 1.39.19`. The install should be handled automatically\nif you're working from Linux or OSX (if using Windows, the best is to use WSL to work\non this repository). If everything is right, running `yarn embin emcc -v` should print\nsomething like this:\n\n```\nemcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 1.39.18\nclang version 11.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 613c4a87ba9bb39d1927402f4dd4c1ef1f9a02f7)\n```\n\nRelated NPM scripts:\n\n- `yarn update-quickjs` will sync the ./quickjs folder with a\n  github repo tracking the upstream QuickJS.\n- `yarn make-debug` will rebuild C outputs into ./build/wrapper\n- `yarn run-n` builds and runs ./c/test.c\n\n### The Typescript parts\n\nThe ./ts directory contains Typescript types and wraps the generated Emscripten\nFFI in a more usable interface.\n\nYou'll need `node` and `npm` or `yarn`. Install dependencies with `npm install`\nor `yarn install`.\n\n- `yarn build` produces ./dist.\n- `yarn test` runs the tests.\n- `yarn test --watch` watches for changes and re-runs the tests.\n\n### Yarn updates\n\nJust run `yarn set version from sources` to upgrade the Yarn release.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscriptedalchemy%2Fquick-worker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscriptedalchemy%2Fquick-worker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscriptedalchemy%2Fquick-worker/lists"}