Initial commit - Dutchie dispensary scraper

This commit is contained in:
Kelly
2025-11-28 19:45:44 -07:00
commit 5757a8e9bd
23375 changed files with 3788799 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
MIT License
quickjs-emscripten copyright (c) 2019 Jake Teton-Landis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,597 @@
# quickjs-emscripten
Javascript/Typescript bindings for QuickJS, a modern Javascript interpreter,
compiled to WebAssembly.
- Safely evaluate untrusted Javascript (up to ES2020).
- Create and manipulate values inside the QuickJS runtime ([more][values]).
- Expose host functions to the QuickJS runtime ([more][functions]).
- Execute synchronous code that uses asynchronous functions, with [asyncify][asyncify].
[Github] | [NPM] | [API Documentation][api] | [Examples][tests]
```typescript
import { getQuickJS } from "quickjs-emscripten"
async function main() {
const QuickJS = await getQuickJS()
const vm = QuickJS.newContext()
const world = vm.newString("world")
vm.setProp(vm.global, "NAME", world)
world.dispose()
const result = vm.evalCode(`"Hello " + NAME + "!"`)
if (result.error) {
console.log("Execution failed:", vm.dump(result.error))
result.error.dispose()
} else {
console.log("Success:", vm.dump(result.value))
result.value.dispose()
}
vm.dispose()
}
main()
```
[github]: https://github.com/justjake/quickjs-emscripten
[npm]: https://www.npmjs.com/package/quickjs-emscripten
[api]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md
[tests]: https://github.com/justjake/quickjs-emscripten/blob/main/ts/quickjs.test.ts
[values]: #interfacing-with-the-interpreter
[asyncify]: #asyncify
[functions]: #exposing-apis
## Usage
Install from `npm`: `npm install --save quickjs-emscripten` or `yarn add quickjs-emscripten`.
The root entrypoint of this library is the `getQuickJS` function, which returns
a promise that resolves to a [QuickJS singleton](./doc/classes/quickjs.md) when
the QuickJS WASM module is ready.
Once `getQuickJS` has been awaited at least once, you also can use the `getQuickJSSync`
function to directly access the singleton engine in your synchronous code.
### Safely evaluate Javascript code
See [QuickJS.evalCode](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/quickjs.md#evalcode)
```typescript
import { getQuickJS, shouldInterruptAfterDeadline } from "quickjs-emscripten"
getQuickJS().then((QuickJS) => {
const result = QuickJS.evalCode("1 + 1", {
shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 1000),
memoryLimitBytes: 1024 * 1024,
})
console.log(result)
})
```
### Interfacing with the interpreter
You can use [QuickJSContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/QuickJSContext.md)
to build a scripting environment by modifying globals and exposing functions
into the QuickJS interpreter.
Each `QuickJSContext` instance has its own environment -- globals, built-in
classes -- and actions from one context won't leak into other contexts or
runtimes (with one exception, see [Asyncify][asyncify]).
Every context is created inside a
[QuickJSRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/QuickJSRuntime.md).
A runtime represents a Javascript heap, and you can even share values between
contexts in the same runtime.
```typescript
const vm = QuickJS.newContext()
let state = 0
const fnHandle = vm.newFunction("nextId", () => {
return vm.newNumber(++state)
})
vm.setProp(vm.global, "nextId", fnHandle)
fnHandle.dispose()
const nextId = vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`))
console.log("vm result:", vm.getNumber(nextId), "native state:", state)
nextId.dispose()
vm.dispose()
```
When you create a context from a top-level API like in the example above,
instead of by calling `runtime.newContext()`, a runtime is automatically created
for the lifetime of the context, and disposed of when you dispose the context.
#### Runtime
The runtime has APIs for CPU and memory limits that apply to all contexts within
the runtime in aggregate. You can also use the runtime to configure EcmaScript
module loading.
```typescript
const runtime = QuickJS.newRuntime()
// "Should be enough for everyone" -- attributed to B. Gates
runtime.setMemoryLimit(1024 * 640)
// Limit stack size
runtime.setMaxStackSize(1024 * 320)
// Interrupt computation after 1024 calls to the interrupt handler
let interruptCycles = 0
runtime.setInterruptHandler(() => ++interruptCycles > 1024)
// Toy module system that always returns the module name
// as the default export
runtime.setModuleLoader((moduleName) => `export default '${moduleName}'`)
const context = runtime.newContext()
const ok = context.evalCode(`
import fooName from './foo.js'
globalThis.result = fooName
`)
context.unwrapResult(ok).dispose()
// logs "foo.js"
console.log(context.getProp(context.global, "result").consume(context.dump))
context.dispose()
runtime.dispose()
```
### Memory Management
Many methods in this library return handles to memory allocated inside the
WebAssembly heap. These types cannot be garbage-collected as usual in
Javascript. Instead, you must manually manage their memory by calling a
`.dispose()` method to free the underlying resources. Once a handle has been
disposed, it cannot be used anymore. Note that in the example above, we call
`.dispose()` on each handle once it is no longer needed.
Calling `QuickJSContext.dispose()` will throw a RuntimeError if you've forgotten to
dispose any handles associated with that VM, so it's good practice to create a
new VM instance for each of your tests, and to call `vm.dispose()` at the end
of every test.
```typescript
const vm = QuickJS.newContext()
const numberHandle = vm.newNumber(42)
// Note: numberHandle not disposed, so it leaks memory.
vm.dispose()
// throws RuntimeError: abort(Assertion failed: list_empty(&rt->gc_obj_list), at: quickjs/quickjs.c,1963,JS_FreeRuntime)
```
Here are some strategies to reduce the toil of calling `.dispose()` on each
handle you create:
#### Scope
A
[`Scope`](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/scope.md#class-scope)
instance manages a set of disposables and calls their `.dispose()`
method in the reverse order in which they're added to the scope. Here's the
"Interfacing with the interpreter" example re-written using `Scope`:
```typescript
Scope.withScope((scope) => {
const vm = scope.manage(QuickJS.newContext())
let state = 0
const fnHandle = scope.manage(
vm.newFunction("nextId", () => {
return vm.newNumber(++state)
})
)
vm.setProp(vm.global, "nextId", fnHandle)
const nextId = scope.manage(vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`)))
console.log("vm result:", vm.getNumber(nextId), "native state:", state)
// When the withScope block exits, it calls scope.dispose(), which in turn calls
// the .dispose() methods of all the disposables managed by the scope.
})
```
You can also create `Scope` instances with `new Scope()` if you want to manage
calling `scope.dispose()` yourself.
#### `Lifetime.consume(fn)`
[`Lifetime.consume`](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/lifetime.md#consume)
is sugar for the common pattern of using a handle and then
immediately disposing of it. `Lifetime.consume` takes a `map` function that
produces a result of any type. The `map` fuction is called with the handle,
then the handle is disposed, then the result is returned.
Here's the "Interfacing with interpreter" example re-written using `.consume()`:
```typescript
const vm = QuickJS.newContext()
let state = 0
vm.newFunction("nextId", () => {
return vm.newNumber(++state)
}).consume((fnHandle) => vm.setProp(vm.global, "nextId", fnHandle))
vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`)).consume((nextId) =>
console.log("vm result:", vm.getNumber(nextId), "native state:", state)
)
vm.dispose()
```
Generally working with `Scope` leads to more straight-forward code, but
`Lifetime.consume` can be handy sugar as part of a method call chain.
### Exposing APIs
To add APIs inside the QuickJS environment, you'll need to create objects to
define the shape of your API, and add properties and functions to those objects
to allow code inside QuickJS to call code on the host.
By default, no host functionality is exposed to code running inside QuickJS.
```typescript
const vm = QuickJS.newContext()
// `console.log`
const logHandle = vm.newFunction("log", (...args) => {
const nativeArgs = args.map(vm.dump)
console.log("QuickJS:", ...nativeArgs)
})
// Partially implement `console` object
const consoleHandle = vm.newObject()
vm.setProp(consoleHandle, "log", logHandle)
vm.setProp(vm.global, "console", consoleHandle)
consoleHandle.dispose()
logHandle.dispose()
vm.unwrapResult(vm.evalCode(`console.log("Hello from QuickJS!")`)).dispose()
```
#### Promises
To expose an asynchronous function that _returns a promise_ to callers within
QuickJS, your function can return the handle of a `QuickJSDeferredPromise`
created via `context.newPromise()`.
When you resolve a `QuickJSDeferredPromise` -- and generally whenever async
behavior completes for the VM -- pending listeners inside QuickJS may not
execute immediately. Your code needs to explicitly call
`runtime.executePendingJobs()` to resume execution inside QuickJS. This API
gives your code maximum control to _schedule_ when QuickJS will block the host's
event loop by resuming execution.
To work with QuickJS handles that contain a promise inside the environment, you
can convert the QuickJSHandle into a native promise using
`context.resolvePromise()`. Take care with this API to avoid 'deadlocks' where
the host awaits a guest promise, but the guest cannot make progress until the
host calls `runtime.executePendingJobs()`. The simplest way to avoid this kind
of deadlock is to always schedule `executePendingJobs` after any promise is
settled.
```typescript
const vm = QuickJS.newContext()
const fakeFileSystem = new Map([["example.txt", "Example file content"]])
// Function that simulates reading data asynchronously
const readFileHandle = vm.newFunction("readFile", (pathHandle) => {
const path = vm.getString(pathHandle)
const promise = vm.newPromise()
setTimeout(() => {
const content = fakeFileSystem.get(path)
promise.resolve(vm.newString(content || ""))
}, 100)
// IMPORTANT: Once you resolve an async action inside QuickJS,
// call runtime.executePendingJobs() to run any code that was
// waiting on the promise or callback.
promise.settled.then(vm.runtime.executePendingJobs)
return promise.handle
})
readFileHandle.consume((handle) => vm.setProp(vm.global, "readFile", handle))
// Evaluate code that uses `readFile`, which returns a promise
const result = vm.evalCode(`(async () => {
const content = await readFile('example.txt')
return content.toUpperCase()
})()`)
const promiseHandle = vm.unwrapResult(result)
// Convert the promise handle into a native promise and await it.
// If code like this deadlocks, make sure you are calling
// runtime.executePendingJobs appropriately.
const resolvedResult = await vm.resolvePromise(promiseHandle)
promiseHandle.dispose()
const resolvedHandle = vm.unwrapResult(resolvedResult)
console.log("Result:", vm.getString(resolvedHandle))
resolvedHandle.dispose()
```
#### Asyncify
Sometimes, we want to create a function that's synchronous from the perspective
of QuickJS, but prefer to implement that function _asynchronously_ in your host
code. The most obvious use-case is for EcmaScript module loading. The underlying
QuickJS C library expects the module loader function to return synchronously,
but loading data synchronously in the browser or server is somewhere between "a
bad idea" and "impossible". QuickJS also doesn't expose an API to "pause" the
execution of a runtime, and adding such an API is tricky due to the VM's
implementation.
As a work-around, we provide an alternate build of QuickJS processed by
Emscripten/Binaryen's [ASYNCIFY](https://emscripten.org/docs/porting/asyncify.html)
compiler transform. Here's how Emscripten's documentation describes Asyncify:
> Asyncify lets synchronous C or C++ code interact with asynchronous \[host] JavaScript. This allows things like:
>
> - A synchronous call in C that yields to the event loop, which allows browser events to be handled.
>
> - A synchronous call in C that waits for an asynchronous operation in \[host] JS to complete.
>
> Asyncify automatically transforms ... code into a form that can be paused and
> resumed ..., so that it is asynchronous (hence the name “Asyncify”) even though
> \[it is written] in a normal synchronous way.
This means we can suspend an _entire WebAssembly module_ (which could contain
multiple runtimes and contexts) while our host Javascript loads data
asynchronously, and then resume execution once the data load completes. This is
a very handy superpower, but it comes with a couple of major limitations:
1. _An asyncified WebAssembly module can only suspend to wait for a single
asynchronous call at a time_. You may call back into a suspended WebAssembly
module eg. to create a QuickJS value to return a result, but the system will
crash if this call tries to suspend again. Take a look at Emscripten's documentation
on [reentrancy](https://emscripten.org/docs/porting/asyncify.html#reentrancy).
2. _Asyncified code is bigger and runs slower_. The asyncified build of
Quickjs-emscripten library is 1M, 2x larger than the 500K of the default
version. There may be room for further
[optimization](https://emscripten.org/docs/porting/asyncify.html#optimizing)
Of our build in the future.
To use asyncify features, use the following functions:
- [newAsyncRuntime][]: create a runtime inside a new WebAssembly module.
- [newAsyncContext][]: create runtime and context together inside a new
WebAssembly module.
- [newQuickJSAsyncWASMModule][]: create an empty WebAssembly module.
[newasyncruntime]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md#newasyncruntime
[newasynccontext]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md#newasynccontext
[newquickjsasyncwasmmodule]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md#newquickjsasyncwasmmodule
These functions are asynchronous because they always create a new underlying
WebAssembly module so that each instance can suspend and resume independently,
and instantiating a WebAssembly module is an async operation. This also adds
substantial overhead compared to creating a runtime or context inside an
existing module; if you only need to wait for a single async action at a time,
you can create a single top-level module and create runtimes or contexts inside
of it.
##### Async module loader
Here's an example of valuating a script that loads React asynchronously as an ES
module. In our example, we're loading from the filesystem for reproducibility,
but you can use this technique to load using `fetch`.
```typescript
const module = await newQuickJSAsyncWASMModule()
const runtime = module.newRuntime()
const path = await import("path")
const { promises: fs } = await import("fs")
const importsPath = path.join(__dirname, "../examples/imports") + "/"
// Module loaders can return promises.
// Execution will suspend until the promise resolves.
runtime.setModuleLoader((moduleName) => {
const modulePath = path.join(importsPath, moduleName)
if (!modulePath.startsWith(importsPath)) {
throw new Error("out of bounds")
}
console.log("loading", moduleName, "from", modulePath)
return fs.readFile(modulePath, "utf-8")
})
// evalCodeAsync is required when execution may suspend.
const context = runtime.newContext()
const result = await context.evalCodeAsync(`
import * as React from 'esm.sh/react@17'
import * as ReactDOMServer from 'esm.sh/react-dom@17/server'
const e = React.createElement
globalThis.html = ReactDOMServer.renderToStaticMarkup(
e('div', null, e('strong', null, 'Hello world!'))
)
`)
context.unwrapResult(result).dispose()
const html = context.getProp(context.global, "html").consume(context.getString)
console.log(html) // <div><strong>Hello world!</strong></div>
```
##### Async on host, sync in QuickJS
Here's an example of turning an async function into a sync function inside the
VM.
```typescript
const context = await newAsyncContext()
const path = await import("path")
const { promises: fs } = await import("fs")
const importsPath = path.join(__dirname, "../examples/imports") + "/"
const readFileHandle = context.newAsyncifiedFunction("readFile", async (pathHandle) => {
const pathString = path.join(importsPath, context.getString(pathHandle))
if (!pathString.startsWith(importsPath)) {
throw new Error("out of bounds")
}
const data = await fs.readFile(pathString, "utf-8")
return context.newString(data)
})
readFileHandle.consume((fn) => context.setProp(context.global, "readFile", fn))
// evalCodeAsync is required when execution may suspend.
const result = await context.evalCodeAsync(`
// Not a promise! Sync! vvvvvvvvvvvvvvvvvvvv
const data = JSON.parse(readFile('data.json'))
data.map(x => x.toUpperCase()).join(' ')
`)
const upperCaseData = context.unwrapResult(result).consume(context.getString)
console.log(upperCaseData) // 'VERY USEFUL DATA'
```
### Testing your code
This library is complicated to use, so please consider automated testing your
implementation. We highly writing your test suite to run with both the "release"
build variant of quickjs-emscripten, and also the [DEBUG_SYNC] build variant.
The debug sync build variant has extra instrumentation code for detecting memory
leaks.
The class [TestQuickJSWASMModule] exposes the memory leak detection API, although
this API is only accurate when using `DEBUG_SYNC` variant.
```typescript
// Define your test suite in a function, so that you can test against
// different module loaders.
function myTests(moduleLoader: () => Promise<QuickJSWASMModule>) {
let QuickJS: TestQuickJSWASMModule
beforeEach(async () => {
// Get a unique TestQuickJSWASMModule instance for each test.
const wasmModule = await moduleLoader()
QuickJS = new TestQuickJSWASMModule(wasmModule)
})
afterEach(() => {
// Assert that the test disposed all handles. The DEBUG_SYNC build
// variant will show detailed traces for each leak.
QuickJS.assertNoMemoryAllocated()
})
it("works well", () => {
// TODO: write a test using QuickJS
const context = QuickJS.newContext()
context.unwrapResult(context.evalCode("1 + 1")).dispose()
context.dispose()
})
}
// Run the test suite against a matrix of module loaders.
describe("Check for memory leaks with QuickJS DEBUG build", () => {
const moduleLoader = memoizePromiseFactory(() => newQuickJSWASMModule(DEBUG_SYNC))
myTests(moduleLoader)
})
describe("Realistic test with QuickJS RELEASE build", () => {
myTests(getQuickJS)
})
```
For more testing examples, please explore the typescript source of [quickjs-emscripten][ts] repository.
[ts]: https://github.com/justjake/quickjs-emscripten/blob/main/ts
[debug_sync]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md#debug_sync
[testquickjswasmmodule]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/TestQuickJSWASMModule.md
### Debugging
- Switch to a DEBUG build variant of the WebAssembly module to see debug log messages from the C part of this library.
- Set `process.env.QTS_DEBUG` to see debug log messages from the Javascript part of this library.
### More Documentation
[Github] | [NPM] | [API Documentation][api] | [Examples][tests]
## Background
This was inspired by seeing https://github.com/maple3142/duktape-eval
[on Hacker News](https://news.ycombinator.com/item?id=21946565) and Figma's
blogposts about using building a Javascript plugin runtime:
- [How Figma built the Figma plugin system](https://www.figma.com/blog/how-we-built-the-figma-plugin-system/): Describes the LowLevelJavascriptVm interface.
- [An update on plugin security](https://www.figma.com/blog/an-update-on-plugin-security/): Figma switches to QuickJS.
## Status & Roadmap
**Stability**: Because the version number of this project is below `1.0.0`,
\*expect occasional breaking API changes.
**Security**: This project makes every effort to be secure, but has not been
audited. Please use with care in production settings.
**Roadmap**: I work on this project in my free time, for fun. Here's I'm
thinking comes next. Last updated 2022-03-18.
1. Further work on module loading APIs:
- Create modules via Javascript, instead of source text.
- Scan source text for imports, for ahead of time or concurrent loading.
(This is possible with third-party tools, so lower priority.)
2. Higher-level tools for reading QuickJS values:
- Type guard functions: `context.isArray(handle)`, `context.isPromise(handle)`, etc.
- Iteration utilities: `context.getIterable(handle)`, `context.iterateObjectEntries(handle)`.
This better supports user-level code to deserialize complex handle objects.
3. Higher-level tools for creating QuickJS values:
- Devise a way to avoid needing to mess around with handles when setting up
the environment.
- Consider integrating
[quickjs-emscripten-sync](https://github.com/reearth/quickjs-emscripten-sync)
for automatic translation.
- Consider class-based or interface-type-based marshalling.
4. EcmaScript Modules / WebAssembly files / Deno support. This requires me to
learn a lot of new things, but should be interesting for modern browser usage.
5. SQLite integration.
## Related
- Duktape wrapped in Wasm: https://github.com/maple3142/duktape-eval/blob/main/src/Makefile
- QuickJS wrapped in C++: https://github.com/ftk/quickjspp
## Developing
This library is implemented in two languages: C (compiled to WASM with
Emscripten), and Typescript.
### The C parts
The ./c directory contains C code that wraps the QuickJS C library (in ./quickjs).
Public functions (those starting with `QTS_`) in ./c/interface.c are
automatically exported to native code (via a generated header) and to
Typescript (via a generated FFI class). See ./generate.ts for how this works.
The C code builds as both with `emscripten` (using `emcc`), to produce WASM (or
ASM.js) and with `clang`. Build outputs are checked in, so you can iterate on
the Javascript parts of the library without setting up the Emscripten toolchain.
Intermediate object files from QuickJS end up in ./build/quickjs/.
This project uses `emscripten 3.1.32`.
- On ARM64, you should install `emscripten` on your machine. For example on macOS, `brew install emscripten`.
- If _the correct version of emcc_ is not in your PATH, compilation falls back to using Docker.
On ARM64, this is 10-50x slower than native compilation, but it's just fine on x64.
Related NPM scripts:
- `yarn update-quickjs` will sync the ./quickjs folder with a
github repo tracking the upstream QuickJS.
- `yarn make-debug` will rebuild C outputs into ./build/wrapper
- `yarn make-release` will rebuild C outputs in release mode, which is the mode
that should be checked into the repo.
### The Typescript parts
The ./ts directory contains Typescript types and wraps the generated Emscripten
FFI in a more usable interface.
You'll need `node` and `yarn`. Install dependencies with `yarn install`.
- `yarn build` produces ./dist.
- `yarn test` runs the tests.
- `yarn test --watch` watches for changes and re-runs the tests.
### Yarn updates
Just run `yarn set version from sources` to upgrade the Yarn release.

View File

@@ -0,0 +1,819 @@
/**
* interface.c
*
* We primarily use JSValue* (pointer to JSValue) when communicating with the
* host javascript environment, because pointers are trivial to use for calls
* into emscripten because they're just a number!
*
* As with the quickjs.h API, a JSValueConst* value is "borrowed" and should
* not be freed. A JSValue* is "owned" and should be freed by the owner.
*
* Functions starting with "QTS_" are exported by generate.ts to:
* - interface.h for native C code.
* - ffi.ts for emscripten.
*
* We support building the following build outputs:
*
* ## 1. Native machine code
* For internal development testing purposes.
*
* ## 2. WASM via Emscripten
* For general production use.
*
* ## 3. Experimental: Asyncified WASM via Emscripten with -s ASYNCIFY=1.
* This variant supports treating async host Javascript calls as synchronous
* from the perspective of the WASM c code.
*
* The way this works is described here:
* https://emscripten.org/docs/porting/asyncify.html
*
* In this variant, any call into our C code could return a promise if it ended
* up suspended. We mark the methods we suspect might suspend due to users' code
* as returning MaybeAsync(T). This information is ignored for the regular
* build.
*/
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#include <math.h> // For NAN
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#ifdef QTS_SANITIZE_LEAK
#include <sanitizer/lsan_interface.h>
#endif
#include "../quickjs/cutils.h"
#include "../quickjs/quickjs-libc.h"
#include "../quickjs/quickjs.h"
#define PKG "quickjs-emscripten: "
#ifdef QTS_DEBUG_MODE
#define QTS_DEBUG(msg) qts_log(msg);
#define QTS_DUMP(value) qts_dump(ctx, value);
#else
#define QTS_DEBUG(msg) ;
#define QTS_DUMP(value) ;
#endif
/**
* Signal to our FFI code generator that this string argument should be passed as a pointer
* allocated by the caller on the heap, not a JS string on the stack.
* https://github.com/emscripten-core/emscripten/issues/6860#issuecomment-405818401
*/
#define BorrowedHeapChar const char
#define OwnedHeapChar char
#define JSBorrowedChar const char
/**
* Signal to our FFI code generator that this function should be called
* asynchronously when compiled with ASYNCIFY.
*/
#define MaybeAsync(T) T
/**
* Signal to our FFI code generator that this function is only available in
* ASYNCIFY builds.
*/
#define AsyncifyOnly(T) T
#define JSVoid void
#define EvalFlags int
#define EvalDetectModule int
void qts_log(char *msg) {
fputs(PKG, stderr);
fputs(msg, stderr);
fputs("\n", stderr);
}
void qts_dump(JSContext *ctx, JSValueConst value) {
const char *str = JS_ToCString(ctx, value);
if (!str) {
QTS_DEBUG("QTS_DUMP: can't dump");
return;
}
fputs(str, stderr);
JS_FreeCString(ctx, str);
putchar('\n');
}
void copy_prop_if_needed(JSContext *ctx, JSValueConst dest, JSValueConst src, const char *prop_name) {
JSAtom prop_atom = JS_NewAtom(ctx, prop_name);
JSValue dest_prop = JS_GetProperty(ctx, dest, prop_atom);
if (JS_IsUndefined(dest_prop)) {
JSValue src_prop = JS_GetProperty(ctx, src, prop_atom);
if (!JS_IsUndefined(src_prop) && !JS_IsException(src_prop)) {
JS_SetProperty(ctx, dest, prop_atom, src_prop);
}
} else {
JS_FreeValue(ctx, dest_prop);
}
JS_FreeAtom(ctx, prop_atom);
}
JSValue *jsvalue_to_heap(JSValueConst value) {
JSValue *result = malloc(sizeof(JSValue));
if (result) {
// Could be better optimized, but at -0z / -ftlo, it
// appears to produce the same binary code as a memcpy.
*result = value;
}
return result;
}
JSValue *QTS_Throw(JSContext *ctx, JSValueConst *error) {
JSValue copy = JS_DupValue(ctx, *error);
return jsvalue_to_heap(JS_Throw(ctx, copy));
}
JSValue *QTS_NewError(JSContext *ctx) {
return jsvalue_to_heap(JS_NewError(ctx));
}
/**
* Limits.
*/
/**
* Memory limit. Set to -1 to disable.
*/
void QTS_RuntimeSetMemoryLimit(JSRuntime *rt, size_t limit) {
JS_SetMemoryLimit(rt, limit);
}
/**
* Memory diagnostics
*/
JSValue *QTS_RuntimeComputeMemoryUsage(JSRuntime *rt, JSContext *ctx) {
JSMemoryUsage s;
JS_ComputeMemoryUsage(rt, &s);
// Note that we're going to allocate more memory just to report the memory usage.
// A more sound approach would be to bind JSMemoryUsage struct directly - but that's
// a lot of work. This should be okay in the mean time.
JSValue result = JS_NewObject(ctx);
// Manually generated via editor-fu from JSMemoryUsage struct definition in quickjs.h
JS_SetPropertyStr(ctx, result, "malloc_limit", JS_NewInt64(ctx, s.malloc_limit));
JS_SetPropertyStr(ctx, result, "memory_used_size", JS_NewInt64(ctx, s.memory_used_size));
JS_SetPropertyStr(ctx, result, "malloc_count", JS_NewInt64(ctx, s.malloc_count));
JS_SetPropertyStr(ctx, result, "memory_used_count", JS_NewInt64(ctx, s.memory_used_count));
JS_SetPropertyStr(ctx, result, "atom_count", JS_NewInt64(ctx, s.atom_count));
JS_SetPropertyStr(ctx, result, "atom_size", JS_NewInt64(ctx, s.atom_size));
JS_SetPropertyStr(ctx, result, "str_count", JS_NewInt64(ctx, s.str_count));
JS_SetPropertyStr(ctx, result, "str_size", JS_NewInt64(ctx, s.str_size));
JS_SetPropertyStr(ctx, result, "obj_count", JS_NewInt64(ctx, s.obj_count));
JS_SetPropertyStr(ctx, result, "obj_size", JS_NewInt64(ctx, s.obj_size));
JS_SetPropertyStr(ctx, result, "prop_count", JS_NewInt64(ctx, s.prop_count));
JS_SetPropertyStr(ctx, result, "prop_size", JS_NewInt64(ctx, s.prop_size));
JS_SetPropertyStr(ctx, result, "shape_count", JS_NewInt64(ctx, s.shape_count));
JS_SetPropertyStr(ctx, result, "shape_size", JS_NewInt64(ctx, s.shape_size));
JS_SetPropertyStr(ctx, result, "js_func_count", JS_NewInt64(ctx, s.js_func_count));
JS_SetPropertyStr(ctx, result, "js_func_size", JS_NewInt64(ctx, s.js_func_size));
JS_SetPropertyStr(ctx, result, "js_func_code_size", JS_NewInt64(ctx, s.js_func_code_size));
JS_SetPropertyStr(ctx, result, "js_func_pc2line_count", JS_NewInt64(ctx, s.js_func_pc2line_count));
JS_SetPropertyStr(ctx, result, "js_func_pc2line_size", JS_NewInt64(ctx, s.js_func_pc2line_size));
JS_SetPropertyStr(ctx, result, "c_func_count", JS_NewInt64(ctx, s.c_func_count));
JS_SetPropertyStr(ctx, result, "array_count", JS_NewInt64(ctx, s.array_count));
JS_SetPropertyStr(ctx, result, "fast_array_count", JS_NewInt64(ctx, s.fast_array_count));
JS_SetPropertyStr(ctx, result, "fast_array_elements", JS_NewInt64(ctx, s.fast_array_elements));
JS_SetPropertyStr(ctx, result, "binary_object_count", JS_NewInt64(ctx, s.binary_object_count));
JS_SetPropertyStr(ctx, result, "binary_object_size", JS_NewInt64(ctx, s.binary_object_size));
return jsvalue_to_heap(result);
}
OwnedHeapChar *QTS_RuntimeDumpMemoryUsage(JSRuntime *rt) {
char *result = malloc(sizeof(char) * 1024);
FILE *memfile = fmemopen(result, 1024, "w");
JSMemoryUsage s;
JS_ComputeMemoryUsage(rt, &s);
JS_DumpMemoryUsage(memfile, &s, rt);
fclose(memfile);
return result;
}
int QTS_RecoverableLeakCheck() {
#ifdef QTS_SANITIZE_LEAK
return __lsan_do_recoverable_leak_check();
#else
return 0;
#endif
}
int QTS_BuildIsSanitizeLeak() {
#ifdef QTS_SANITIZE_LEAK
return 1;
#else
return 0;
#endif
}
#ifdef QTS_ASYNCIFY
EM_JS(void, set_asyncify_stack_size, (size_t size), {
Asyncify.StackSize = size || 81920;
});
#endif
/**
* Set the stack size limit, in bytes. Set to 0 to disable.
*/
void QTS_RuntimeSetMaxStackSize(JSRuntime *rt, size_t stack_size) {
#ifdef QTS_ASYNCIFY
set_asyncify_stack_size(stack_size);
#endif
JS_SetMaxStackSize(rt, stack_size);
}
/**
* Constant pointers. Because we always use JSValue* from the host Javascript environment,
* we need helper fuctions to return pointers to these constants.
*/
JSValueConst QTS_Undefined = JS_UNDEFINED;
JSValueConst *QTS_GetUndefined() {
return &QTS_Undefined;
}
JSValueConst QTS_Null = JS_NULL;
JSValueConst *QTS_GetNull() {
return &QTS_Null;
}
JSValueConst QTS_False = JS_FALSE;
JSValueConst *QTS_GetFalse() {
return &QTS_False;
}
JSValueConst QTS_True = JS_TRUE;
JSValueConst *QTS_GetTrue() {
return &QTS_True;
}
/**
* Standard FFI functions
*/
JSRuntime *QTS_NewRuntime() {
return JS_NewRuntime();
}
void QTS_FreeRuntime(JSRuntime *rt) {
JS_FreeRuntime(rt);
}
JSContext *QTS_NewContext(JSRuntime *rt) {
return JS_NewContext(rt);
}
void QTS_FreeContext(JSContext *ctx) {
JS_FreeContext(ctx);
}
void QTS_FreeValuePointer(JSContext *ctx, JSValue *value) {
JS_FreeValue(ctx, *value);
free(value);
}
void QTS_FreeValuePointerRuntime(JSRuntime *rt, JSValue *value) {
JS_FreeValueRT(rt, *value);
free(value);
}
void QTS_FreeVoidPointer(JSContext *ctx, JSVoid *ptr) {
js_free(ctx, ptr);
}
void QTS_FreeCString(JSContext *ctx, JSBorrowedChar *str) {
JS_FreeCString(ctx, str);
}
JSValue *QTS_DupValuePointer(JSContext *ctx, JSValueConst *val) {
return jsvalue_to_heap(JS_DupValue(ctx, *val));
}
JSValue *QTS_NewObject(JSContext *ctx) {
return jsvalue_to_heap(JS_NewObject(ctx));
}
JSValue *QTS_NewObjectProto(JSContext *ctx, JSValueConst *proto) {
return jsvalue_to_heap(JS_NewObjectProto(ctx, *proto));
}
JSValue *QTS_NewArray(JSContext *ctx) {
return jsvalue_to_heap(JS_NewArray(ctx));
}
JSValue *QTS_NewFloat64(JSContext *ctx, double num) {
return jsvalue_to_heap(JS_NewFloat64(ctx, num));
}
double QTS_GetFloat64(JSContext *ctx, JSValueConst *value) {
double result = NAN;
JS_ToFloat64(ctx, &result, *value);
return result;
}
JSValue *QTS_NewString(JSContext *ctx, BorrowedHeapChar *string) {
return jsvalue_to_heap(JS_NewString(ctx, string));
}
JSBorrowedChar *QTS_GetString(JSContext *ctx, JSValueConst *value) {
return JS_ToCString(ctx, *value);
}
JSValue qts_get_symbol_key(JSContext *ctx, JSValueConst *value) {
JSValue global = JS_GetGlobalObject(ctx);
JSValue Symbol = JS_GetPropertyStr(ctx, global, "Symbol");
JS_FreeValue(ctx, global);
JSValue Symbol_keyFor = JS_GetPropertyStr(ctx, Symbol, "keyFor");
JSValue key = JS_Call(ctx, Symbol_keyFor, Symbol, 1, value);
JS_FreeValue(ctx, Symbol_keyFor);
JS_FreeValue(ctx, Symbol);
return key;
}
JSValue *QTS_NewSymbol(JSContext *ctx, BorrowedHeapChar *description, int isGlobal) {
JSValue global = JS_GetGlobalObject(ctx);
JSValue Symbol = JS_GetPropertyStr(ctx, global, "Symbol");
JS_FreeValue(ctx, global);
JSValue descriptionValue = JS_NewString(ctx, description);
JSValue symbol;
if (isGlobal != 0) {
JSValue Symbol_for = JS_GetPropertyStr(ctx, Symbol, "for");
symbol = JS_Call(ctx, Symbol_for, Symbol, 1, &descriptionValue);
JS_FreeValue(ctx, descriptionValue);
JS_FreeValue(ctx, Symbol_for);
JS_FreeValue(ctx, Symbol);
return jsvalue_to_heap(symbol);
}
symbol = JS_Call(ctx, Symbol, JS_UNDEFINED, 1, &descriptionValue);
JS_FreeValue(ctx, descriptionValue);
JS_FreeValue(ctx, Symbol);
return jsvalue_to_heap(symbol);
}
MaybeAsync(JSBorrowedChar *) QTS_GetSymbolDescriptionOrKey(JSContext *ctx, JSValueConst *value) {
JSBorrowedChar *result;
JSValue key = qts_get_symbol_key(ctx, value);
if (!JS_IsUndefined(key)) {
result = JS_ToCString(ctx, key);
JS_FreeValue(ctx, key);
return result;
}
JSValue description = JS_GetPropertyStr(ctx, *value, "description");
result = JS_ToCString(ctx, description);
JS_FreeValue(ctx, description);
return result;
}
int QTS_IsGlobalSymbol(JSContext *ctx, JSValueConst *value) {
JSValue key = qts_get_symbol_key(ctx, value);
int undefined = JS_IsUndefined(key);
JS_FreeValue(ctx, key);
if (undefined) {
return 0;
} else {
return 1;
}
}
int QTS_IsJobPending(JSRuntime *rt) {
return JS_IsJobPending(rt);
}
/*
runs pending jobs (Promises/async functions) until it encounters
an exception or it executed the passed maxJobsToExecute jobs.
Passing a negative value will run the loop until there are no more
pending jobs or an exception happened
Returns the executed number of jobs or the exception encountered
*/
MaybeAsync(JSValue *) QTS_ExecutePendingJob(JSRuntime *rt, int maxJobsToExecute, JSContext **lastJobContext) {
JSContext *pctx;
int status = 1;
int executed = 0;
while (executed != maxJobsToExecute && status == 1) {
status = JS_ExecutePendingJob(rt, &pctx);
if (status == -1) {
*lastJobContext = pctx;
return jsvalue_to_heap(JS_GetException(pctx));
} else if (status == 1) {
*lastJobContext = pctx;
executed++;
}
}
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "QTS_ExecutePendingJob(executed: %d, pctx: %p, lastJobExecuted: %p)", executed, pctx, *lastJobContext);
QTS_DEBUG(msg)
#endif
return jsvalue_to_heap(JS_NewFloat64(pctx, executed));
}
MaybeAsync(JSValue *) QTS_GetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name) {
JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
JSValue prop_val = JS_GetProperty(ctx, *this_val, prop_atom);
JS_FreeAtom(ctx, prop_atom);
return jsvalue_to_heap(prop_val);
}
MaybeAsync(void) QTS_SetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value) {
JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
JSValue extra_prop_value = JS_DupValue(ctx, *prop_value);
// TODO: should we use DefineProperty internally if this object doesn't have the property yet?
JS_SetProperty(ctx, *this_val, prop_atom, extra_prop_value); // consumes extra_prop_value
JS_FreeAtom(ctx, prop_atom);
}
void QTS_DefineProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value, JSValueConst *get, JSValueConst *set, bool configurable, bool enumerable, bool has_value) {
JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
int flags = 0;
if (configurable) {
flags = flags | JS_PROP_CONFIGURABLE;
if (has_value) {
flags = flags | JS_PROP_HAS_CONFIGURABLE;
}
}
if (enumerable) {
flags = flags | JS_PROP_ENUMERABLE;
if (has_value) {
flags = flags | JS_PROP_HAS_ENUMERABLE;
}
}
if (!JS_IsUndefined(*get)) {
flags = flags | JS_PROP_HAS_GET;
}
if (!JS_IsUndefined(*set)) {
flags = flags | JS_PROP_HAS_SET;
}
if (has_value) {
flags = flags | JS_PROP_HAS_VALUE;
}
JS_DefineProperty(ctx, *this_val, prop_atom, *prop_value, *get, *set, flags);
JS_FreeAtom(ctx, prop_atom);
}
MaybeAsync(JSValue *) QTS_Call(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj, int argc, JSValueConst **argv_ptrs) {
// convert array of pointers to array of values
JSValueConst argv[argc];
int i;
for (i = 0; i < argc; i++) {
argv[i] = *(argv_ptrs[i]);
}
return jsvalue_to_heap(JS_Call(ctx, *func_obj, *this_obj, argc, argv));
}
/**
* If maybe_exception is an exception, get the error.
* Otherwise, return NULL.
*/
JSValue *QTS_ResolveException(JSContext *ctx, JSValue *maybe_exception) {
if (JS_IsException(*maybe_exception)) {
return jsvalue_to_heap(JS_GetException(ctx));
}
return NULL;
}
MaybeAsync(JSBorrowedChar *) QTS_Dump(JSContext *ctx, JSValueConst *obj) {
JSValue obj_json_value = JS_JSONStringify(ctx, *obj, JS_UNDEFINED, JS_UNDEFINED);
if (!JS_IsException(obj_json_value)) {
const char *obj_json_chars = JS_ToCString(ctx, obj_json_value);
JS_FreeValue(ctx, obj_json_value);
if (obj_json_chars != NULL) {
JSValue enumerable_props = JS_ParseJSON(ctx, obj_json_chars, strlen(obj_json_chars), "<dump>");
JS_FreeCString(ctx, obj_json_chars);
if (!JS_IsException(enumerable_props)) {
// Copy common non-enumerable props for different object types.
// Errors:
copy_prop_if_needed(ctx, enumerable_props, *obj, "name");
copy_prop_if_needed(ctx, enumerable_props, *obj, "message");
copy_prop_if_needed(ctx, enumerable_props, *obj, "stack");
// Serialize again.
JSValue enumerable_json = JS_JSONStringify(ctx, enumerable_props, JS_UNDEFINED, JS_UNDEFINED);
JS_FreeValue(ctx, enumerable_props);
JSBorrowedChar *result = QTS_GetString(ctx, &enumerable_json);
JS_FreeValue(ctx, enumerable_json);
return result;
}
}
}
#ifdef QTS_DEBUG_MODE
qts_log("Error dumping JSON:");
js_std_dump_error(ctx);
#endif
// Fallback: convert to string
return QTS_GetString(ctx, obj);
}
MaybeAsync(JSValue *) QTS_Eval(JSContext *ctx, BorrowedHeapChar *js_code, const char *filename, EvalDetectModule detectModule, EvalFlags evalFlags) {
size_t js_code_len = strlen(js_code);
if (detectModule) {
if (JS_DetectModule((const char *)js_code, js_code_len)) {
QTS_DEBUG("QTS_Eval: Detected module = true");
evalFlags |= JS_EVAL_TYPE_MODULE;
} else {
QTS_DEBUG("QTS_Eval: Detected module = false");
}
} else {
QTS_DEBUG("QTS_Eval: do not detect module");
}
return jsvalue_to_heap(JS_Eval(ctx, js_code, strlen(js_code), filename, evalFlags));
}
OwnedHeapChar *QTS_Typeof(JSContext *ctx, JSValueConst *value) {
const char *result = "unknown";
uint32_t tag = JS_VALUE_GET_TAG(*value);
if (JS_IsNumber(*value)) {
result = "number";
} else if (JS_IsBigInt(ctx, *value)) {
result = "bigint";
} else if (JS_IsBigFloat(*value)) {
result = "bigfloat";
} else if (JS_IsBigDecimal(*value)) {
result = "bigdecimal";
} else if (JS_IsFunction(ctx, *value)) {
result = "function";
} else if (JS_IsBool(*value)) {
result = "boolean";
} else if (JS_IsNull(*value)) {
result = "object";
} else if (JS_IsUndefined(*value)) {
result = "undefined";
} else if (JS_IsUninitialized(*value)) {
result = "undefined";
} else if (JS_IsString(*value)) {
result = "string";
} else if (JS_IsSymbol(*value)) {
result = "symbol";
} else if (JS_IsObject(*value)) {
result = "object";
}
char *out = strdup(result);
return out;
}
JSValue *QTS_GetGlobalObject(JSContext *ctx) {
return jsvalue_to_heap(JS_GetGlobalObject(ctx));
}
JSValue *QTS_NewPromiseCapability(JSContext *ctx, JSValue **resolve_funcs_out) {
JSValue resolve_funcs[2];
JSValue promise = JS_NewPromiseCapability(ctx, resolve_funcs);
resolve_funcs_out[0] = jsvalue_to_heap(resolve_funcs[0]);
resolve_funcs_out[1] = jsvalue_to_heap(resolve_funcs[1]);
return jsvalue_to_heap(promise);
}
void QTS_TestStringArg(const char *string) {
// pass
}
int QTS_BuildIsDebug() {
#ifdef QTS_DEBUG_MODE
return 1;
#else
return 0;
#endif
}
int QTS_BuildIsAsyncify() {
#ifdef QTS_ASYNCIFY
return 1;
#else
return 0;
#endif
}
// ----------------------------------------------------------------------------
// Module loading helpers
// ----------------------------------------------------------------------------
// C -> Host Callbacks
// Note: inside EM_JS, we need to use ['...'] subscript syntax for accessing JS
// objects, because in optimized builds, Closure compiler will mangle all the
// names.
// -------------------
// function: C -> Host
#ifdef __EMSCRIPTEN__
EM_JS(MaybeAsync(JSValue *), qts_host_call_function, (JSContext * ctx, JSValueConst *this_ptr, int argc, JSValueConst *argv, uint32_t magic_func_id), {
#ifdef QTS_ASYNCIFY
const asyncify = {['handleSleep'] : Asyncify.handleSleep};
#else
const asyncify = undefined;
#endif
return Module['callbacks']['callFunction'](asyncify, ctx, this_ptr, argc, argv, magic_func_id);
});
#endif
// Function: QuickJS -> C
JSValue qts_call_function(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) {
JSValue *result_ptr = qts_host_call_function(ctx, &this_val, argc, argv, magic);
if (result_ptr == NULL) {
return JS_UNDEFINED;
}
JSValue result = *result_ptr;
free(result_ptr);
return result;
}
// Function: Host -> QuickJS
JSValue *QTS_NewFunction(JSContext *ctx, uint32_t func_id, const char *name) {
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "new_function(name: %s, magic: %d)", name, func_id);
QTS_DEBUG(msg)
#endif
JSValue func_obj = JS_NewCFunctionMagic(
/* context */ ctx,
/* JSCFunctionMagic* */ &qts_call_function,
/* name */ name,
/* min argc */ 0,
/* function type */ JS_CFUNC_generic_magic,
/* magic: fn id */ func_id);
return jsvalue_to_heap(func_obj);
}
JSValueConst *QTS_ArgvGetJSValueConstPointer(JSValueConst *argv, int index) {
return &argv[index];
}
// --------------------
// interrupt: C -> Host
#ifdef __EMSCRIPTEN__
EM_JS(int, qts_host_interrupt_handler, (JSRuntime * rt), {
// Async not supported here.
// #ifdef QTS_ASYNCIFY
// const asyncify = Asyncify;
// #else
const asyncify = undefined;
// #endif
return Module['callbacks']['shouldInterrupt'](asyncify, rt);
});
#endif
// interrupt: QuickJS -> C
int qts_interrupt_handler(JSRuntime *rt, void *_unused) {
return qts_host_interrupt_handler(rt);
}
// interrupt: Host -> QuickJS
void QTS_RuntimeEnableInterruptHandler(JSRuntime *rt) {
JS_SetInterruptHandler(rt, &qts_interrupt_handler, NULL);
}
void QTS_RuntimeDisableInterruptHandler(JSRuntime *rt) {
JS_SetInterruptHandler(rt, NULL, NULL);
}
// --------------------
// load module: C -> Host
// TODO: a future version can support host returning JSModuleDef* directly;
// for now we only support loading module source code.
/*
The module loading model under ASYNCIFY is convoluted. We need to make sure we
never have an async request running concurrently for loading modules.
The first implemenation looked like this:
C HOST SUSPENDED
qts_host_load_module(name) ------> false
call rt.loadModule(name) false
Start async load module false
Suspend C true
Async load complete true
< --------------- QTS_CompileModule(source) true
QTS_Eval(source, COMPILE_ONLY) true
Loaded module has import true
qts_host_load_module(dep) -------> true
call rt.loadModule(dep) true
Start async load module true
ALREADY SUSPENDED, CRASH
We can solve this in two different ways:
1. Return to C as soon as we async load the module source.
That way, we unsuspend before calling QTS_CompileModule.
2. Once we load the module, use a new API to detect and async
load the module's downstream dependencies. This way
they're loaded synchronously so we don't need to suspend "again".
Probably we could optimize (2) to make it more performant, eg with parallel
loading, but (1) seems much easier to implement in the sort run.
*/
JSModuleDef *qts_compile_module(JSContext *ctx, const char *module_name, BorrowedHeapChar *module_body) {
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "QTS_CompileModule(ctx: %p, name: %s, bodyLength: %lu)", ctx, module_name, strlen(module_body));
QTS_DEBUG(msg)
#endif
JSValue func_val = JS_Eval(ctx, module_body, strlen(module_body), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(func_val)) {
return NULL;
}
// TODO: Is exception ok?
// TODO: set import.meta?
JSModuleDef *module = JS_VALUE_GET_PTR(func_val);
JS_FreeValue(ctx, func_val);
return module;
}
#ifdef __EMSCRIPTEN__
EM_JS(MaybeAsync(char *), qts_host_load_module_source, (JSRuntime * rt, JSContext *ctx, const char *module_name), {
#ifdef QTS_ASYNCIFY
const asyncify = {['handleSleep'] : Asyncify.handleSleep};
#else
const asyncify = undefined;
#endif
// https://emscripten.org/docs/api_reference/preamble.js.html#UTF8ToString
const moduleNameString = UTF8ToString(module_name);
return Module['callbacks']['loadModuleSource'](asyncify, rt, ctx, moduleNameString);
});
EM_JS(MaybeAsync(char *), qts_host_normalize_module, (JSRuntime * rt, JSContext *ctx, const char *module_base_name, const char *module_name), {
#ifdef QTS_ASYNCIFY
const asyncify = {['handleSleep'] : Asyncify.handleSleep};
#else
const asyncify = undefined;
#endif
// https://emscripten.org/docs/api_reference/preamble.js.html#UTF8ToString
const moduleBaseNameString = UTF8ToString(module_base_name);
const moduleNameString = UTF8ToString(module_name);
return Module['callbacks']['normalizeModule'](asyncify, rt, ctx, moduleBaseNameString, moduleNameString);
});
#endif
// load module: QuickJS -> C
// See js_module_loader in quickjs/quickjs-libc.c:567
JSModuleDef *qts_load_module(JSContext *ctx, const char *module_name, void *_unused) {
JSRuntime *rt = JS_GetRuntime(ctx);
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "qts_load_module(rt: %p, ctx: %p, name: %s)", rt, ctx, module_name);
QTS_DEBUG(msg)
#endif
char *module_source = qts_host_load_module_source(rt, ctx, module_name);
if (module_source == NULL) {
return NULL;
}
JSModuleDef *module = qts_compile_module(ctx, module_name, module_source);
free(module_source);
return module;
}
char *qts_normalize_module(JSContext *ctx, const char *module_base_name, const char *module_name, void *_unused) {
JSRuntime *rt = JS_GetRuntime(ctx);
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "qts_normalize_module(rt: %p, ctx: %p, base_name: %s, name: %s)", rt, ctx, module_base_name, module_name);
QTS_DEBUG(msg)
#endif
char *em_module_name = qts_host_normalize_module(rt, ctx, module_base_name, module_name);
char *js_module_name = js_strdup(ctx, em_module_name);
free(em_module_name);
return js_module_name;
}
// Load module: Host -> QuickJS
void QTS_RuntimeEnableModuleLoader(JSRuntime *rt, int use_custom_normalize) {
JSModuleNormalizeFunc *module_normalize = NULL; /* use default name normalizer */
if (use_custom_normalize) {
module_normalize = &qts_normalize_module;
}
JS_SetModuleLoaderFunc(rt, module_normalize, &qts_load_module, NULL);
}
void QTS_RuntimeDisableModuleLoader(JSRuntime *rt) {
JS_SetModuleLoaderFunc(rt, NULL, NULL, NULL);
}

View File

@@ -0,0 +1,24 @@
declare function awaitYield<T>(value: T | Promise<T>): Generator<T | Promise<T>, T, T>;
declare function awaitYieldOf<T, Yielded>(generator: Generator<Yielded | Promise<Yielded>, T, Yielded>): Generator<T | Promise<T>, T, T>;
export type AwaitYield = typeof awaitYield & {
of: typeof awaitYieldOf;
};
/**
* Create a function that may or may not be async, using a generator
*
* Within the generator, call `yield* awaited(maybePromise)` to await a value
* that may or may not be a promise.
*
* If the inner function never yields a promise, it will return synchronously.
*/
export declare function maybeAsyncFn<
/** Function arguments */
Args extends any[], This,
/** Function return type */
Return,
/** Yields to unwrap */
Yielded>(that: This, fn: (this: This, awaited: AwaitYield, ...args: Args) => Generator<Yielded | Promise<Yielded>, Return, Yielded>): (...args: Args) => Return | Promise<Return>;
export type MaybeAsyncBlock<Return, This, Yielded, Args extends any[] = []> = (this: This, awaited: AwaitYield, ...args: Args) => Generator<Yielded | Promise<Yielded>, Return, Yielded>;
export declare function maybeAsync<Return, This, Yielded>(that: This, startGenerator: (this: This, await: AwaitYield) => Generator<Yielded | Promise<Yielded>, Return, Yielded>): Return | Promise<Return>;
export declare function awaitEachYieldedPromise<Yielded, Returned>(gen: Generator<Yielded | Promise<Yielded>, Returned, Yielded>): Returned | Promise<Returned>;
export {};

View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.awaitEachYieldedPromise = exports.maybeAsync = exports.maybeAsyncFn = void 0;
function* awaitYield(value) {
return (yield value);
}
function awaitYieldOf(generator) {
return awaitYield(awaitEachYieldedPromise(generator));
}
const AwaitYield = awaitYield;
AwaitYield.of = awaitYieldOf;
/**
* Create a function that may or may not be async, using a generator
*
* Within the generator, call `yield* awaited(maybePromise)` to await a value
* that may or may not be a promise.
*
* If the inner function never yields a promise, it will return synchronously.
*/
function maybeAsyncFn(that, fn) {
return (...args) => {
const generator = fn.call(that, AwaitYield, ...args);
return awaitEachYieldedPromise(generator);
};
}
exports.maybeAsyncFn = maybeAsyncFn;
class Example {
constructor() {
this.maybeAsyncMethod = maybeAsyncFn(this, function* (awaited, a) {
yield* awaited(new Promise((resolve) => setTimeout(resolve, a)));
return 5;
});
}
}
function maybeAsync(that, startGenerator) {
const generator = startGenerator.call(that, AwaitYield);
return awaitEachYieldedPromise(generator);
}
exports.maybeAsync = maybeAsync;
function awaitEachYieldedPromise(gen) {
function handleNextStep(step) {
if (step.done) {
return step.value;
}
if (step.value instanceof Promise) {
return step.value.then((value) => handleNextStep(gen.next(value)), (error) => handleNextStep(gen.throw(error)));
}
return handleNextStep(gen.next(step.value));
}
return handleNextStep(gen.next());
}
exports.awaitEachYieldedPromise = awaitEachYieldedPromise;
//# sourceMappingURL=asyncify-helpers.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"asyncify-helpers.js","sourceRoot":"","sources":["../ts/asyncify-helpers.ts"],"names":[],"mappings":";;;AAAA,QAAQ,CAAC,CAAC,UAAU,CAAI,KAAqB;IAC3C,OAAO,CAAC,MAAM,KAAK,CAAM,CAAA;AAC3B,CAAC;AAED,SAAS,YAAY,CACnB,SAA4D;IAE5D,OAAO,UAAU,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAA;AACvD,CAAC;AAMD,MAAM,UAAU,GAAe,UAAwB,CAAA;AACvD,UAAU,CAAC,EAAE,GAAG,YAAY,CAAA;AAE5B;;;;;;;GAOG;AACH,SAAgB,YAAY,CAS1B,IAAU,EACV,EAI2D;IAE3D,OAAO,CAAC,GAAG,IAAU,EAAE,EAAE;QACvB,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,CAAA;QACpD,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAA;IAC3C,CAAC,CAAA;AACH,CAAC;AApBD,oCAoBC;AAED,MAAM,OAAO;IAAb;QACU,qBAAgB,GAAG,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAS;YACzE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YAChE,OAAO,CAAC,CAAA;QACV,CAAC,CAAC,CAAA;IACJ,CAAC;CAAA;AAQD,SAAgB,UAAU,CACxB,IAAU,EACV,cAG2D;IAE3D,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACvD,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAA;AAC3C,CAAC;AATD,gCASC;AAED,SAAgB,uBAAuB,CACrC,GAA6D;IAI7D,SAAS,cAAc,CAAC,IAAgB;QACtC,IAAI,IAAI,CAAC,IAAI,EAAE;YACb,OAAO,IAAI,CAAC,KAAK,CAAA;SAClB;QAED,IAAI,IAAI,CAAC,KAAK,YAAY,OAAO,EAAE;YACjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CACpB,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAC1C,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAC5C,CAAA;SACF;QAED,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IAC7C,CAAC;IAED,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;AACnC,CAAC;AArBD,0DAqBC","sourcesContent":["function* awaitYield<T>(value: T | Promise<T>) {\n return (yield value) as T\n}\n\nfunction awaitYieldOf<T, Yielded>(\n generator: Generator<Yielded | Promise<Yielded>, T, Yielded>\n): Generator<T | Promise<T>, T, T> {\n return awaitYield(awaitEachYieldedPromise(generator))\n}\n\nexport type AwaitYield = typeof awaitYield & {\n of: typeof awaitYieldOf\n}\n\nconst AwaitYield: AwaitYield = awaitYield as AwaitYield\nAwaitYield.of = awaitYieldOf\n\n/**\n * Create a function that may or may not be async, using a generator\n *\n * Within the generator, call `yield* awaited(maybePromise)` to await a value\n * that may or may not be a promise.\n *\n * If the inner function never yields a promise, it will return synchronously.\n */\nexport function maybeAsyncFn<\n /** Function arguments */\n Args extends any[],\n This,\n /** Function return type */\n Return,\n /** Yields to unwrap */\n Yielded\n>(\n that: This,\n fn: (\n this: This,\n awaited: AwaitYield,\n ...args: Args\n ) => Generator<Yielded | Promise<Yielded>, Return, Yielded>\n): (...args: Args) => Return | Promise<Return> {\n return (...args: Args) => {\n const generator = fn.call(that, AwaitYield, ...args)\n return awaitEachYieldedPromise(generator)\n }\n}\n\nclass Example {\n private maybeAsyncMethod = maybeAsyncFn(this, function* (awaited, a: number) {\n yield* awaited(new Promise((resolve) => setTimeout(resolve, a)))\n return 5\n })\n}\n\nexport type MaybeAsyncBlock<Return, This, Yielded, Args extends any[] = []> = (\n this: This,\n awaited: AwaitYield,\n ...args: Args\n) => Generator<Yielded | Promise<Yielded>, Return, Yielded>\n\nexport function maybeAsync<Return, This, Yielded>(\n that: This,\n startGenerator: (\n this: This,\n await: AwaitYield\n ) => Generator<Yielded | Promise<Yielded>, Return, Yielded>\n): Return | Promise<Return> {\n const generator = startGenerator.call(that, AwaitYield)\n return awaitEachYieldedPromise(generator)\n}\n\nexport function awaitEachYieldedPromise<Yielded, Returned>(\n gen: Generator<Yielded | Promise<Yielded>, Returned, Yielded>\n): Returned | Promise<Returned> {\n type NextResult = ReturnType<typeof gen.next>\n\n function handleNextStep(step: NextResult): Returned | Promise<Returned> {\n if (step.done) {\n return step.value\n }\n\n if (step.value instanceof Promise) {\n return step.value.then(\n (value) => handleNextStep(gen.next(value)),\n (error) => handleNextStep(gen.throw(error))\n )\n }\n\n return handleNextStep(gen.next(step.value))\n }\n\n return handleNextStep(gen.next())\n}\n"]}

View File

@@ -0,0 +1,48 @@
import { QuickJSContext } from "./context";
import { QuickJSAsyncEmscriptenModule } from "./emscripten-types";
import { QuickJSAsyncFFI } from "./variants";
import { JSRuntimePointer } from "./types-ffi";
import { Lifetime } from "./lifetime";
import { QuickJSModuleCallbacks } from "./module";
import { QuickJSAsyncRuntime } from "./runtime-asyncify";
import { ContextEvalOptions, QuickJSHandle } from "./types";
import { VmCallResult } from "./vm-interface";
export type AsyncFunctionImplementation = (this: QuickJSHandle, ...args: QuickJSHandle[]) => Promise<QuickJSHandle | VmCallResult<QuickJSHandle> | void>;
/**
* Asyncified version of [[QuickJSContext]].
*
* *Asyncify* allows normally synchronous code to wait for asynchronous Promises
* or callbacks. The asyncified version of QuickJSContext can wait for async
* host functions as though they were synchronous.
*/
export declare class QuickJSAsyncContext extends QuickJSContext {
runtime: QuickJSAsyncRuntime;
/** @private */
protected module: QuickJSAsyncEmscriptenModule;
/** @private */
protected ffi: QuickJSAsyncFFI;
/** @private */
protected rt: Lifetime<JSRuntimePointer>;
/** @private */
protected callbacks: QuickJSModuleCallbacks;
/**
* Asyncified version of [[evalCode]].
*/
evalCodeAsync(code: string, filename?: string,
/** See [[EvalFlags]] for number semantics */
options?: number | ContextEvalOptions): Promise<VmCallResult<QuickJSHandle>>;
/**
* Similar to [[newFunction]].
* Convert an async host Javascript function into a synchronous QuickJS function value.
*
* Whenever QuickJS calls this function, the VM's stack will be unwound while
* waiting the async function to complete, and then restored when the returned
* promise resolves.
*
* Asyncified functions must never call other asyncified functions or
* `import`, even indirectly, because the stack cannot be unwound twice.
*
* See [Emscripten's docs on Asyncify](https://emscripten.org/docs/porting/asyncify.html).
*/
newAsyncifiedFunction(name: string, fn: AsyncFunctionImplementation): QuickJSHandle;
}

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSAsyncContext = void 0;
const context_1 = require("./context");
const debug_1 = require("./debug");
const types_1 = require("./types");
/**
* Asyncified version of [[QuickJSContext]].
*
* *Asyncify* allows normally synchronous code to wait for asynchronous Promises
* or callbacks. The asyncified version of QuickJSContext can wait for async
* host functions as though they were synchronous.
*/
class QuickJSAsyncContext extends context_1.QuickJSContext {
/**
* Asyncified version of [[evalCode]].
*/
async evalCodeAsync(code, filename = "eval.js",
/** See [[EvalFlags]] for number semantics */
options) {
const detectModule = (options === undefined ? 1 : 0);
const flags = (0, types_1.evalOptionsToFlags)(options);
let resultPtr = 0;
try {
resultPtr = await this.memory
.newHeapCharPointer(code)
.consume((charHandle) => this.ffi.QTS_Eval_MaybeAsync(this.ctx.value, charHandle.value, filename, detectModule, flags));
}
catch (error) {
(0, debug_1.debugLog)("QTS_Eval_MaybeAsync threw", error);
throw error;
}
const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr);
if (errorPtr) {
this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr);
return { error: this.memory.heapValueHandle(errorPtr) };
}
return { value: this.memory.heapValueHandle(resultPtr) };
}
/**
* Similar to [[newFunction]].
* Convert an async host Javascript function into a synchronous QuickJS function value.
*
* Whenever QuickJS calls this function, the VM's stack will be unwound while
* waiting the async function to complete, and then restored when the returned
* promise resolves.
*
* Asyncified functions must never call other asyncified functions or
* `import`, even indirectly, because the stack cannot be unwound twice.
*
* See [Emscripten's docs on Asyncify](https://emscripten.org/docs/porting/asyncify.html).
*/
newAsyncifiedFunction(name, fn) {
return this.newFunction(name, fn);
}
}
exports.QuickJSAsyncContext = QuickJSAsyncContext;
//# sourceMappingURL=context-asyncify.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"context-asyncify.js","sourceRoot":"","sources":["../ts/context-asyncify.ts"],"names":[],"mappings":";;;AAAA,uCAA0C;AAC1C,mCAAkC;AAOlC,mCAA+E;AAQ/E;;;;;;GAMG;AACH,MAAa,mBAAoB,SAAQ,wBAAc;IAWrD;;OAEG;IACH,KAAK,CAAC,aAAa,CACjB,IAAY,EACZ,WAAmB,SAAS;IAC5B,6CAA6C;IAC7C,OAAqC;QAErC,MAAM,YAAY,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAqB,CAAA;QACxE,MAAM,KAAK,GAAG,IAAA,0BAAkB,EAAC,OAAO,CAAc,CAAA;QACtD,IAAI,SAAS,GAAG,CAAmB,CAAA;QACnC,IAAI;YACF,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM;iBAC1B,kBAAkB,CAAC,IAAI,CAAC;iBACxB,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE,CACtB,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAC1B,IAAI,CAAC,GAAG,CAAC,KAAK,EACd,UAAU,CAAC,KAAK,EAChB,QAAQ,EACR,YAAY,EACZ,KAAK,CACN,CACF,CAAA;SACJ;QAAC,OAAO,KAAK,EAAE;YACd,IAAA,gBAAQ,EAAC,2BAA2B,EAAE,KAAK,CAAC,CAAA;YAC5C,MAAM,KAAK,CAAA;SACZ;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QACzE,IAAI,QAAQ,EAAE;YACZ,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;YACxD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAA;SACxD;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAA;IAC1D,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,qBAAqB,CAAC,IAAY,EAAE,EAA+B;QACjE,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAS,CAAC,CAAA;IAC1C,CAAC;CACF;AA/DD,kDA+DC","sourcesContent":["import { QuickJSContext } from \"./context\"\nimport { debugLog } from \"./debug\"\nimport { QuickJSAsyncEmscriptenModule } from \"./emscripten-types\"\nimport { QuickJSAsyncFFI } from \"./variants\"\nimport { EvalDetectModule, EvalFlags, JSRuntimePointer, JSValuePointer } from \"./types-ffi\"\nimport { Lifetime } from \"./lifetime\"\nimport { QuickJSModuleCallbacks } from \"./module\"\nimport { QuickJSAsyncRuntime } from \"./runtime-asyncify\"\nimport { ContextEvalOptions, evalOptionsToFlags, QuickJSHandle } from \"./types\"\nimport { VmCallResult } from \"./vm-interface\"\n\nexport type AsyncFunctionImplementation = (\n this: QuickJSHandle,\n ...args: QuickJSHandle[]\n) => Promise<QuickJSHandle | VmCallResult<QuickJSHandle> | void>\n\n/**\n * Asyncified version of [[QuickJSContext]].\n *\n * *Asyncify* allows normally synchronous code to wait for asynchronous Promises\n * or callbacks. The asyncified version of QuickJSContext can wait for async\n * host functions as though they were synchronous.\n */\nexport class QuickJSAsyncContext extends QuickJSContext {\n public declare runtime: QuickJSAsyncRuntime\n /** @private */\n protected declare module: QuickJSAsyncEmscriptenModule\n /** @private */\n protected declare ffi: QuickJSAsyncFFI\n /** @private */\n protected declare rt: Lifetime<JSRuntimePointer>\n /** @private */\n protected declare callbacks: QuickJSModuleCallbacks\n\n /**\n * Asyncified version of [[evalCode]].\n */\n async evalCodeAsync(\n code: string,\n filename: string = \"eval.js\",\n /** See [[EvalFlags]] for number semantics */\n options?: number | ContextEvalOptions\n ): Promise<VmCallResult<QuickJSHandle>> {\n const detectModule = (options === undefined ? 1 : 0) as EvalDetectModule\n const flags = evalOptionsToFlags(options) as EvalFlags\n let resultPtr = 0 as JSValuePointer\n try {\n resultPtr = await this.memory\n .newHeapCharPointer(code)\n .consume((charHandle) =>\n this.ffi.QTS_Eval_MaybeAsync(\n this.ctx.value,\n charHandle.value,\n filename,\n detectModule,\n flags\n )\n )\n } catch (error) {\n debugLog(\"QTS_Eval_MaybeAsync threw\", error)\n throw error\n }\n const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr)\n if (errorPtr) {\n this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr)\n return { error: this.memory.heapValueHandle(errorPtr) }\n }\n return { value: this.memory.heapValueHandle(resultPtr) }\n }\n\n /**\n * Similar to [[newFunction]].\n * Convert an async host Javascript function into a synchronous QuickJS function value.\n *\n * Whenever QuickJS calls this function, the VM's stack will be unwound while\n * waiting the async function to complete, and then restored when the returned\n * promise resolves.\n *\n * Asyncified functions must never call other asyncified functions or\n * `import`, even indirectly, because the stack cannot be unwound twice.\n *\n * See [Emscripten's docs on Asyncify](https://emscripten.org/docs/porting/asyncify.html).\n */\n newAsyncifiedFunction(name: string, fn: AsyncFunctionImplementation): QuickJSHandle {\n return this.newFunction(name, fn as any)\n }\n}\n"]}

View File

@@ -0,0 +1,371 @@
import { QuickJSDeferredPromise } from "./deferred-promise";
import type { EitherModule } from "./emscripten-types";
import { JSBorrowedCharPointer, JSContextPointer, JSRuntimePointer, JSValueConstPointer, JSValuePointer } from "./types-ffi";
import { Disposable, Lifetime, Scope } from "./lifetime";
import { ModuleMemory } from "./memory";
import { QuickJSModuleCallbacks } from "./module";
import { QuickJSRuntime } from "./runtime";
import { ContextEvalOptions, EitherFFI, JSValue, PromiseExecutor, QuickJSHandle } from "./types";
import { LowLevelJavascriptVm, SuccessOrFail, VmCallResult, VmFunctionImplementation, VmPropertyDescriptor } from "./vm-interface";
/**
* Property key for getting or setting a property on a handle with
* [[QuickJSContext.getProp]], [[QuickJSContext.setProp]], or [[QuickJSContext.defineProp]].
*/
export type QuickJSPropertyKey = number | string | QuickJSHandle;
/**
* @private
*/
declare class ContextMemory extends ModuleMemory implements Disposable {
readonly owner: QuickJSRuntime;
readonly ctx: Lifetime<JSContextPointer>;
readonly rt: Lifetime<JSRuntimePointer>;
readonly module: EitherModule;
readonly ffi: EitherFFI;
readonly scope: Scope;
/** @private */
constructor(args: {
owner: QuickJSRuntime;
module: EitherModule;
ffi: EitherFFI;
ctx: Lifetime<JSContextPointer>;
rt: Lifetime<JSRuntimePointer>;
ownedLifetimes?: Disposable[];
});
get alive(): boolean;
dispose(): void;
/**
* Track `lifetime` so that it is disposed when this scope is disposed.
*/
manage<T extends Disposable>(lifetime: T): T;
copyJSValue: (ptr: JSValuePointer | JSValueConstPointer) => any;
freeJSValue: (ptr: JSValuePointer) => void;
consumeJSCharPointer(ptr: JSBorrowedCharPointer): string;
heapValueHandle(ptr: JSValuePointer): JSValue;
}
/**
* QuickJSContext wraps a QuickJS Javascript context (JSContext*) within a
* runtime. The contexts within the same runtime may exchange objects freely.
* You can think of separate runtimes like different domains in a browser, and
* the contexts within a runtime like the different windows open to the same
* domain. The {@link runtime} references the context's runtime.
*
* This class's methods return {@link QuickJSHandle}, which wrap C pointers (JSValue*).
* It's the caller's responsibility to call `.dispose()` on any
* handles you create to free memory once you're done with the handle.
*
* Use {@link QuickJSRuntime.newContext} or {@link QuickJSWASMModule.newContext}
* to create a new QuickJSContext.
*
* Create QuickJS values inside the interpreter with methods like
* [[newNumber]], [[newString]], [[newArray]], [[newObject]],
* [[newFunction]], and [[newPromise]].
*
* Call [[setProp]] or [[defineProp]] to customize objects. Use those methods
* with [[global]] to expose the values you create to the interior of the
* interpreter, so they can be used in [[evalCode]].
*
* Use [[evalCode]] or [[callFunction]] to execute Javascript inside the VM. If
* you're using asynchronous code inside the QuickJSContext, you may need to also
* call [[executePendingJobs]]. Executing code inside the runtime returns a
* result object representing successful execution or an error. You must dispose
* of any such results to avoid leaking memory inside the VM.
*
* Implement memory and CPU constraints at the runtime level, using [[runtime]].
* See {@link QuickJSRuntime} for more information.
*
*/
export declare class QuickJSContext implements LowLevelJavascriptVm<QuickJSHandle>, Disposable {
/**
* The runtime that created this context.
*/
readonly runtime: QuickJSRuntime;
/** @private */
protected readonly ctx: Lifetime<JSContextPointer>;
/** @private */
protected readonly rt: Lifetime<JSRuntimePointer>;
/** @private */
protected readonly module: EitherModule;
/** @private */
protected readonly ffi: EitherFFI;
/** @private */
protected memory: ContextMemory;
/** @private */
protected _undefined: QuickJSHandle | undefined;
/** @private */
protected _null: QuickJSHandle | undefined;
/** @private */
protected _false: QuickJSHandle | undefined;
/** @private */
protected _true: QuickJSHandle | undefined;
/** @private */
protected _global: QuickJSHandle | undefined;
/** @private */
protected _BigInt: QuickJSHandle | undefined;
/**
* Use {@link QuickJS.createVm} to create a QuickJSContext instance.
*/
constructor(args: {
module: EitherModule;
ffi: EitherFFI;
ctx: Lifetime<JSContextPointer>;
rt: Lifetime<JSRuntimePointer>;
runtime: QuickJSRuntime;
ownedLifetimes?: Disposable[];
callbacks: QuickJSModuleCallbacks;
});
get alive(): boolean;
/**
* Dispose of this VM's underlying resources.
*
* @throws Calling this method without disposing of all created handles
* will result in an error.
*/
dispose(): void;
/**
* [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined).
*/
get undefined(): QuickJSHandle;
/**
* [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null).
*/
get null(): QuickJSHandle;
/**
* [`true`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/true).
*/
get true(): QuickJSHandle;
/**
* [`false`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/false).
*/
get false(): QuickJSHandle;
/**
* [`global`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects).
* A handle to the global object inside the interpreter.
* You can set properties to create global variables.
*/
get global(): QuickJSHandle;
/**
* Converts a Javascript number into a QuickJS value.
*/
newNumber(num: number): QuickJSHandle;
/**
* Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) value.
*/
newString(str: string): QuickJSHandle;
/**
* Create a QuickJS [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) value.
* No two symbols created with this function will be the same value.
*/
newUniqueSymbol(description: string | symbol): QuickJSHandle;
/**
* Get a symbol from the [global registry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry) for the given key.
* All symbols created with the same key will be the same value.
*/
newSymbolFor(key: string | symbol): QuickJSHandle;
/**
* Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) value.
*/
newBigInt(num: bigint): QuickJSHandle;
/**
* `{}`.
* Create a new QuickJS [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer).
*
* @param prototype - Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create).
*/
newObject(prototype?: QuickJSHandle): QuickJSHandle;
/**
* `[]`.
* Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
*/
newArray(): QuickJSHandle;
/**
* Create a new [[QuickJSDeferredPromise]]. Use `deferred.resolve(handle)` and
* `deferred.reject(handle)` to fulfill the promise handle available at `deferred.handle`.
* Note that you are responsible for calling `deferred.dispose()` to free the underlying
* resources; see the documentation on [[QuickJSDeferredPromise]] for details.
*/
newPromise(): QuickJSDeferredPromise;
/**
* Create a new [[QuickJSDeferredPromise]] that resolves when the
* given native Promise<QuickJSHandle> resolves. Rejections will be coerced
* to a QuickJS error.
*
* You can still resolve/reject the created promise "early" using its methods.
*/
newPromise(promise: Promise<QuickJSHandle>): QuickJSDeferredPromise;
/**
* Construct a new native Promise<QuickJSHandle>, and then convert it into a
* [[QuickJSDeferredPromise]].
*
* You can still resolve/reject the created promise "early" using its methods.
*/
newPromise(newPromiseFn: PromiseExecutor<QuickJSHandle, Error | QuickJSHandle>): QuickJSDeferredPromise;
/**
* Convert a Javascript function into a QuickJS function value.
* See [[VmFunctionImplementation]] for more details.
*
* A [[VmFunctionImplementation]] should not free its arguments or its return
* value. A VmFunctionImplementation should also not retain any references to
* its return value.
*
* To implement an async function, create a promise with [[newPromise]], then
* return the deferred promise handle from `deferred.handle` from your
* function implementation:
*
* ```
* const deferred = vm.newPromise()
* someNativeAsyncFunction().then(deferred.resolve)
* return deferred.handle
* ```
*/
newFunction(name: string, fn: VmFunctionImplementation<QuickJSHandle>): QuickJSHandle;
newError(error: {
name: string;
message: string;
}): QuickJSHandle;
newError(message: string): QuickJSHandle;
newError(): QuickJSHandle;
/**
* `typeof` operator. **Not** [standards compliant](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof).
*
* @remarks
* Does not support BigInt values correctly.
*/
typeof(handle: QuickJSHandle): string;
/**
* Converts `handle` into a Javascript number.
* @returns `NaN` on error, otherwise a `number`.
*/
getNumber(handle: QuickJSHandle): number;
/**
* Converts `handle` to a Javascript string.
*/
getString(handle: QuickJSHandle): string;
/**
* Converts `handle` into a Javascript symbol. If the symbol is in the global
* registry in the guest, it will be created with Symbol.for on the host.
*/
getSymbol(handle: QuickJSHandle): symbol;
/**
* Converts `handle` to a Javascript bigint.
*/
getBigInt(handle: QuickJSHandle): bigint;
/**
* `Promise.resolve(value)`.
* Convert a handle containing a Promise-like value inside the VM into an
* actual promise on the host.
*
* @remarks
* You may need to call [[executePendingJobs]] to ensure that the promise is resolved.
*
* @param promiseLikeHandle - A handle to a Promise-like value with a `.then(onSuccess, onError)` method.
*/
resolvePromise(promiseLikeHandle: QuickJSHandle): Promise<VmCallResult<QuickJSHandle>>;
/**
* `handle[key]`.
* Get a property from a JSValue.
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string (which will be converted automatically).
*/
getProp(handle: QuickJSHandle, key: QuickJSPropertyKey): QuickJSHandle;
/**
* `handle[key] = value`.
* Set a property on a JSValue.
*
* @remarks
* Note that the QuickJS authors recommend using [[defineProp]] to define new
* properties.
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string or number (which will be converted automatically to a JSValue).
*/
setProp(handle: QuickJSHandle, key: QuickJSPropertyKey, value: QuickJSHandle): void;
/**
* [`Object.defineProperty(handle, key, descriptor)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string or number (which will be converted automatically to a JSValue).
*/
defineProp(handle: QuickJSHandle, key: QuickJSPropertyKey, descriptor: VmPropertyDescriptor<QuickJSHandle>): void;
/**
* [`func.call(thisVal, ...args)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call).
* Call a JSValue as a function.
*
* See [[unwrapResult]], which will throw if the function returned an error, or
* return the result handle directly. If evaluation returned a handle containing
* a promise, use [[resolvePromise]] to convert it to a native promise and
* [[executePendingJobs]] to finish evaluating the promise.
*
* @returns A result. If the function threw synchronously, `result.error` be a
* handle to the exception. Otherwise `result.value` will be a handle to the
* value.
*/
callFunction(func: QuickJSHandle, thisVal: QuickJSHandle, ...args: QuickJSHandle[]): VmCallResult<QuickJSHandle>;
/**
* Like [`eval(code)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Description).
* Evaluates the Javascript source `code` in the global scope of this VM.
* When working with async code, you many need to call [[executePendingJobs]]
* to execute callbacks pending after synchronous evaluation returns.
*
* See [[unwrapResult]], which will throw if the function returned an error, or
* return the result handle directly. If evaluation returned a handle containing
* a promise, use [[resolvePromise]] to convert it to a native promise and
* [[executePendingJobs]] to finish evaluating the promise.
*
* *Note*: to protect against infinite loops, provide an interrupt handler to
* [[setInterruptHandler]]. You can use [[shouldInterruptAfterDeadline]] to
* create a time-based deadline.
*
* @returns The last statement's value. If the code threw synchronously,
* `result.error` will be a handle to the exception. If execution was
* interrupted, the error will have name `InternalError` and message
* `interrupted`.
*/
evalCode(code: string, filename?: string,
/**
* If no options are passed, a heuristic will be used to detect if `code` is
* an ES module.
*
* See [[EvalFlags]] for number semantics.
*/
options?: number | ContextEvalOptions): VmCallResult<QuickJSHandle>;
/**
* Throw an error in the VM, interrupted whatever current execution is in progress when execution resumes.
* @experimental
*/
throw(error: Error | QuickJSHandle): any;
/**
* @private
*/
protected borrowPropertyKey(key: QuickJSPropertyKey): QuickJSHandle;
/**
* @private
*/
getMemory(rt: JSRuntimePointer): ContextMemory;
/**
* Dump a JSValue to Javascript in a best-effort fashion.
* Returns `handle.toString()` if it cannot be serialized to JSON.
*/
dump(handle: QuickJSHandle): any;
/**
* Unwrap a SuccessOrFail result such as a [[VmCallResult]] or a
* [[ExecutePendingJobsResult]], where the fail branch contains a handle to a QuickJS error value.
* If the result is a success, returns the value.
* If the result is an error, converts the error to a native object and throws the error.
*/
unwrapResult<T>(result: SuccessOrFail<T, QuickJSHandle>): T;
/** @private */
protected fnNextId: number;
/** @private */
protected fnMaps: Map<number, Map<number, VmFunctionImplementation<QuickJSHandle>>>;
/** @private */
protected getFunction(fn_id: number): VmFunctionImplementation<QuickJSHandle> | undefined;
/** @private */
protected setFunction(fn_id: number, handle: VmFunctionImplementation<QuickJSHandle>): Map<number, VmFunctionImplementation<QuickJSHandle>>;
/**
* @hidden
*/
private cToHostCallbacks;
private errorToHandle;
}
export {};

View File

@@ -0,0 +1,691 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSContext = void 0;
const debug_1 = require("./debug");
const deferred_promise_1 = require("./deferred-promise");
const errors_1 = require("./errors");
const lifetime_1 = require("./lifetime");
const memory_1 = require("./memory");
const types_1 = require("./types");
/**
* @private
*/
class ContextMemory extends memory_1.ModuleMemory {
/** @private */
constructor(args) {
super(args.module);
this.scope = new lifetime_1.Scope();
this.copyJSValue = (ptr) => {
return this.ffi.QTS_DupValuePointer(this.ctx.value, ptr);
};
this.freeJSValue = (ptr) => {
this.ffi.QTS_FreeValuePointer(this.ctx.value, ptr);
};
args.ownedLifetimes?.forEach((lifetime) => this.scope.manage(lifetime));
this.owner = args.owner;
this.module = args.module;
this.ffi = args.ffi;
this.rt = args.rt;
this.ctx = this.scope.manage(args.ctx);
}
get alive() {
return this.scope.alive;
}
dispose() {
return this.scope.dispose();
}
/**
* Track `lifetime` so that it is disposed when this scope is disposed.
*/
manage(lifetime) {
return this.scope.manage(lifetime);
}
consumeJSCharPointer(ptr) {
const str = this.module.UTF8ToString(ptr);
this.ffi.QTS_FreeCString(this.ctx.value, ptr);
return str;
}
heapValueHandle(ptr) {
return new lifetime_1.Lifetime(ptr, this.copyJSValue, this.freeJSValue, this.owner);
}
}
/**
* QuickJSContext wraps a QuickJS Javascript context (JSContext*) within a
* runtime. The contexts within the same runtime may exchange objects freely.
* You can think of separate runtimes like different domains in a browser, and
* the contexts within a runtime like the different windows open to the same
* domain. The {@link runtime} references the context's runtime.
*
* This class's methods return {@link QuickJSHandle}, which wrap C pointers (JSValue*).
* It's the caller's responsibility to call `.dispose()` on any
* handles you create to free memory once you're done with the handle.
*
* Use {@link QuickJSRuntime.newContext} or {@link QuickJSWASMModule.newContext}
* to create a new QuickJSContext.
*
* Create QuickJS values inside the interpreter with methods like
* [[newNumber]], [[newString]], [[newArray]], [[newObject]],
* [[newFunction]], and [[newPromise]].
*
* Call [[setProp]] or [[defineProp]] to customize objects. Use those methods
* with [[global]] to expose the values you create to the interior of the
* interpreter, so they can be used in [[evalCode]].
*
* Use [[evalCode]] or [[callFunction]] to execute Javascript inside the VM. If
* you're using asynchronous code inside the QuickJSContext, you may need to also
* call [[executePendingJobs]]. Executing code inside the runtime returns a
* result object representing successful execution or an error. You must dispose
* of any such results to avoid leaking memory inside the VM.
*
* Implement memory and CPU constraints at the runtime level, using [[runtime]].
* See {@link QuickJSRuntime} for more information.
*
*/
// TODO: Manage own callback registration
class QuickJSContext {
/**
* Use {@link QuickJS.createVm} to create a QuickJSContext instance.
*/
constructor(args) {
/** @private */
this._undefined = undefined;
/** @private */
this._null = undefined;
/** @private */
this._false = undefined;
/** @private */
this._true = undefined;
/** @private */
this._global = undefined;
/** @private */
this._BigInt = undefined;
/** @private */
this.fnNextId = -32768; // min value of signed 16bit int used by Quickjs
/** @private */
this.fnMaps = new Map();
/**
* @hidden
*/
this.cToHostCallbacks = {
callFunction: (ctx, this_ptr, argc, argv, fn_id) => {
if (ctx !== this.ctx.value) {
throw new Error("QuickJSContext instance received C -> JS call with mismatched ctx");
}
const fn = this.getFunction(fn_id);
if (!fn) {
// this "throw" is not catch-able from the TS side. could we somehow handle this higher up?
throw new Error(`QuickJSContext had no callback with id ${fn_id}`);
}
return lifetime_1.Scope.withScopeMaybeAsync(this, function* (awaited, scope) {
const thisHandle = scope.manage(new lifetime_1.WeakLifetime(this_ptr, this.memory.copyJSValue, this.memory.freeJSValue, this.runtime));
const argHandles = new Array(argc);
for (let i = 0; i < argc; i++) {
const ptr = this.ffi.QTS_ArgvGetJSValueConstPointer(argv, i);
argHandles[i] = scope.manage(new lifetime_1.WeakLifetime(ptr, this.memory.copyJSValue, this.memory.freeJSValue, this.runtime));
}
try {
const result = yield* awaited(fn.apply(thisHandle, argHandles));
if (result) {
if ("error" in result && result.error) {
(0, debug_1.debugLog)("throw error", result.error);
throw result.error;
}
const handle = scope.manage(result instanceof lifetime_1.Lifetime ? result : result.value);
return this.ffi.QTS_DupValuePointer(this.ctx.value, handle.value);
}
return 0;
}
catch (error) {
return this.errorToHandle(error).consume((errorHandle) => this.ffi.QTS_Throw(this.ctx.value, errorHandle.value));
}
});
},
};
this.runtime = args.runtime;
this.module = args.module;
this.ffi = args.ffi;
this.rt = args.rt;
this.ctx = args.ctx;
this.memory = new ContextMemory({
...args,
owner: this.runtime,
});
args.callbacks.setContextCallbacks(this.ctx.value, this.cToHostCallbacks);
this.dump = this.dump.bind(this);
this.getString = this.getString.bind(this);
this.getNumber = this.getNumber.bind(this);
this.resolvePromise = this.resolvePromise.bind(this);
}
// @implement Disposable ----------------------------------------------------
get alive() {
return this.memory.alive;
}
/**
* Dispose of this VM's underlying resources.
*
* @throws Calling this method without disposing of all created handles
* will result in an error.
*/
dispose() {
this.memory.dispose();
}
// Globals ------------------------------------------------------------------
/**
* [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined).
*/
get undefined() {
if (this._undefined) {
return this._undefined;
}
// Undefined is a constant, immutable value in QuickJS.
const ptr = this.ffi.QTS_GetUndefined();
return (this._undefined = new lifetime_1.StaticLifetime(ptr));
}
/**
* [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null).
*/
get null() {
if (this._null) {
return this._null;
}
// Null is a constant, immutable value in QuickJS.
const ptr = this.ffi.QTS_GetNull();
return (this._null = new lifetime_1.StaticLifetime(ptr));
}
/**
* [`true`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/true).
*/
get true() {
if (this._true) {
return this._true;
}
// True is a constant, immutable value in QuickJS.
const ptr = this.ffi.QTS_GetTrue();
return (this._true = new lifetime_1.StaticLifetime(ptr));
}
/**
* [`false`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/false).
*/
get false() {
if (this._false) {
return this._false;
}
// False is a constant, immutable value in QuickJS.
const ptr = this.ffi.QTS_GetFalse();
return (this._false = new lifetime_1.StaticLifetime(ptr));
}
/**
* [`global`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects).
* A handle to the global object inside the interpreter.
* You can set properties to create global variables.
*/
get global() {
if (this._global) {
return this._global;
}
// The global is a JSValue, but since it's lifetime is as long as the VM's,
// we should manage it.
const ptr = this.ffi.QTS_GetGlobalObject(this.ctx.value);
// Automatically clean up this reference when we dispose
this.memory.manage(this.memory.heapValueHandle(ptr));
// This isn't technically a static lifetime, but since it has the same
// lifetime as the VM, it's okay to fake one since when the VM is
// disposed, no other functions will accept the value.
this._global = new lifetime_1.StaticLifetime(ptr, this.runtime);
return this._global;
}
// New values ---------------------------------------------------------------
/**
* Converts a Javascript number into a QuickJS value.
*/
newNumber(num) {
return this.memory.heapValueHandle(this.ffi.QTS_NewFloat64(this.ctx.value, num));
}
/**
* Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) value.
*/
newString(str) {
const ptr = this.memory
.newHeapCharPointer(str)
.consume((charHandle) => this.ffi.QTS_NewString(this.ctx.value, charHandle.value));
return this.memory.heapValueHandle(ptr);
}
/**
* Create a QuickJS [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) value.
* No two symbols created with this function will be the same value.
*/
newUniqueSymbol(description) {
const key = (typeof description === "symbol" ? description.description : description) ?? "";
const ptr = this.memory
.newHeapCharPointer(key)
.consume((charHandle) => this.ffi.QTS_NewSymbol(this.ctx.value, charHandle.value, 0));
return this.memory.heapValueHandle(ptr);
}
/**
* Get a symbol from the [global registry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry) for the given key.
* All symbols created with the same key will be the same value.
*/
newSymbolFor(key) {
const description = (typeof key === "symbol" ? key.description : key) ?? "";
const ptr = this.memory
.newHeapCharPointer(description)
.consume((charHandle) => this.ffi.QTS_NewSymbol(this.ctx.value, charHandle.value, 1));
return this.memory.heapValueHandle(ptr);
}
/**
* Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) value.
*/
newBigInt(num) {
if (!this._BigInt) {
const bigIntHandle = this.getProp(this.global, "BigInt");
this.memory.manage(bigIntHandle);
this._BigInt = new lifetime_1.StaticLifetime(bigIntHandle.value, this.runtime);
}
const bigIntHandle = this._BigInt;
const asString = String(num);
return this.newString(asString).consume((handle) => this.unwrapResult(this.callFunction(bigIntHandle, this.undefined, handle)));
}
/**
* `{}`.
* Create a new QuickJS [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer).
*
* @param prototype - Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create).
*/
newObject(prototype) {
if (prototype) {
this.runtime.assertOwned(prototype);
}
const ptr = prototype
? this.ffi.QTS_NewObjectProto(this.ctx.value, prototype.value)
: this.ffi.QTS_NewObject(this.ctx.value);
return this.memory.heapValueHandle(ptr);
}
/**
* `[]`.
* Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
*/
newArray() {
const ptr = this.ffi.QTS_NewArray(this.ctx.value);
return this.memory.heapValueHandle(ptr);
}
newPromise(value) {
const deferredPromise = lifetime_1.Scope.withScope((scope) => {
const mutablePointerArray = scope.manage(this.memory.newMutablePointerArray(2));
const promisePtr = this.ffi.QTS_NewPromiseCapability(this.ctx.value, mutablePointerArray.value.ptr);
const promiseHandle = this.memory.heapValueHandle(promisePtr);
const [resolveHandle, rejectHandle] = Array.from(mutablePointerArray.value.typedArray).map((jsvaluePtr) => this.memory.heapValueHandle(jsvaluePtr));
return new deferred_promise_1.QuickJSDeferredPromise({
context: this,
promiseHandle,
resolveHandle,
rejectHandle,
});
});
if (value && typeof value === "function") {
value = new Promise(value);
}
if (value) {
Promise.resolve(value).then(deferredPromise.resolve, (error) => error instanceof lifetime_1.Lifetime
? deferredPromise.reject(error)
: this.newError(error).consume(deferredPromise.reject));
}
return deferredPromise;
}
/**
* Convert a Javascript function into a QuickJS function value.
* See [[VmFunctionImplementation]] for more details.
*
* A [[VmFunctionImplementation]] should not free its arguments or its return
* value. A VmFunctionImplementation should also not retain any references to
* its return value.
*
* To implement an async function, create a promise with [[newPromise]], then
* return the deferred promise handle from `deferred.handle` from your
* function implementation:
*
* ```
* const deferred = vm.newPromise()
* someNativeAsyncFunction().then(deferred.resolve)
* return deferred.handle
* ```
*/
newFunction(name, fn) {
const fnId = ++this.fnNextId;
this.setFunction(fnId, fn);
return this.memory.heapValueHandle(this.ffi.QTS_NewFunction(this.ctx.value, fnId, name));
}
newError(error) {
const errorHandle = this.memory.heapValueHandle(this.ffi.QTS_NewError(this.ctx.value));
if (error && typeof error === "object") {
if (error.name !== undefined) {
this.newString(error.name).consume((handle) => this.setProp(errorHandle, "name", handle));
}
if (error.message !== undefined) {
this.newString(error.message).consume((handle) => this.setProp(errorHandle, "message", handle));
}
}
else if (typeof error === "string") {
this.newString(error).consume((handle) => this.setProp(errorHandle, "message", handle));
}
else if (error !== undefined) {
// This isn't supported in the type signature but maybe it will make life easier.
this.newString(String(error)).consume((handle) => this.setProp(errorHandle, "message", handle));
}
return errorHandle;
}
// Read values --------------------------------------------------------------
/**
* `typeof` operator. **Not** [standards compliant](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof).
*
* @remarks
* Does not support BigInt values correctly.
*/
typeof(handle) {
this.runtime.assertOwned(handle);
return this.memory.consumeHeapCharPointer(this.ffi.QTS_Typeof(this.ctx.value, handle.value));
}
/**
* Converts `handle` into a Javascript number.
* @returns `NaN` on error, otherwise a `number`.
*/
getNumber(handle) {
this.runtime.assertOwned(handle);
return this.ffi.QTS_GetFloat64(this.ctx.value, handle.value);
}
/**
* Converts `handle` to a Javascript string.
*/
getString(handle) {
this.runtime.assertOwned(handle);
return this.memory.consumeJSCharPointer(this.ffi.QTS_GetString(this.ctx.value, handle.value));
}
/**
* Converts `handle` into a Javascript symbol. If the symbol is in the global
* registry in the guest, it will be created with Symbol.for on the host.
*/
getSymbol(handle) {
this.runtime.assertOwned(handle);
const key = this.memory.consumeJSCharPointer(this.ffi.QTS_GetSymbolDescriptionOrKey(this.ctx.value, handle.value));
const isGlobal = this.ffi.QTS_IsGlobalSymbol(this.ctx.value, handle.value);
return isGlobal ? Symbol.for(key) : Symbol(key);
}
/**
* Converts `handle` to a Javascript bigint.
*/
getBigInt(handle) {
this.runtime.assertOwned(handle);
const asString = this.getString(handle);
return BigInt(asString);
}
/**
* `Promise.resolve(value)`.
* Convert a handle containing a Promise-like value inside the VM into an
* actual promise on the host.
*
* @remarks
* You may need to call [[executePendingJobs]] to ensure that the promise is resolved.
*
* @param promiseLikeHandle - A handle to a Promise-like value with a `.then(onSuccess, onError)` method.
*/
resolvePromise(promiseLikeHandle) {
this.runtime.assertOwned(promiseLikeHandle);
const vmResolveResult = lifetime_1.Scope.withScope((scope) => {
const vmPromise = scope.manage(this.getProp(this.global, "Promise"));
const vmPromiseResolve = scope.manage(this.getProp(vmPromise, "resolve"));
return this.callFunction(vmPromiseResolve, vmPromise, promiseLikeHandle);
});
if (vmResolveResult.error) {
return Promise.resolve(vmResolveResult);
}
return new Promise((resolve) => {
lifetime_1.Scope.withScope((scope) => {
const resolveHandle = scope.manage(this.newFunction("resolve", (value) => {
resolve({ value: value && value.dup() });
}));
const rejectHandle = scope.manage(this.newFunction("reject", (error) => {
resolve({ error: error && error.dup() });
}));
const promiseHandle = scope.manage(vmResolveResult.value);
const promiseThenHandle = scope.manage(this.getProp(promiseHandle, "then"));
this.unwrapResult(this.callFunction(promiseThenHandle, promiseHandle, resolveHandle, rejectHandle)).dispose();
});
});
}
// Properties ---------------------------------------------------------------
/**
* `handle[key]`.
* Get a property from a JSValue.
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string (which will be converted automatically).
*/
getProp(handle, key) {
this.runtime.assertOwned(handle);
const ptr = this.borrowPropertyKey(key).consume((quickJSKey) => this.ffi.QTS_GetProp(this.ctx.value, handle.value, quickJSKey.value));
const result = this.memory.heapValueHandle(ptr);
return result;
}
/**
* `handle[key] = value`.
* Set a property on a JSValue.
*
* @remarks
* Note that the QuickJS authors recommend using [[defineProp]] to define new
* properties.
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string or number (which will be converted automatically to a JSValue).
*/
setProp(handle, key, value) {
this.runtime.assertOwned(handle);
// free newly allocated value if key was a string or number. No-op if string was already
// a QuickJS handle.
this.borrowPropertyKey(key).consume((quickJSKey) => this.ffi.QTS_SetProp(this.ctx.value, handle.value, quickJSKey.value, value.value));
}
/**
* [`Object.defineProperty(handle, key, descriptor)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string or number (which will be converted automatically to a JSValue).
*/
defineProp(handle, key, descriptor) {
this.runtime.assertOwned(handle);
lifetime_1.Scope.withScope((scope) => {
const quickJSKey = scope.manage(this.borrowPropertyKey(key));
const value = descriptor.value || this.undefined;
const configurable = Boolean(descriptor.configurable);
const enumerable = Boolean(descriptor.enumerable);
const hasValue = Boolean(descriptor.value);
const get = descriptor.get
? scope.manage(this.newFunction(descriptor.get.name, descriptor.get))
: this.undefined;
const set = descriptor.set
? scope.manage(this.newFunction(descriptor.set.name, descriptor.set))
: this.undefined;
this.ffi.QTS_DefineProp(this.ctx.value, handle.value, quickJSKey.value, value.value, get.value, set.value, configurable, enumerable, hasValue);
});
}
// Evaluation ---------------------------------------------------------------
/**
* [`func.call(thisVal, ...args)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call).
* Call a JSValue as a function.
*
* See [[unwrapResult]], which will throw if the function returned an error, or
* return the result handle directly. If evaluation returned a handle containing
* a promise, use [[resolvePromise]] to convert it to a native promise and
* [[executePendingJobs]] to finish evaluating the promise.
*
* @returns A result. If the function threw synchronously, `result.error` be a
* handle to the exception. Otherwise `result.value` will be a handle to the
* value.
*/
callFunction(func, thisVal, ...args) {
this.runtime.assertOwned(func);
const resultPtr = this.memory
.toPointerArray(args)
.consume((argsArrayPtr) => this.ffi.QTS_Call(this.ctx.value, func.value, thisVal.value, args.length, argsArrayPtr.value));
const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr);
if (errorPtr) {
this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr);
return { error: this.memory.heapValueHandle(errorPtr) };
}
return { value: this.memory.heapValueHandle(resultPtr) };
}
/**
* Like [`eval(code)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Description).
* Evaluates the Javascript source `code` in the global scope of this VM.
* When working with async code, you many need to call [[executePendingJobs]]
* to execute callbacks pending after synchronous evaluation returns.
*
* See [[unwrapResult]], which will throw if the function returned an error, or
* return the result handle directly. If evaluation returned a handle containing
* a promise, use [[resolvePromise]] to convert it to a native promise and
* [[executePendingJobs]] to finish evaluating the promise.
*
* *Note*: to protect against infinite loops, provide an interrupt handler to
* [[setInterruptHandler]]. You can use [[shouldInterruptAfterDeadline]] to
* create a time-based deadline.
*
* @returns The last statement's value. If the code threw synchronously,
* `result.error` will be a handle to the exception. If execution was
* interrupted, the error will have name `InternalError` and message
* `interrupted`.
*/
evalCode(code, filename = "eval.js",
/**
* If no options are passed, a heuristic will be used to detect if `code` is
* an ES module.
*
* See [[EvalFlags]] for number semantics.
*/
options) {
const detectModule = (options === undefined ? 1 : 0);
const flags = (0, types_1.evalOptionsToFlags)(options);
const resultPtr = this.memory
.newHeapCharPointer(code)
.consume((charHandle) => this.ffi.QTS_Eval(this.ctx.value, charHandle.value, filename, detectModule, flags));
const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr);
if (errorPtr) {
this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr);
return { error: this.memory.heapValueHandle(errorPtr) };
}
return { value: this.memory.heapValueHandle(resultPtr) };
}
/**
* Throw an error in the VM, interrupted whatever current execution is in progress when execution resumes.
* @experimental
*/
throw(error) {
return this.errorToHandle(error).consume((handle) => this.ffi.QTS_Throw(this.ctx.value, handle.value));
}
/**
* @private
*/
borrowPropertyKey(key) {
if (typeof key === "number") {
return this.newNumber(key);
}
if (typeof key === "string") {
return this.newString(key);
}
// key is already a JSValue, but we're borrowing it. Return a static handle
// for internal use only.
return new lifetime_1.StaticLifetime(key.value, this.runtime);
}
/**
* @private
*/
getMemory(rt) {
if (rt === this.rt.value) {
return this.memory;
}
else {
throw new Error("Private API. Cannot get memory from a different runtime");
}
}
// Utilities ----------------------------------------------------------------
/**
* Dump a JSValue to Javascript in a best-effort fashion.
* Returns `handle.toString()` if it cannot be serialized to JSON.
*/
dump(handle) {
this.runtime.assertOwned(handle);
const type = this.typeof(handle);
if (type === "string") {
return this.getString(handle);
}
else if (type === "number") {
return this.getNumber(handle);
}
else if (type === "bigint") {
return this.getBigInt(handle);
}
else if (type === "undefined") {
return undefined;
}
else if (type === "symbol") {
return this.getSymbol(handle);
}
const str = this.memory.consumeJSCharPointer(this.ffi.QTS_Dump(this.ctx.value, handle.value));
try {
return JSON.parse(str);
}
catch (err) {
return str;
}
}
/**
* Unwrap a SuccessOrFail result such as a [[VmCallResult]] or a
* [[ExecutePendingJobsResult]], where the fail branch contains a handle to a QuickJS error value.
* If the result is a success, returns the value.
* If the result is an error, converts the error to a native object and throws the error.
*/
unwrapResult(result) {
if (result.error) {
const context = "context" in result.error ? result.error.context : this;
const cause = result.error.consume((error) => this.dump(error));
if (cause && typeof cause === "object" && typeof cause.message === "string") {
const { message, name, stack } = cause;
const exception = new errors_1.QuickJSUnwrapError("");
const hostStack = exception.stack;
if (typeof name === "string") {
exception.name = cause.name;
}
if (typeof stack === "string") {
exception.stack = `${name}: ${message}\n${cause.stack}Host: ${hostStack}`;
}
Object.assign(exception, { cause, context, message });
throw exception;
}
throw new errors_1.QuickJSUnwrapError(cause, context);
}
return result.value;
}
/** @private */
getFunction(fn_id) {
const map_id = fn_id >> 8;
const fnMap = this.fnMaps.get(map_id);
if (!fnMap) {
return undefined;
}
return fnMap.get(fn_id);
}
/** @private */
setFunction(fn_id, handle) {
const map_id = fn_id >> 8;
let fnMap = this.fnMaps.get(map_id);
if (!fnMap) {
fnMap = new Map();
this.fnMaps.set(map_id, fnMap);
}
return fnMap.set(fn_id, handle);
}
errorToHandle(error) {
if (error instanceof lifetime_1.Lifetime) {
return error;
}
return this.newError(error);
}
}
exports.QuickJSContext = QuickJSContext;
//# sourceMappingURL=context.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
export declare const QTS_DEBUG: boolean;
export declare let debugLog: {
(...data: any[]): void;
(message?: any, ...optionalParams: any[]): void;
};

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.debugLog = exports.QTS_DEBUG = void 0;
exports.QTS_DEBUG = false || Boolean(typeof process === "object" && process.env.QTS_DEBUG);
exports.debugLog = exports.QTS_DEBUG ? console.log.bind(console) : () => { };
//# sourceMappingURL=debug.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../ts/debug.ts"],"names":[],"mappings":";;;AAAa,QAAA,SAAS,GAAG,KAAK,IAAI,OAAO,CAAC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;AACpF,QAAA,QAAQ,GAAG,iBAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAE,CAAC,CAAA","sourcesContent":["export const QTS_DEBUG = false || Boolean(typeof process === \"object\" && process.env.QTS_DEBUG)\nexport let debugLog = QTS_DEBUG ? console.log.bind(console) : () => {}\n"]}

View File

@@ -0,0 +1,75 @@
import type { Disposable } from "./lifetime";
import type { QuickJSHandle } from "./types";
import type { QuickJSRuntime } from "./runtime";
import type { QuickJSContext } from "./context";
export type { PromiseExecutor } from "./types";
/**
* QuickJSDeferredPromise wraps a QuickJS promise [[handle]] and allows
* [[resolve]]ing or [[reject]]ing that promise. Use it to bridge asynchronous
* code on the host to APIs inside a QuickJSContext.
*
* Managing the lifetime of promises is tricky. There are three
* [[QuickJSHandle]]s inside of each deferred promise object: (1) the promise
* itself, (2) the `resolve` callback, and (3) the `reject` callback.
*
* - If the promise will be fulfilled before the end of it's [[owner]]'s lifetime,
* the only cleanup necessary is `deferred.handle.dispose()`, because
* calling [[resolve]] or [[reject]] will dispose of both callbacks automatically.
*
* - As the return value of a [[VmFunctionImplementation]], return [[handle]],
* and ensure that either [[resolve]] or [[reject]] will be called. No other
* clean-up is necessary.
*
* - In other cases, call [[dispose]], which will dispose [[handle]] as well as the
* QuickJS handles that back [[resolve]] and [[reject]]. For this object,
* [[dispose]] is idempotent.
*/
export declare class QuickJSDeferredPromise implements Disposable {
owner: QuickJSRuntime;
context: QuickJSContext;
/**
* A handle of the Promise instance inside the QuickJSContext.
* You must dispose [[handle]] or the entire QuickJSDeferredPromise once you
* are finished with it.
*/
handle: QuickJSHandle;
/**
* A native promise that will resolve once this deferred is settled.
*/
settled: Promise<void>;
private resolveHandle;
private rejectHandle;
private onSettled;
/**
* Use [[QuickJSContext.newPromise]] to create a new promise instead of calling
* this constructor directly.
* @unstable
*/
constructor(args: {
context: QuickJSContext;
promiseHandle: QuickJSHandle;
resolveHandle: QuickJSHandle;
rejectHandle: QuickJSHandle;
});
/**
* Resolve [[handle]] with the given value, if any.
* Calling this method after calling [[dispose]] is a no-op.
*
* Note that after resolving a promise, you may need to call
* [[QuickJSContext.executePendingJobs]] to propagate the result to the promise's
* callbacks.
*/
resolve: (value?: QuickJSHandle) => void;
/**
* Reject [[handle]] with the given value, if any.
* Calling this method after calling [[dispose]] is a no-op.
*
* Note that after rejecting a promise, you may need to call
* [[QuickJSContext.executePendingJobs]] to propagate the result to the promise's
* callbacks.
*/
reject: (value?: QuickJSHandle) => void;
get alive(): boolean;
dispose: () => void;
private disposeResolvers;
}

View File

@@ -0,0 +1,96 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSDeferredPromise = void 0;
/**
* QuickJSDeferredPromise wraps a QuickJS promise [[handle]] and allows
* [[resolve]]ing or [[reject]]ing that promise. Use it to bridge asynchronous
* code on the host to APIs inside a QuickJSContext.
*
* Managing the lifetime of promises is tricky. There are three
* [[QuickJSHandle]]s inside of each deferred promise object: (1) the promise
* itself, (2) the `resolve` callback, and (3) the `reject` callback.
*
* - If the promise will be fulfilled before the end of it's [[owner]]'s lifetime,
* the only cleanup necessary is `deferred.handle.dispose()`, because
* calling [[resolve]] or [[reject]] will dispose of both callbacks automatically.
*
* - As the return value of a [[VmFunctionImplementation]], return [[handle]],
* and ensure that either [[resolve]] or [[reject]] will be called. No other
* clean-up is necessary.
*
* - In other cases, call [[dispose]], which will dispose [[handle]] as well as the
* QuickJS handles that back [[resolve]] and [[reject]]. For this object,
* [[dispose]] is idempotent.
*/
class QuickJSDeferredPromise {
/**
* Use [[QuickJSContext.newPromise]] to create a new promise instead of calling
* this constructor directly.
* @unstable
*/
constructor(args) {
/**
* Resolve [[handle]] with the given value, if any.
* Calling this method after calling [[dispose]] is a no-op.
*
* Note that after resolving a promise, you may need to call
* [[QuickJSContext.executePendingJobs]] to propagate the result to the promise's
* callbacks.
*/
this.resolve = (value) => {
if (!this.resolveHandle.alive) {
return;
}
this.context
.unwrapResult(this.context.callFunction(this.resolveHandle, this.context.undefined, value || this.context.undefined))
.dispose();
this.disposeResolvers();
this.onSettled();
};
/**
* Reject [[handle]] with the given value, if any.
* Calling this method after calling [[dispose]] is a no-op.
*
* Note that after rejecting a promise, you may need to call
* [[QuickJSContext.executePendingJobs]] to propagate the result to the promise's
* callbacks.
*/
this.reject = (value) => {
if (!this.rejectHandle.alive) {
return;
}
this.context
.unwrapResult(this.context.callFunction(this.rejectHandle, this.context.undefined, value || this.context.undefined))
.dispose();
this.disposeResolvers();
this.onSettled();
};
this.dispose = () => {
if (this.handle.alive) {
this.handle.dispose();
}
this.disposeResolvers();
};
this.context = args.context;
this.owner = args.context.runtime;
this.handle = args.promiseHandle;
this.settled = new Promise((resolve) => {
this.onSettled = resolve;
});
this.resolveHandle = args.resolveHandle;
this.rejectHandle = args.rejectHandle;
}
get alive() {
return this.handle.alive || this.resolveHandle.alive || this.rejectHandle.alive;
}
disposeResolvers() {
if (this.resolveHandle.alive) {
this.resolveHandle.dispose();
}
if (this.rejectHandle.alive) {
this.rejectHandle.dispose();
}
}
}
exports.QuickJSDeferredPromise = QuickJSDeferredPromise;
//# sourceMappingURL=deferred-promise.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,97 @@
import { BorrowedHeapCharPointer, JSContextPointer, JSRuntimePointer, JSValueConstPointer, JSValuePointer, OwnedHeapCharPointer } from "./types-ffi";
declare namespace Emscripten {
interface FileSystemType {
}
type EnvironmentType = "WEB" | "NODE" | "SHELL" | "WORKER";
type ValueType = "number" | "string" | "array" | "boolean";
type TypeCompatibleWithC = number | string | any[] | boolean;
type WebAssemblyImports = Array<{
name: string;
kind: string;
}>;
type WebAssemblyExports = Array<{
module: string;
name: string;
kind: string;
}>;
interface CCallOpts {
async?: boolean;
}
}
/**
* Typings for the features we use to interface with our Emscripten build of
* QuickJS.
*/
interface EmscriptenModule {
/**
* Write JS `str` to HeapChar pointer.
* https://emscripten.org/docs/api_reference/preamble.js.html#stringToUTF8
*/
stringToUTF8(str: string, outPtr: OwnedHeapCharPointer, maxBytesToRead?: number): void;
/**
* HeapChar to JS string.
* https://emscripten.org/docs/api_reference/preamble.js.html#UTF8ToString
*/
UTF8ToString(ptr: BorrowedHeapCharPointer, maxBytesToRead?: number): string;
lengthBytesUTF8(str: string): number;
_malloc(size: number): number;
_free(ptr: number): void;
cwrap(ident: string, returnType: Emscripten.ValueType | null, argTypes: Emscripten.ValueType[], opts?: Emscripten.CCallOpts): (...args: any[]) => any;
HEAP8: Int8Array;
HEAP16: Int16Array;
HEAP32: Int32Array;
HEAPU8: Uint8Array;
HEAPU16: Uint16Array;
HEAPU32: Uint32Array;
HEAPF32: Float32Array;
HEAPF64: Float64Array;
TOTAL_STACK: number;
TOTAL_MEMORY: number;
FAST_MEMORY: number;
}
declare const AsyncifySleepReturnValue: unique symbol;
/** @private */
export type AsyncifySleepResult<T> = T & typeof AsyncifySleepReturnValue;
/**
* Allows us to optionally suspend the Emscripten runtime to wait for a promise.
* https://emscripten.org/docs/porting/asyncify.html#ways-to-use-async-apis-in-older-engines
* ```
* EM_JS(int, do_fetch, (), {
* return Asyncify.handleSleep(function (wakeUp) {
* out("waiting for a fetch");
* fetch("a.html").then(function (response) {
* out("got the fetch response");
* // (normally you would do something with the fetch here)
* wakeUp(42);
* });
* });
* });
* ```
* @private
*/
export interface Asyncify {
handleSleep<T>(maybeAsyncFn: (wakeUp: (result: T) => void) => void): AsyncifySleepResult<T>;
}
/**
* @private
*/
export interface EmscriptenModuleCallbacks {
callFunction: (asyncify: Asyncify | undefined, ctx: JSContextPointer, this_ptr: JSValueConstPointer, argc: number, argv: JSValueConstPointer, fn_id: number) => JSValuePointer | AsyncifySleepResult<JSValuePointer>;
loadModuleSource: (asyncify: Asyncify | undefined, rt: JSRuntimePointer, ctx: JSContextPointer, module_name: string) => BorrowedHeapCharPointer | AsyncifySleepResult<BorrowedHeapCharPointer>;
normalizeModule: (asyncify: Asyncify | undefined, rt: JSRuntimePointer, ctx: JSContextPointer, module_base_name: string, module_name: string) => BorrowedHeapCharPointer | AsyncifySleepResult<BorrowedHeapCharPointer>;
shouldInterrupt: (asyncify: Asyncify | undefined, rt: JSRuntimePointer) => 0 | 1 | AsyncifySleepResult<0 | 1>;
}
export interface QuickJSEmscriptenModule extends EmscriptenModule {
type: "sync";
callbacks: EmscriptenModuleCallbacks;
}
export interface QuickJSAsyncEmscriptenModule extends EmscriptenModule {
/** @todo Implement this field */
type: "async";
callbacks: EmscriptenModuleCallbacks;
}
export type EitherModule = QuickJSEmscriptenModule | QuickJSAsyncEmscriptenModule;
export interface EmscriptenModuleLoader<T extends EmscriptenModule> {
(): Promise<T>;
}
export {};

View File

@@ -0,0 +1,15 @@
"use strict";
// This is a subset of the Emscripten type definitions from @types/emscripten
// Project: http://kripken.github.io/emscripten-site/index.html
// Definitions by: Kensuke Matsuzaki <https://github.com/zakki>
// Periklis Tsirakidis <https://github.com/periklis>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
//
// quickjs-emscripten doesn't use the full EmscriptenModule type from @types/emscripten because:
//
// - the upstream types define many properties that don't exist on our module due
// to our build settings
// - some upstream types reference web-only ambient types like WebGL stuff, which
// we don't use.
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=emscripten-types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
import type { QuickJSContext } from "./context";
/**
* Error thrown if [[QuickJSContext.unwrapResult]] unwraps an error value that isn't an object.
*/
export declare class QuickJSUnwrapError extends Error {
cause: unknown;
context?: QuickJSContext | undefined;
name: string;
constructor(cause: unknown, context?: QuickJSContext | undefined);
}
export declare class QuickJSWrongOwner extends Error {
name: string;
}
export declare class QuickJSUseAfterFree extends Error {
name: string;
}
export declare class QuickJSNotImplemented extends Error {
name: string;
}
export declare class QuickJSAsyncifyError extends Error {
name: string;
}
export declare class QuickJSAsyncifySuspended extends Error {
name: string;
}
export declare class QuickJSMemoryLeakDetected extends Error {
name: string;
}

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSMemoryLeakDetected = exports.QuickJSAsyncifySuspended = exports.QuickJSAsyncifyError = exports.QuickJSNotImplemented = exports.QuickJSUseAfterFree = exports.QuickJSWrongOwner = exports.QuickJSUnwrapError = void 0;
/**
* Error thrown if [[QuickJSContext.unwrapResult]] unwraps an error value that isn't an object.
*/
class QuickJSUnwrapError extends Error {
constructor(cause, context) {
super(String(cause));
this.cause = cause;
this.context = context;
this.name = "QuickJSUnwrapError";
}
}
exports.QuickJSUnwrapError = QuickJSUnwrapError;
class QuickJSWrongOwner extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSWrongOwner";
}
}
exports.QuickJSWrongOwner = QuickJSWrongOwner;
class QuickJSUseAfterFree extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSUseAfterFree";
}
}
exports.QuickJSUseAfterFree = QuickJSUseAfterFree;
class QuickJSNotImplemented extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSNotImplemented";
}
}
exports.QuickJSNotImplemented = QuickJSNotImplemented;
class QuickJSAsyncifyError extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSAsyncifyError";
}
}
exports.QuickJSAsyncifyError = QuickJSAsyncifyError;
class QuickJSAsyncifySuspended extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSAsyncifySuspended";
}
}
exports.QuickJSAsyncifySuspended = QuickJSAsyncifySuspended;
class QuickJSMemoryLeakDetected extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSMemoryLeakDetected";
}
}
exports.QuickJSMemoryLeakDetected = QuickJSMemoryLeakDetected;
//# sourceMappingURL=errors.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../ts/errors.ts"],"names":[],"mappings":";;;AAEA;;GAEG;AACH,MAAa,kBAAmB,SAAQ,KAAK;IAE3C,YAAmB,KAAc,EAAS,OAAwB;QAChE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QADH,UAAK,GAAL,KAAK,CAAS;QAAS,YAAO,GAAP,OAAO,CAAiB;QADlE,SAAI,GAAG,oBAAoB,CAAA;IAG3B,CAAC;CACF;AALD,gDAKC;AAED,MAAa,iBAAkB,SAAQ,KAAK;IAA5C;;QACE,SAAI,GAAG,mBAAmB,CAAA;IAC5B,CAAC;CAAA;AAFD,8CAEC;AAED,MAAa,mBAAoB,SAAQ,KAAK;IAA9C;;QACE,SAAI,GAAG,qBAAqB,CAAA;IAC9B,CAAC;CAAA;AAFD,kDAEC;AAED,MAAa,qBAAsB,SAAQ,KAAK;IAAhD;;QACE,SAAI,GAAG,uBAAuB,CAAA;IAChC,CAAC;CAAA;AAFD,sDAEC;AAED,MAAa,oBAAqB,SAAQ,KAAK;IAA/C;;QACE,SAAI,GAAG,sBAAsB,CAAA;IAC/B,CAAC;CAAA;AAFD,oDAEC;AAED,MAAa,wBAAyB,SAAQ,KAAK;IAAnD;;QACE,SAAI,GAAG,0BAA0B,CAAA;IACnC,CAAC;CAAA;AAFD,4DAEC;AAED,MAAa,yBAA0B,SAAQ,KAAK;IAApD;;QACE,SAAI,GAAG,2BAA2B,CAAA;IACpC,CAAC;CAAA;AAFD,8DAEC","sourcesContent":["import type { QuickJSContext } from \"./context\"\n\n/**\n * Error thrown if [[QuickJSContext.unwrapResult]] unwraps an error value that isn't an object.\n */\nexport class QuickJSUnwrapError extends Error {\n name = \"QuickJSUnwrapError\"\n constructor(public cause: unknown, public context?: QuickJSContext) {\n super(String(cause))\n }\n}\n\nexport class QuickJSWrongOwner extends Error {\n name = \"QuickJSWrongOwner\"\n}\n\nexport class QuickJSUseAfterFree extends Error {\n name = \"QuickJSUseAfterFree\"\n}\n\nexport class QuickJSNotImplemented extends Error {\n name = \"QuickJSNotImplemented\"\n}\n\nexport class QuickJSAsyncifyError extends Error {\n name = \"QuickJSAsyncifyError\"\n}\n\nexport class QuickJSAsyncifySuspended extends Error {\n name = \"QuickJSAsyncifySuspended\"\n}\n\nexport class QuickJSMemoryLeakDetected extends Error {\n name = \"QuickJSMemoryLeakDetected\"\n}\n"]}

View File

@@ -0,0 +1,9 @@
/** Typescript thinks import('...js/.d.ts') needs mod.default.default */
declare function fakeUnwrapDefault<T>(mod: {
default: T;
}): T;
/** Typescript thinks import('...ts') doesn't need mod.default.default, but does */
declare function actualUnwrapDefault<T>(mod: T): T;
export declare const unwrapTypescript: typeof actualUnwrapDefault;
export declare const unwrapJavascript: typeof fakeUnwrapDefault;
export {};

View File

@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.unwrapJavascript = exports.unwrapTypescript = void 0;
/** Typescript thinks import('...js/.d.ts') needs mod.default.default */
function fakeUnwrapDefault(mod) {
// console.log("fakeUnwrapDefault", mod)
return mod.default;
}
/** Typescript thinks import('...ts') doesn't need mod.default.default, but does */
function actualUnwrapDefault(mod) {
// console.log("actualUnwrapDefault", mod)
const maybeUnwrap = mod.default;
return maybeUnwrap ?? mod;
}
// I'm not sure if this behavior is needed in all runtimes,
// or just for mocha + ts-node.
exports.unwrapTypescript = actualUnwrapDefault;
exports.unwrapJavascript = fakeUnwrapDefault;
//# sourceMappingURL=esmHelpers.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"esmHelpers.js","sourceRoot":"","sources":["../ts/esmHelpers.ts"],"names":[],"mappings":";;;AAAA,wEAAwE;AACxE,SAAS,iBAAiB,CAAI,GAAmB;IAC/C,wCAAwC;IACxC,OAAO,GAAG,CAAC,OAAY,CAAA;AACzB,CAAC;AAED,mFAAmF;AACnF,SAAS,mBAAmB,CAAI,GAAM;IACpC,0CAA0C;IAC1C,MAAM,WAAW,GAAI,GAAW,CAAC,OAAO,CAAA;IACxC,OAAO,WAAW,IAAI,GAAG,CAAA;AAC3B,CAAC;AAED,2DAA2D;AAC3D,+BAA+B;AAClB,QAAA,gBAAgB,GAAG,mBAAmB,CAAA;AACtC,QAAA,gBAAgB,GAAG,iBAAiB,CAAA","sourcesContent":["/** Typescript thinks import('...js/.d.ts') needs mod.default.default */\nfunction fakeUnwrapDefault<T>(mod: { default: T }): T {\n // console.log(\"fakeUnwrapDefault\", mod)\n return mod.default as T\n}\n\n/** Typescript thinks import('...ts') doesn't need mod.default.default, but does */\nfunction actualUnwrapDefault<T>(mod: T): T {\n // console.log(\"actualUnwrapDefault\", mod)\n const maybeUnwrap = (mod as any).default\n return maybeUnwrap ?? mod\n}\n\n// I'm not sure if this behavior is needed in all runtimes,\n// or just for mocha + ts-node.\nexport const unwrapTypescript = actualUnwrapDefault\nexport const unwrapJavascript = fakeUnwrapDefault\n"]}

View File

@@ -0,0 +1,5 @@
export = QuickJSRaw;
declare function QuickJSRaw(QuickJSRaw?: {}): any;
declare namespace QuickJSRaw {
export { QuickJSRaw };
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,67 @@
import { QuickJSEmscriptenModule } from "../emscripten-types";
import { JSRuntimePointer, JSContextPointer, JSContextPointerPointer, JSValuePointer, JSValueConstPointer, JSValuePointerPointer, JSValueConstPointerPointer, BorrowedHeapCharPointer, OwnedHeapCharPointer, JSBorrowedCharPointer, JSVoidPointer, EvalFlags, EvalDetectModule } from "../types-ffi";
/**
* Low-level FFI bindings to QuickJS's Emscripten module.
* See instead [[QuickJSContext]], the public Javascript interface exposed by this
* library.
*
* @unstable The FFI interface is considered private and may change.
*/
export declare class QuickJSFFI {
private module;
constructor(module: QuickJSEmscriptenModule);
/** Set at compile time. */
readonly DEBUG = false;
QTS_Throw: (ctx: JSContextPointer, error: JSValuePointer | JSValueConstPointer) => JSValuePointer;
QTS_NewError: (ctx: JSContextPointer) => JSValuePointer;
QTS_RuntimeSetMemoryLimit: (rt: JSRuntimePointer, limit: number) => void;
QTS_RuntimeComputeMemoryUsage: (rt: JSRuntimePointer, ctx: JSContextPointer) => JSValuePointer;
QTS_RuntimeDumpMemoryUsage: (rt: JSRuntimePointer) => OwnedHeapCharPointer;
QTS_RecoverableLeakCheck: () => number;
QTS_BuildIsSanitizeLeak: () => number;
QTS_RuntimeSetMaxStackSize: (rt: JSRuntimePointer, stack_size: number) => void;
QTS_GetUndefined: () => JSValueConstPointer;
QTS_GetNull: () => JSValueConstPointer;
QTS_GetFalse: () => JSValueConstPointer;
QTS_GetTrue: () => JSValueConstPointer;
QTS_NewRuntime: () => JSRuntimePointer;
QTS_FreeRuntime: (rt: JSRuntimePointer) => void;
QTS_NewContext: (rt: JSRuntimePointer) => JSContextPointer;
QTS_FreeContext: (ctx: JSContextPointer) => void;
QTS_FreeValuePointer: (ctx: JSContextPointer, value: JSValuePointer) => void;
QTS_FreeValuePointerRuntime: (rt: JSRuntimePointer, value: JSValuePointer) => void;
QTS_FreeVoidPointer: (ctx: JSContextPointer, ptr: JSVoidPointer) => void;
QTS_FreeCString: (ctx: JSContextPointer, str: JSBorrowedCharPointer) => void;
QTS_DupValuePointer: (ctx: JSContextPointer, val: JSValuePointer | JSValueConstPointer) => JSValuePointer;
QTS_NewObject: (ctx: JSContextPointer) => JSValuePointer;
QTS_NewObjectProto: (ctx: JSContextPointer, proto: JSValuePointer | JSValueConstPointer) => JSValuePointer;
QTS_NewArray: (ctx: JSContextPointer) => JSValuePointer;
QTS_NewFloat64: (ctx: JSContextPointer, num: number) => JSValuePointer;
QTS_GetFloat64: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => number;
QTS_NewString: (ctx: JSContextPointer, string: BorrowedHeapCharPointer) => JSValuePointer;
QTS_GetString: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => JSBorrowedCharPointer;
QTS_NewSymbol: (ctx: JSContextPointer, description: BorrowedHeapCharPointer, isGlobal: number) => JSValuePointer;
QTS_GetSymbolDescriptionOrKey: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => JSBorrowedCharPointer;
QTS_IsGlobalSymbol: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => number;
QTS_IsJobPending: (rt: JSRuntimePointer) => number;
QTS_ExecutePendingJob: (rt: JSRuntimePointer, maxJobsToExecute: number, lastJobContext: JSContextPointerPointer) => JSValuePointer;
QTS_GetProp: (ctx: JSContextPointer, this_val: JSValuePointer | JSValueConstPointer, prop_name: JSValuePointer | JSValueConstPointer) => JSValuePointer;
QTS_SetProp: (ctx: JSContextPointer, this_val: JSValuePointer | JSValueConstPointer, prop_name: JSValuePointer | JSValueConstPointer, prop_value: JSValuePointer | JSValueConstPointer) => void;
QTS_DefineProp: (ctx: JSContextPointer, this_val: JSValuePointer | JSValueConstPointer, prop_name: JSValuePointer | JSValueConstPointer, prop_value: JSValuePointer | JSValueConstPointer, get: JSValuePointer | JSValueConstPointer, set: JSValuePointer | JSValueConstPointer, configurable: boolean, enumerable: boolean, has_value: boolean) => void;
QTS_Call: (ctx: JSContextPointer, func_obj: JSValuePointer | JSValueConstPointer, this_obj: JSValuePointer | JSValueConstPointer, argc: number, argv_ptrs: JSValueConstPointerPointer) => JSValuePointer;
QTS_ResolveException: (ctx: JSContextPointer, maybe_exception: JSValuePointer) => JSValuePointer;
QTS_Dump: (ctx: JSContextPointer, obj: JSValuePointer | JSValueConstPointer) => JSBorrowedCharPointer;
QTS_Eval: (ctx: JSContextPointer, js_code: BorrowedHeapCharPointer, filename: string, detectModule: EvalDetectModule, evalFlags: EvalFlags) => JSValuePointer;
QTS_Typeof: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => OwnedHeapCharPointer;
QTS_GetGlobalObject: (ctx: JSContextPointer) => JSValuePointer;
QTS_NewPromiseCapability: (ctx: JSContextPointer, resolve_funcs_out: JSValuePointerPointer) => JSValuePointer;
QTS_TestStringArg: (string: string) => void;
QTS_BuildIsDebug: () => number;
QTS_BuildIsAsyncify: () => number;
QTS_NewFunction: (ctx: JSContextPointer, func_id: number, name: string) => JSValuePointer;
QTS_ArgvGetJSValueConstPointer: (argv: JSValuePointer | JSValueConstPointer, index: number) => JSValueConstPointer;
QTS_RuntimeEnableInterruptHandler: (rt: JSRuntimePointer) => void;
QTS_RuntimeDisableInterruptHandler: (rt: JSRuntimePointer) => void;
QTS_RuntimeEnableModuleLoader: (rt: JSRuntimePointer, use_custom_normalize: number) => void;
QTS_RuntimeDisableModuleLoader: (rt: JSRuntimePointer) => void;
}

View File

@@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSFFI = void 0;
/**
* Low-level FFI bindings to QuickJS's Emscripten module.
* See instead [[QuickJSContext]], the public Javascript interface exposed by this
* library.
*
* @unstable The FFI interface is considered private and may change.
*/
class QuickJSFFI {
constructor(module) {
this.module = module;
/** Set at compile time. */
this.DEBUG = false;
this.QTS_Throw = this.module.cwrap("QTS_Throw", "number", ["number", "number"]);
this.QTS_NewError = this.module.cwrap("QTS_NewError", "number", ["number"]);
this.QTS_RuntimeSetMemoryLimit = this.module.cwrap("QTS_RuntimeSetMemoryLimit", null, ["number", "number"]);
this.QTS_RuntimeComputeMemoryUsage = this.module.cwrap("QTS_RuntimeComputeMemoryUsage", "number", ["number", "number"]);
this.QTS_RuntimeDumpMemoryUsage = this.module.cwrap("QTS_RuntimeDumpMemoryUsage", "number", ["number"]);
this.QTS_RecoverableLeakCheck = this.module.cwrap("QTS_RecoverableLeakCheck", "number", []);
this.QTS_BuildIsSanitizeLeak = this.module.cwrap("QTS_BuildIsSanitizeLeak", "number", []);
this.QTS_RuntimeSetMaxStackSize = this.module.cwrap("QTS_RuntimeSetMaxStackSize", null, ["number", "number"]);
this.QTS_GetUndefined = this.module.cwrap("QTS_GetUndefined", "number", []);
this.QTS_GetNull = this.module.cwrap("QTS_GetNull", "number", []);
this.QTS_GetFalse = this.module.cwrap("QTS_GetFalse", "number", []);
this.QTS_GetTrue = this.module.cwrap("QTS_GetTrue", "number", []);
this.QTS_NewRuntime = this.module.cwrap("QTS_NewRuntime", "number", []);
this.QTS_FreeRuntime = this.module.cwrap("QTS_FreeRuntime", null, ["number"]);
this.QTS_NewContext = this.module.cwrap("QTS_NewContext", "number", ["number"]);
this.QTS_FreeContext = this.module.cwrap("QTS_FreeContext", null, ["number"]);
this.QTS_FreeValuePointer = this.module.cwrap("QTS_FreeValuePointer", null, ["number", "number"]);
this.QTS_FreeValuePointerRuntime = this.module.cwrap("QTS_FreeValuePointerRuntime", null, ["number", "number"]);
this.QTS_FreeVoidPointer = this.module.cwrap("QTS_FreeVoidPointer", null, ["number", "number"]);
this.QTS_FreeCString = this.module.cwrap("QTS_FreeCString", null, ["number", "number"]);
this.QTS_DupValuePointer = this.module.cwrap("QTS_DupValuePointer", "number", ["number", "number"]);
this.QTS_NewObject = this.module.cwrap("QTS_NewObject", "number", ["number"]);
this.QTS_NewObjectProto = this.module.cwrap("QTS_NewObjectProto", "number", ["number", "number"]);
this.QTS_NewArray = this.module.cwrap("QTS_NewArray", "number", ["number"]);
this.QTS_NewFloat64 = this.module.cwrap("QTS_NewFloat64", "number", ["number", "number"]);
this.QTS_GetFloat64 = this.module.cwrap("QTS_GetFloat64", "number", ["number", "number"]);
this.QTS_NewString = this.module.cwrap("QTS_NewString", "number", ["number", "number"]);
this.QTS_GetString = this.module.cwrap("QTS_GetString", "number", ["number", "number"]);
this.QTS_NewSymbol = this.module.cwrap("QTS_NewSymbol", "number", ["number", "number", "number"]);
this.QTS_GetSymbolDescriptionOrKey = this.module.cwrap("QTS_GetSymbolDescriptionOrKey", "number", ["number", "number"]);
this.QTS_IsGlobalSymbol = this.module.cwrap("QTS_IsGlobalSymbol", "number", ["number", "number"]);
this.QTS_IsJobPending = this.module.cwrap("QTS_IsJobPending", "number", ["number"]);
this.QTS_ExecutePendingJob = this.module.cwrap("QTS_ExecutePendingJob", "number", ["number", "number", "number"]);
this.QTS_GetProp = this.module.cwrap("QTS_GetProp", "number", ["number", "number", "number"]);
this.QTS_SetProp = this.module.cwrap("QTS_SetProp", null, ["number", "number", "number", "number"]);
this.QTS_DefineProp = this.module.cwrap("QTS_DefineProp", null, ["number", "number", "number", "number", "number", "number", "boolean", "boolean", "boolean"]);
this.QTS_Call = this.module.cwrap("QTS_Call", "number", ["number", "number", "number", "number", "number"]);
this.QTS_ResolveException = this.module.cwrap("QTS_ResolveException", "number", ["number", "number"]);
this.QTS_Dump = this.module.cwrap("QTS_Dump", "number", ["number", "number"]);
this.QTS_Eval = this.module.cwrap("QTS_Eval", "number", ["number", "number", "string", "number", "number"]);
this.QTS_Typeof = this.module.cwrap("QTS_Typeof", "number", ["number", "number"]);
this.QTS_GetGlobalObject = this.module.cwrap("QTS_GetGlobalObject", "number", ["number"]);
this.QTS_NewPromiseCapability = this.module.cwrap("QTS_NewPromiseCapability", "number", ["number", "number"]);
this.QTS_TestStringArg = this.module.cwrap("QTS_TestStringArg", null, ["string"]);
this.QTS_BuildIsDebug = this.module.cwrap("QTS_BuildIsDebug", "number", []);
this.QTS_BuildIsAsyncify = this.module.cwrap("QTS_BuildIsAsyncify", "number", []);
this.QTS_NewFunction = this.module.cwrap("QTS_NewFunction", "number", ["number", "number", "string"]);
this.QTS_ArgvGetJSValueConstPointer = this.module.cwrap("QTS_ArgvGetJSValueConstPointer", "number", ["number", "number"]);
this.QTS_RuntimeEnableInterruptHandler = this.module.cwrap("QTS_RuntimeEnableInterruptHandler", null, ["number"]);
this.QTS_RuntimeDisableInterruptHandler = this.module.cwrap("QTS_RuntimeDisableInterruptHandler", null, ["number"]);
this.QTS_RuntimeEnableModuleLoader = this.module.cwrap("QTS_RuntimeEnableModuleLoader", null, ["number", "number"]);
this.QTS_RuntimeDisableModuleLoader = this.module.cwrap("QTS_RuntimeDisableModuleLoader", null, ["number"]);
}
}
exports.QuickJSFFI = QuickJSFFI;
//# sourceMappingURL=ffi.WASM_RELEASE_SYNC.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,75 @@
import type { QuickJSWASMModule } from "./module";
import type { QuickJSRuntime, InterruptHandler } from "./runtime";
import type { QuickJSContext } from "./context";
export type { QuickJSWASMModule, QuickJSContext, QuickJSRuntime };
import type { QuickJSAsyncWASMModule } from "./module-asyncify";
import type { QuickJSAsyncRuntime } from "./runtime-asyncify";
import type { QuickJSAsyncContext, AsyncFunctionImplementation } from "./context-asyncify";
import { AsyncRuntimeOptions, ContextOptions } from "./types";
export type { QuickJSAsyncContext, QuickJSAsyncRuntime, QuickJSAsyncWASMModule, AsyncFunctionImplementation, };
import { newQuickJSWASMModule, newQuickJSAsyncWASMModule, DEBUG_ASYNC, DEBUG_SYNC, RELEASE_ASYNC, RELEASE_SYNC, SyncBuildVariant, AsyncBuildVariant } from "./variants";
export { newQuickJSWASMModule, newQuickJSAsyncWASMModule, DEBUG_ASYNC, DEBUG_SYNC, RELEASE_ASYNC, RELEASE_SYNC, SyncBuildVariant, AsyncBuildVariant, };
export * from "./vm-interface";
export * from "./lifetime";
/** Collects the informative errors this library may throw. */
export * as errors from "./errors";
export * from "./deferred-promise";
export * from "./module-test";
export type { StaticJSValue, JSValueConst, JSValue, QuickJSHandle, ContextOptions, ContextEvalOptions, RuntimeOptions, AsyncRuntimeOptions, RuntimeOptionsBase, JSModuleLoader, JSModuleLoadResult, JSModuleLoaderAsync, JSModuleLoadSuccess, JSModuleLoadFailure, JSModuleNormalizer, JSModuleNormalizerAsync, JSModuleNormalizeResult, JSModuleNormalizeFailure, JSModuleNormalizeSuccess, } from "./types";
export type { ModuleEvalOptions } from "./module";
export type { InterruptHandler, ExecutePendingJobsResult } from "./runtime";
export type { QuickJSPropertyKey } from "./context";
/**
* Get a shared singleton {@link QuickJSWASMModule}. Use this to evaluate code
* or create Javascript environments.
*
* This is the top-level entrypoint for the quickjs-emscripten library.
*
* If you need strictest possible isolation guarantees, you may create a
* separate {@link QuickJSWASMModule} via {@link newQuickJSWASMModule}.
*
* To work with the asyncified version of this library, see these functions:
*
* - {@link newAsyncRuntime}.
* - {@link newAsyncContext}.
* - {@link newQuickJSAsyncWASMModule}.
*/
export declare function getQuickJS(): Promise<QuickJSWASMModule>;
/**
* Provides synchronous access to the shared {@link QuickJSWASMModule} instance returned by {@link getQuickJS}, as long as
* least once.
* @throws If called before `getQuickJS` resolves.
*/
export declare function getQuickJSSync(): QuickJSWASMModule;
/**
* Create a new [[QuickJSAsyncRuntime]] in a separate WebAssembly module.
*
* Each runtime is isolated in a separate WebAssembly module, so that errors in
* one runtime cannot contaminate another runtime, and each runtime can execute
* an asynchronous action without conflicts.
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
export declare function newAsyncRuntime(options?: AsyncRuntimeOptions): Promise<QuickJSAsyncRuntime>;
/**
* Create a new [[QuickJSAsyncContext]] (with an associated runtime) in an
* separate WebAssembly module.
*
* Each context is isolated in a separate WebAssembly module, so that errors in
* one runtime cannot contaminate another runtime, and each runtime can execute
* an asynchronous action without conflicts.
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
export declare function newAsyncContext(options?: ContextOptions): Promise<QuickJSAsyncContext>;
/**
* Returns an interrupt handler that interrupts Javascript execution after a deadline time.
*
* @param deadline - Interrupt execution if it's still running after this time.
* Number values are compared against `Date.now()`
*/
export declare function shouldInterruptAfterDeadline(deadline: Date | number): InterruptHandler;

View File

@@ -0,0 +1,128 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.shouldInterruptAfterDeadline = exports.newAsyncContext = exports.newAsyncRuntime = exports.getQuickJSSync = exports.getQuickJS = exports.errors = exports.RELEASE_SYNC = exports.RELEASE_ASYNC = exports.DEBUG_SYNC = exports.DEBUG_ASYNC = exports.newQuickJSAsyncWASMModule = exports.newQuickJSWASMModule = void 0;
// Build variants
const variants_1 = require("./variants");
Object.defineProperty(exports, "newQuickJSWASMModule", { enumerable: true, get: function () { return variants_1.newQuickJSWASMModule; } });
Object.defineProperty(exports, "newQuickJSAsyncWASMModule", { enumerable: true, get: function () { return variants_1.newQuickJSAsyncWASMModule; } });
Object.defineProperty(exports, "DEBUG_ASYNC", { enumerable: true, get: function () { return variants_1.DEBUG_ASYNC; } });
Object.defineProperty(exports, "DEBUG_SYNC", { enumerable: true, get: function () { return variants_1.DEBUG_SYNC; } });
Object.defineProperty(exports, "RELEASE_ASYNC", { enumerable: true, get: function () { return variants_1.RELEASE_ASYNC; } });
Object.defineProperty(exports, "RELEASE_SYNC", { enumerable: true, get: function () { return variants_1.RELEASE_SYNC; } });
// Export helpers
__exportStar(require("./vm-interface"), exports);
__exportStar(require("./lifetime"), exports);
/** Collects the informative errors this library may throw. */
exports.errors = __importStar(require("./errors"));
__exportStar(require("./deferred-promise"), exports);
__exportStar(require("./module-test"), exports);
let singleton = undefined;
let singletonPromise = undefined;
/**
* Get a shared singleton {@link QuickJSWASMModule}. Use this to evaluate code
* or create Javascript environments.
*
* This is the top-level entrypoint for the quickjs-emscripten library.
*
* If you need strictest possible isolation guarantees, you may create a
* separate {@link QuickJSWASMModule} via {@link newQuickJSWASMModule}.
*
* To work with the asyncified version of this library, see these functions:
*
* - {@link newAsyncRuntime}.
* - {@link newAsyncContext}.
* - {@link newQuickJSAsyncWASMModule}.
*/
async function getQuickJS() {
singletonPromise ?? (singletonPromise = (0, variants_1.newQuickJSWASMModule)().then((instance) => {
singleton = instance;
return instance;
}));
return await singletonPromise;
}
exports.getQuickJS = getQuickJS;
/**
* Provides synchronous access to the shared {@link QuickJSWASMModule} instance returned by {@link getQuickJS}, as long as
* least once.
* @throws If called before `getQuickJS` resolves.
*/
function getQuickJSSync() {
if (!singleton) {
throw new Error("QuickJS not initialized. Await getQuickJS() at least once.");
}
return singleton;
}
exports.getQuickJSSync = getQuickJSSync;
/**
* Create a new [[QuickJSAsyncRuntime]] in a separate WebAssembly module.
*
* Each runtime is isolated in a separate WebAssembly module, so that errors in
* one runtime cannot contaminate another runtime, and each runtime can execute
* an asynchronous action without conflicts.
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
async function newAsyncRuntime(options) {
const module = await (0, variants_1.newQuickJSAsyncWASMModule)();
return module.newRuntime(options);
}
exports.newAsyncRuntime = newAsyncRuntime;
/**
* Create a new [[QuickJSAsyncContext]] (with an associated runtime) in an
* separate WebAssembly module.
*
* Each context is isolated in a separate WebAssembly module, so that errors in
* one runtime cannot contaminate another runtime, and each runtime can execute
* an asynchronous action without conflicts.
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
async function newAsyncContext(options) {
const module = await (0, variants_1.newQuickJSAsyncWASMModule)();
return module.newContext(options);
}
exports.newAsyncContext = newAsyncContext;
/**
* Returns an interrupt handler that interrupts Javascript execution after a deadline time.
*
* @param deadline - Interrupt execution if it's still running after this time.
* Number values are compared against `Date.now()`
*/
function shouldInterruptAfterDeadline(deadline) {
const deadlineAsNumber = typeof deadline === "number" ? deadline : deadline.getTime();
return function () {
return Date.now() > deadlineAsNumber;
};
}
exports.shouldInterruptAfterDeadline = shouldInterruptAfterDeadline;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,115 @@
import { MaybeAsyncBlock } from "./asyncify-helpers";
import type { QuickJSHandle } from "./types";
/**
* An object that can be disposed.
* [[Lifetime]] is the canonical implementation of Disposable.
* Use [[Scope]] to manage cleaning up multiple disposables.
*/
export interface Disposable {
/**
* Dispose of the underlying resources used by this object.
*/
dispose(): void;
/**
* @returns true if the object is alive
* @returns false after the object has been [[dispose]]d
*/
alive: boolean;
}
/**
* A lifetime prevents access to a value after the lifetime has been
* [[dispose]]ed.
*
* Typically, quickjs-emscripten uses Lifetimes to protect C memory pointers.
*/
export declare class Lifetime<T, TCopy = never, Owner = never> implements Disposable {
protected readonly _value: T;
protected readonly copier?: ((value: T | TCopy) => TCopy) | undefined;
protected readonly disposer?: ((value: T | TCopy) => void) | undefined;
protected readonly _owner?: Owner | undefined;
protected _alive: boolean;
protected _constructorStack: string | undefined;
/**
* When the Lifetime is disposed, it will call `disposer(_value)`. Use the
* disposer function to implement whatever cleanup needs to happen at the end
* of `value`'s lifetime.
*
* `_owner` is not used or controlled by the lifetime. It's just metadata for
* the creator.
*/
constructor(_value: T, copier?: ((value: T | TCopy) => TCopy) | undefined, disposer?: ((value: T | TCopy) => void) | undefined, _owner?: Owner | undefined);
get alive(): boolean;
/**
* The value this Lifetime protects. You must never retain the value - it
* may become invalid, leading to memory errors.
*
* @throws If the lifetime has been [[dispose]]d already.
*/
get value(): T;
get owner(): Owner | undefined;
get dupable(): boolean;
/**
* Create a new handle pointing to the same [[value]].
*/
dup(): Lifetime<TCopy, TCopy, Owner>;
/**
* Call `map` with this lifetime, then dispose the lifetime.
* @return the result of `map(this)`.
*/
consume<O>(map: (lifetime: this) => O): O;
consume<O>(map: (lifetime: QuickJSHandle) => O): O;
/**
* Dispose of [[value]] and perform cleanup.
*/
dispose(): void;
private assertAlive;
}
/**
* A Lifetime that lives forever. Used for constants.
*/
export declare class StaticLifetime<T, Owner = never> extends Lifetime<T, T, Owner> {
constructor(value: T, owner?: Owner);
get dupable(): boolean;
dup(): this;
dispose(): void;
}
/**
* A Lifetime that does not own its `value`. A WeakLifetime never calls its
* `disposer` function, but can be `dup`ed to produce regular lifetimes that
* do.
*
* Used for function arguments.
*/
export declare class WeakLifetime<T, TCopy = never, Owner = never> extends Lifetime<T, TCopy, Owner> {
constructor(value: T, copier?: (value: T | TCopy) => TCopy, disposer?: (value: TCopy) => void, owner?: Owner);
dispose(): void;
}
/**
* Scope helps reduce the burden of manually tracking and disposing of
* Lifetimes. See [[withScope]]. and [[withScopeAsync]].
*/
export declare class Scope implements Disposable {
/**
* Run `block` with a new Scope instance that will be disposed after the block returns.
* Inside `block`, call `scope.manage` on each lifetime you create to have the lifetime
* automatically disposed after the block returns.
*
* @warning Do not use with async functions. Instead, use [[withScopeAsync]].
*/
static withScope<R>(block: (scope: Scope) => R): R;
static withScopeMaybeAsync<Return, This, Yielded>(_this: This, block: MaybeAsyncBlock<Return, This, Yielded, [Scope]>): Return | Promise<Return>;
/**
* Run `block` with a new Scope instance that will be disposed after the
* block's returned promise settles. Inside `block`, call `scope.manage` on each
* lifetime you create to have the lifetime automatically disposed after the
* block returns.
*/
static withScopeAsync<R>(block: (scope: Scope) => Promise<R>): Promise<R>;
private _disposables;
/**
* Track `lifetime` so that it is disposed when this scope is disposed.
*/
manage<T extends Disposable>(lifetime: T): T;
get alive(): boolean;
dispose(): void;
}

View File

@@ -0,0 +1,227 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Scope = exports.WeakLifetime = exports.StaticLifetime = exports.Lifetime = void 0;
const asyncify_helpers_1 = require("./asyncify-helpers");
const debug_1 = require("./debug");
const errors_1 = require("./errors");
/**
* A lifetime prevents access to a value after the lifetime has been
* [[dispose]]ed.
*
* Typically, quickjs-emscripten uses Lifetimes to protect C memory pointers.
*/
class Lifetime {
/**
* When the Lifetime is disposed, it will call `disposer(_value)`. Use the
* disposer function to implement whatever cleanup needs to happen at the end
* of `value`'s lifetime.
*
* `_owner` is not used or controlled by the lifetime. It's just metadata for
* the creator.
*/
constructor(_value, copier, disposer, _owner) {
this._value = _value;
this.copier = copier;
this.disposer = disposer;
this._owner = _owner;
this._alive = true;
this._constructorStack = debug_1.QTS_DEBUG ? new Error("Lifetime constructed").stack : undefined;
}
get alive() {
return this._alive;
}
/**
* The value this Lifetime protects. You must never retain the value - it
* may become invalid, leading to memory errors.
*
* @throws If the lifetime has been [[dispose]]d already.
*/
get value() {
this.assertAlive();
return this._value;
}
get owner() {
return this._owner;
}
get dupable() {
return !!this.copier;
}
/**
* Create a new handle pointing to the same [[value]].
*/
dup() {
this.assertAlive();
if (!this.copier) {
throw new Error("Non-dupable lifetime");
}
return new Lifetime(this.copier(this._value), this.copier, this.disposer, this._owner);
}
consume(map) {
this.assertAlive();
const result = map(this);
this.dispose();
return result;
}
/**
* Dispose of [[value]] and perform cleanup.
*/
dispose() {
this.assertAlive();
if (this.disposer) {
this.disposer(this._value);
}
this._alive = false;
}
assertAlive() {
if (!this.alive) {
if (this._constructorStack) {
throw new errors_1.QuickJSUseAfterFree(`Lifetime not alive\n${this._constructorStack}\nLifetime used`);
}
throw new errors_1.QuickJSUseAfterFree("Lifetime not alive");
}
}
}
exports.Lifetime = Lifetime;
/**
* A Lifetime that lives forever. Used for constants.
*/
class StaticLifetime extends Lifetime {
constructor(value, owner) {
super(value, undefined, undefined, owner);
}
// Static lifetime doesn't need a copier to be copiable
get dupable() {
return true;
}
// Copy returns the same instance.
dup() {
return this;
}
// Dispose does nothing.
dispose() { }
}
exports.StaticLifetime = StaticLifetime;
/**
* A Lifetime that does not own its `value`. A WeakLifetime never calls its
* `disposer` function, but can be `dup`ed to produce regular lifetimes that
* do.
*
* Used for function arguments.
*/
class WeakLifetime extends Lifetime {
constructor(value, copier, disposer, owner) {
// We don't care if the disposer doesn't support freeing T
super(value, copier, disposer, owner);
}
dispose() {
this._alive = false;
}
}
exports.WeakLifetime = WeakLifetime;
function scopeFinally(scope, blockError) {
// console.log('scopeFinally', scope, blockError)
let disposeError;
try {
scope.dispose();
}
catch (error) {
disposeError = error;
}
if (blockError && disposeError) {
Object.assign(blockError, {
message: `${blockError.message}\n Then, failed to dispose scope: ${disposeError.message}`,
disposeError,
});
throw blockError;
}
if (blockError || disposeError) {
throw blockError || disposeError;
}
}
/**
* Scope helps reduce the burden of manually tracking and disposing of
* Lifetimes. See [[withScope]]. and [[withScopeAsync]].
*/
class Scope {
constructor() {
this._disposables = new Lifetime(new Set());
}
/**
* Run `block` with a new Scope instance that will be disposed after the block returns.
* Inside `block`, call `scope.manage` on each lifetime you create to have the lifetime
* automatically disposed after the block returns.
*
* @warning Do not use with async functions. Instead, use [[withScopeAsync]].
*/
static withScope(block) {
const scope = new Scope();
let blockError;
try {
return block(scope);
}
catch (error) {
blockError = error;
throw error;
}
finally {
scopeFinally(scope, blockError);
}
}
static withScopeMaybeAsync(_this, block) {
return (0, asyncify_helpers_1.maybeAsync)(undefined, function* (awaited) {
const scope = new Scope();
let blockError;
try {
return yield* awaited.of(block.call(_this, awaited, scope));
}
catch (error) {
blockError = error;
throw error;
}
finally {
scopeFinally(scope, blockError);
}
});
}
/**
* Run `block` with a new Scope instance that will be disposed after the
* block's returned promise settles. Inside `block`, call `scope.manage` on each
* lifetime you create to have the lifetime automatically disposed after the
* block returns.
*/
static async withScopeAsync(block) {
const scope = new Scope();
let blockError;
try {
return await block(scope);
}
catch (error) {
blockError = error;
throw error;
}
finally {
scopeFinally(scope, blockError);
}
}
/**
* Track `lifetime` so that it is disposed when this scope is disposed.
*/
manage(lifetime) {
this._disposables.value.add(lifetime);
return lifetime;
}
get alive() {
return this._disposables.alive;
}
dispose() {
const lifetimes = Array.from(this._disposables.value.values()).reverse();
for (const lifetime of lifetimes) {
if (lifetime.alive) {
lifetime.dispose();
}
}
this._disposables.dispose();
}
}
exports.Scope = Scope;
//# sourceMappingURL=lifetime.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
import { EitherModule } from "./emscripten-types";
import { OwnedHeapCharPointer, JSContextPointerPointer, JSValueConstPointerPointer, JSValuePointerPointer } from "./types-ffi";
import { Lifetime } from "./lifetime";
import { QuickJSHandle } from "./types";
/**
* @private
*/
export declare class ModuleMemory {
module: EitherModule;
constructor(module: EitherModule);
toPointerArray(handleArray: QuickJSHandle[]): Lifetime<JSValueConstPointerPointer>;
newMutablePointerArray<T extends JSContextPointerPointer | JSValuePointerPointer>(length: number): Lifetime<{
typedArray: Int32Array;
ptr: T;
}>;
newHeapCharPointer(string: string): Lifetime<OwnedHeapCharPointer>;
consumeHeapCharPointer(ptr: OwnedHeapCharPointer): string;
}

View File

@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleMemory = void 0;
const lifetime_1 = require("./lifetime");
/**
* @private
*/
class ModuleMemory {
constructor(module) {
this.module = module;
}
toPointerArray(handleArray) {
const typedArray = new Int32Array(handleArray.map((handle) => handle.value));
const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT;
const ptr = this.module._malloc(numBytes);
var heapBytes = new Uint8Array(this.module.HEAPU8.buffer, ptr, numBytes);
heapBytes.set(new Uint8Array(typedArray.buffer));
return new lifetime_1.Lifetime(ptr, undefined, (ptr) => this.module._free(ptr));
}
newMutablePointerArray(length) {
const zeros = new Int32Array(new Array(length).fill(0));
const numBytes = zeros.length * zeros.BYTES_PER_ELEMENT;
const ptr = this.module._malloc(numBytes);
const typedArray = new Int32Array(this.module.HEAPU8.buffer, ptr, length);
typedArray.set(zeros);
return new lifetime_1.Lifetime({ typedArray, ptr }, undefined, (value) => this.module._free(value.ptr));
}
newHeapCharPointer(string) {
const numBytes = this.module.lengthBytesUTF8(string) + 1;
const ptr = this.module._malloc(numBytes);
this.module.stringToUTF8(string, ptr, numBytes);
return new lifetime_1.Lifetime(ptr, undefined, (value) => this.module._free(value));
}
consumeHeapCharPointer(ptr) {
const str = this.module.UTF8ToString(ptr);
this.module._free(ptr);
return str;
}
}
exports.ModuleMemory = ModuleMemory;
//# sourceMappingURL=memory.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../ts/memory.ts"],"names":[],"mappings":";;;AAOA,yCAAqC;AAGrC;;GAEG;AACH,MAAa,YAAY;IACvB,YAAmB,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAAG,CAAC;IAE3C,cAAc,CAAC,WAA4B;QACzC,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAC5E,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,GAAG,UAAU,CAAC,iBAAiB,CAAA;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAA+B,CAAA;QACvE,IAAI,SAAS,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;QACxE,SAAS,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;QAChD,OAAO,IAAI,mBAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,CAAC;IAED,sBAAsB,CACpB,MAAc;QAEd,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QACvD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,iBAAiB,CAAA;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAM,CAAA;QAC9C,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAA;QACzE,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACrB,OAAO,IAAI,mBAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9F,CAAC;IAED,kBAAkB,CAAC,MAAc;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACxD,MAAM,GAAG,GAAyB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAyB,CAAA;QACvF,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;QAC/C,OAAO,IAAI,mBAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;IAC1E,CAAC;IAED,sBAAsB,CAAC,GAAyB;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;QACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACtB,OAAO,GAAG,CAAA;IACZ,CAAC;CACF;AAnCD,oCAmCC","sourcesContent":["import { EitherModule } from \"./emscripten-types\"\nimport {\n OwnedHeapCharPointer,\n JSContextPointerPointer,\n JSValueConstPointerPointer,\n JSValuePointerPointer,\n} from \"./types-ffi\"\nimport { Lifetime } from \"./lifetime\"\nimport { EitherFFI, QuickJSHandle } from \"./types\"\n\n/**\n * @private\n */\nexport class ModuleMemory {\n constructor(public module: EitherModule) {}\n\n toPointerArray(handleArray: QuickJSHandle[]): Lifetime<JSValueConstPointerPointer> {\n const typedArray = new Int32Array(handleArray.map((handle) => handle.value))\n const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT\n const ptr = this.module._malloc(numBytes) as JSValueConstPointerPointer\n var heapBytes = new Uint8Array(this.module.HEAPU8.buffer, ptr, numBytes)\n heapBytes.set(new Uint8Array(typedArray.buffer))\n return new Lifetime(ptr, undefined, (ptr) => this.module._free(ptr))\n }\n\n newMutablePointerArray<T extends JSContextPointerPointer | JSValuePointerPointer>(\n length: number\n ): Lifetime<{ typedArray: Int32Array; ptr: T }> {\n const zeros = new Int32Array(new Array(length).fill(0))\n const numBytes = zeros.length * zeros.BYTES_PER_ELEMENT\n const ptr = this.module._malloc(numBytes) as T\n const typedArray = new Int32Array(this.module.HEAPU8.buffer, ptr, length)\n typedArray.set(zeros)\n return new Lifetime({ typedArray, ptr }, undefined, (value) => this.module._free(value.ptr))\n }\n\n newHeapCharPointer(string: string): Lifetime<OwnedHeapCharPointer> {\n const numBytes = this.module.lengthBytesUTF8(string) + 1\n const ptr: OwnedHeapCharPointer = this.module._malloc(numBytes) as OwnedHeapCharPointer\n this.module.stringToUTF8(string, ptr, numBytes)\n return new Lifetime(ptr, undefined, (value) => this.module._free(value))\n }\n\n consumeHeapCharPointer(ptr: OwnedHeapCharPointer): string {\n const str = this.module.UTF8ToString(ptr)\n this.module._free(ptr)\n return str\n }\n}\n"]}

View File

@@ -0,0 +1,53 @@
import { QuickJSAsyncContext } from "./context-asyncify";
import { QuickJSAsyncEmscriptenModule } from "./emscripten-types";
import { QuickJSAsyncFFI } from "./variants";
import { ModuleEvalOptions, QuickJSWASMModule } from "./module";
import { QuickJSAsyncRuntime } from "./runtime-asyncify";
import { AsyncRuntimeOptions, ContextOptions } from "./types";
/**
* Asyncified version of [[QuickJSWASMModule]].
*
* Due to limitations of Emscripten's ASYNCIFY process, only a single async
* function call can happen at a time across the entire WebAssembly module.
*
* That means that all runtimes, contexts, functions, etc created inside this
* WebAssembly are limited to a single concurrent async action.
* **Multiple concurrent async actions is an error.**
*
* To allow for multiple concurrent async actions, you must create multiple WebAssembly
* modules.
*/
export declare class QuickJSAsyncWASMModule extends QuickJSWASMModule {
/** @private */
protected ffi: QuickJSAsyncFFI;
/** @private */
protected module: QuickJSAsyncEmscriptenModule;
/** @private */
constructor(module: QuickJSAsyncEmscriptenModule, ffi: QuickJSAsyncFFI);
/**
* Create a new async runtime inside this WebAssembly module. All runtimes inside a
* module are limited to a single async call at a time. For multiple
* concurrent async actions, create multiple WebAssembly modules.
*/
newRuntime(options?: AsyncRuntimeOptions): QuickJSAsyncRuntime;
/**
* A simplified API to create a new [[QuickJSRuntime]] and a
* [[QuickJSContext]] inside that runtime at the same time. The runtime will
* be disposed when the context is disposed.
*/
newContext(options?: ContextOptions): QuickJSAsyncContext;
/** Synchronous evalCode is not supported. */
evalCode(): never;
/**
* One-off evaluate code without needing to create a [[QuickJSRuntimeAsync]] or
* [[QuickJSContextSync]] explicitly.
*
* This version allows for asynchronous Ecmascript module loading.
*
* Note that only a single async action can occur at a time inside the entire WebAssembly module.
* **Multiple concurrent async actions is an error.**
*
* See the documentation for [[QuickJSWASMModule.evalCode]] for more details.
*/
evalCodeAsync(code: string, options: ModuleEvalOptions): Promise<unknown>;
}

View File

@@ -0,0 +1,97 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSAsyncWASMModule = void 0;
const errors_1 = require("./errors");
const lifetime_1 = require("./lifetime");
const module_1 = require("./module");
const runtime_asyncify_1 = require("./runtime-asyncify");
/**
* Asyncified version of [[QuickJSWASMModule]].
*
* Due to limitations of Emscripten's ASYNCIFY process, only a single async
* function call can happen at a time across the entire WebAssembly module.
*
* That means that all runtimes, contexts, functions, etc created inside this
* WebAssembly are limited to a single concurrent async action.
* **Multiple concurrent async actions is an error.**
*
* To allow for multiple concurrent async actions, you must create multiple WebAssembly
* modules.
*/
class QuickJSAsyncWASMModule extends module_1.QuickJSWASMModule {
/** @private */
constructor(module, ffi) {
super(module, ffi);
this.ffi = ffi;
this.module = module;
}
/**
* Create a new async runtime inside this WebAssembly module. All runtimes inside a
* module are limited to a single async call at a time. For multiple
* concurrent async actions, create multiple WebAssembly modules.
*/
newRuntime(options = {}) {
const rt = new lifetime_1.Lifetime(this.ffi.QTS_NewRuntime(), undefined, (rt_ptr) => {
this.callbacks.deleteRuntime(rt_ptr);
this.ffi.QTS_FreeRuntime(rt_ptr);
});
const runtime = new runtime_asyncify_1.QuickJSAsyncRuntime({
module: this.module,
ffi: this.ffi,
rt,
callbacks: this.callbacks,
});
(0, module_1.applyBaseRuntimeOptions)(runtime, options);
if (options.moduleLoader) {
runtime.setModuleLoader(options.moduleLoader);
}
return runtime;
}
/**
* A simplified API to create a new [[QuickJSRuntime]] and a
* [[QuickJSContext]] inside that runtime at the same time. The runtime will
* be disposed when the context is disposed.
*/
newContext(options = {}) {
const runtime = this.newRuntime();
const lifetimes = options.ownedLifetimes ? options.ownedLifetimes.concat([runtime]) : [runtime];
const context = runtime.newContext({ ...options, ownedLifetimes: lifetimes });
runtime.context = context;
return context;
}
/** Synchronous evalCode is not supported. */
evalCode() {
throw new errors_1.QuickJSNotImplemented("QuickJSWASMModuleAsyncify.evalCode: use evalCodeAsync instead");
}
/**
* One-off evaluate code without needing to create a [[QuickJSRuntimeAsync]] or
* [[QuickJSContextSync]] explicitly.
*
* This version allows for asynchronous Ecmascript module loading.
*
* Note that only a single async action can occur at a time inside the entire WebAssembly module.
* **Multiple concurrent async actions is an error.**
*
* See the documentation for [[QuickJSWASMModule.evalCode]] for more details.
*/
evalCodeAsync(code, options) {
// TODO: we should really figure out generator for the Promise monad...
return lifetime_1.Scope.withScopeAsync(async (scope) => {
const vm = scope.manage(this.newContext());
(0, module_1.applyModuleEvalRuntimeOptions)(vm.runtime, options);
const result = await vm.evalCodeAsync(code, "eval.js");
if (options.memoryLimitBytes !== undefined) {
// Remove memory limit so we can dump the result without exceeding it.
vm.runtime.setMemoryLimit(-1);
}
if (result.error) {
const error = vm.dump(scope.manage(result.error));
throw error;
}
const value = vm.dump(scope.manage(result.value));
return value;
});
}
}
exports.QuickJSAsyncWASMModule = QuickJSAsyncWASMModule;
//# sourceMappingURL=module-asyncify.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,27 @@
import type { QuickJSContext } from "./context";
import type { ModuleEvalOptions, QuickJSWASMModule } from "./module";
import type { QuickJSRuntime } from "./runtime";
import type { ContextOptions, RuntimeOptions } from "./types";
/**
* A test wrapper of [[QuickJSWASMModule]] that keeps a reference to each
* context or runtime created.
*
* Call [[disposeAll]] to reset these sets and calls `dispose` on any left alive
* (which may throw an error).
*
* Call [[assertNoMemoryAllocated]] at the end of a test, when you expect that you've
* freed all the memory you've ever allocated.
*/
export declare class TestQuickJSWASMModule implements Pick<QuickJSWASMModule, keyof QuickJSWASMModule> {
private parent;
contexts: Set<QuickJSContext>;
runtimes: Set<QuickJSRuntime>;
constructor(parent: QuickJSWASMModule);
newRuntime(options?: RuntimeOptions): QuickJSRuntime;
newContext(options?: ContextOptions): QuickJSContext;
evalCode(code: string, options?: ModuleEvalOptions): unknown;
disposeAll(): void;
assertNoMemoryAllocated(): void;
/** @private */
getFFI(): any;
}

View File

@@ -0,0 +1,77 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestQuickJSWASMModule = void 0;
const errors_1 = require("./errors");
const lifetime_1 = require("./lifetime");
/**
* A test wrapper of [[QuickJSWASMModule]] that keeps a reference to each
* context or runtime created.
*
* Call [[disposeAll]] to reset these sets and calls `dispose` on any left alive
* (which may throw an error).
*
* Call [[assertNoMemoryAllocated]] at the end of a test, when you expect that you've
* freed all the memory you've ever allocated.
*/
class TestQuickJSWASMModule {
constructor(parent) {
this.parent = parent;
this.contexts = new Set();
this.runtimes = new Set();
}
newRuntime(options) {
const runtime = this.parent.newRuntime({
...options,
ownedLifetimes: [
new lifetime_1.Lifetime(undefined, undefined, () => this.runtimes.delete(runtime)),
...(options?.ownedLifetimes ?? []),
],
});
this.runtimes.add(runtime);
return runtime;
}
newContext(options) {
const context = this.parent.newContext({
...options,
ownedLifetimes: [
new lifetime_1.Lifetime(undefined, undefined, () => this.contexts.delete(context)),
...(options?.ownedLifetimes ?? []),
],
});
this.contexts.add(context);
return context;
}
evalCode(code, options) {
return this.parent.evalCode(code, options);
}
disposeAll() {
const allDisposables = [...this.contexts, ...this.runtimes];
this.runtimes.clear();
this.contexts.clear();
allDisposables.forEach((d) => {
if (d.alive) {
d.dispose();
}
});
}
assertNoMemoryAllocated() {
const leaksDetected = this.getFFI().QTS_RecoverableLeakCheck();
if (leaksDetected) {
// Note: this is currently only available when building from source
// with debug builds.
throw new errors_1.QuickJSMemoryLeakDetected("Leak sanitizer detected un-freed memory");
}
if (this.contexts.size > 0) {
throw new errors_1.QuickJSMemoryLeakDetected(`${this.contexts.size} contexts leaked`);
}
if (this.runtimes.size > 0) {
throw new errors_1.QuickJSMemoryLeakDetected(`${this.runtimes.size} runtimes leaked`);
}
}
/** @private */
getFFI() {
return this.parent.getFFI();
}
}
exports.TestQuickJSWASMModule = TestQuickJSWASMModule;
//# sourceMappingURL=module-test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"module-test.js","sourceRoot":"","sources":["../ts/module-test.ts"],"names":[],"mappings":";;;AAIA,qCAAoD;AACpD,yCAAqC;AAErC;;;;;;;;;GASG;AACH,MAAa,qBAAqB;IAGhC,YAAoB,MAAyB;QAAzB,WAAM,GAAN,MAAM,CAAmB;QAF7C,aAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;QACpC,aAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;IACY,CAAC;IAEjD,UAAU,CAAC,OAAwB;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;YACrC,GAAG,OAAO;YACV,cAAc,EAAE;gBACd,IAAI,mBAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACvE,GAAG,CAAC,OAAO,EAAE,cAAc,IAAI,EAAE,CAAC;aACnC;SACF,CAAC,CAAA;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAC1B,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,UAAU,CAAC,OAAwB;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;YACrC,GAAG,OAAO;YACV,cAAc,EAAE;gBACd,IAAI,mBAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACvE,GAAG,CAAC,OAAO,EAAE,cAAc,IAAI,EAAE,CAAC;aACnC;SACF,CAAC,CAAA;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAC1B,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,QAAQ,CAAC,IAAY,EAAE,OAA2B;QAChD,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC5C,CAAC;IAED,UAAU;QACR,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC3D,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;QACrB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;QACrB,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YAC3B,IAAI,CAAC,CAAC,KAAK,EAAE;gBACX,CAAC,CAAC,OAAO,EAAE,CAAA;aACZ;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,uBAAuB;QACrB,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,wBAAwB,EAAE,CAAA;QAC9D,IAAI,aAAa,EAAE;YACjB,mEAAmE;YACnE,qBAAqB;YACrB,MAAM,IAAI,kCAAyB,CAAC,yCAAyC,CAAC,CAAA;SAC/E;QAED,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,EAAE;YAC1B,MAAM,IAAI,kCAAyB,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,CAAA;SAC7E;QAED,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,EAAE;YAC1B,MAAM,IAAI,kCAAyB,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,CAAA;SAC7E;IACH,CAAC;IAED,eAAe;IACf,MAAM;QACJ,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAA;IAC7B,CAAC;CACF;AAjED,sDAiEC","sourcesContent":["import type { QuickJSContext } from \"./context\"\nimport type { ModuleEvalOptions, QuickJSWASMModule } from \"./module\"\nimport type { QuickJSRuntime } from \"./runtime\"\nimport type { ContextOptions, RuntimeOptions } from \"./types\"\nimport { QuickJSMemoryLeakDetected } from \"./errors\"\nimport { Lifetime } from \"./lifetime\"\n\n/**\n * A test wrapper of [[QuickJSWASMModule]] that keeps a reference to each\n * context or runtime created.\n *\n * Call [[disposeAll]] to reset these sets and calls `dispose` on any left alive\n * (which may throw an error).\n *\n * Call [[assertNoMemoryAllocated]] at the end of a test, when you expect that you've\n * freed all the memory you've ever allocated.\n */\nexport class TestQuickJSWASMModule implements Pick<QuickJSWASMModule, keyof QuickJSWASMModule> {\n contexts = new Set<QuickJSContext>()\n runtimes = new Set<QuickJSRuntime>()\n constructor(private parent: QuickJSWASMModule) {}\n\n newRuntime(options?: RuntimeOptions): QuickJSRuntime {\n const runtime = this.parent.newRuntime({\n ...options,\n ownedLifetimes: [\n new Lifetime(undefined, undefined, () => this.runtimes.delete(runtime)),\n ...(options?.ownedLifetimes ?? []),\n ],\n })\n this.runtimes.add(runtime)\n return runtime\n }\n\n newContext(options?: ContextOptions): QuickJSContext {\n const context = this.parent.newContext({\n ...options,\n ownedLifetimes: [\n new Lifetime(undefined, undefined, () => this.contexts.delete(context)),\n ...(options?.ownedLifetimes ?? []),\n ],\n })\n this.contexts.add(context)\n return context\n }\n\n evalCode(code: string, options?: ModuleEvalOptions): unknown {\n return this.parent.evalCode(code, options)\n }\n\n disposeAll() {\n const allDisposables = [...this.contexts, ...this.runtimes]\n this.runtimes.clear()\n this.contexts.clear()\n allDisposables.forEach((d) => {\n if (d.alive) {\n d.dispose()\n }\n })\n }\n\n assertNoMemoryAllocated() {\n const leaksDetected = this.getFFI().QTS_RecoverableLeakCheck()\n if (leaksDetected) {\n // Note: this is currently only available when building from source\n // with debug builds.\n throw new QuickJSMemoryLeakDetected(\"Leak sanitizer detected un-freed memory\")\n }\n\n if (this.contexts.size > 0) {\n throw new QuickJSMemoryLeakDetected(`${this.contexts.size} contexts leaked`)\n }\n\n if (this.runtimes.size > 0) {\n throw new QuickJSMemoryLeakDetected(`${this.runtimes.size} runtimes leaked`)\n }\n }\n\n /** @private */\n getFFI() {\n return this.parent.getFFI()\n }\n}\n"]}

View File

@@ -0,0 +1,152 @@
import { QuickJSContext } from "./context";
import { Asyncify, AsyncifySleepResult, EitherModule, EmscriptenModuleCallbacks } from "./emscripten-types";
import { JSContextPointer, JSRuntimePointer } from "./types-ffi";
import { InterruptHandler, QuickJSRuntime } from "./runtime";
import { ContextOptions, EitherFFI, JSModuleLoader, RuntimeOptions, RuntimeOptionsBase } from "./types";
type EmscriptenCallback<BaseArgs extends any[], Result> = (...args: [Asyncify | undefined, ...BaseArgs]) => Result | AsyncifySleepResult<Result>;
type MaybeAsyncEmscriptenCallback<T extends EmscriptenCallback<any, any>> = T extends EmscriptenCallback<infer Args, infer Result> ? (...args: Args) => Result | Promise<Result> : never;
type MaybeAsyncEmscriptenCallbacks = {
[K in keyof EmscriptenModuleCallbacks]: MaybeAsyncEmscriptenCallback<EmscriptenModuleCallbacks[K]>;
};
/**
* @private
*/
export interface ContextCallbacks {
callFunction: MaybeAsyncEmscriptenCallbacks["callFunction"];
}
/**
* @private
*/
export interface RuntimeCallbacks {
shouldInterrupt: MaybeAsyncEmscriptenCallbacks["shouldInterrupt"];
loadModuleSource: MaybeAsyncEmscriptenCallbacks["loadModuleSource"];
normalizeModule: MaybeAsyncEmscriptenCallbacks["normalizeModule"];
}
/**
* Options for [[QuickJSWASMModule.evalCode]].
*/
export interface ModuleEvalOptions {
/**
* Interrupt evaluation if `shouldInterrupt` returns `true`.
* See [[shouldInterruptAfterDeadline]].
*/
shouldInterrupt?: InterruptHandler;
/**
* Memory limit, in bytes, of WebAssembly heap memory used by the QuickJS VM.
*/
memoryLimitBytes?: number;
/**
* Stack size limit for this vm, in bytes
* To remove the limit, set to `0`.
*/
maxStackSizeBytes?: number;
/**
* Module loader for any `import` statements or expressions.
*/
moduleLoader?: JSModuleLoader;
}
/**
* We use static functions per module to dispatch runtime or context calls from
* C to the host. This class manages the indirection from a specific runtime or
* context pointer to the appropriate callback handler.
*
* @private
*/
export declare class QuickJSModuleCallbacks {
private module;
private contextCallbacks;
private runtimeCallbacks;
constructor(module: EitherModule);
setRuntimeCallbacks(rt: JSRuntimePointer, callbacks: RuntimeCallbacks): void;
deleteRuntime(rt: JSRuntimePointer): void;
setContextCallbacks(ctx: JSContextPointer, callbacks: ContextCallbacks): void;
deleteContext(ctx: JSContextPointer): void;
private suspendedCount;
private suspended;
private handleAsyncify;
private cToHostCallbacks;
}
/**
* Process RuntimeOptions and apply them to a QuickJSRuntime.
* @private
*/
export declare function applyBaseRuntimeOptions(runtime: QuickJSRuntime, options: RuntimeOptionsBase): void;
/**
* Process ModuleEvalOptions and apply them to a QuickJSRuntime.
* @private
*/
export declare function applyModuleEvalRuntimeOptions<T extends QuickJSRuntime>(runtime: T, options: ModuleEvalOptions): void;
/**
* This class presents a Javascript interface to QuickJS, a Javascript interpreter
* that supports EcmaScript 2020 (ES2020).
*
* It wraps a single WebAssembly module containing the QuickJS library and
* associated helper C code. WebAssembly modules are completely isolated from
* each other by the host's WebAssembly runtime. Separate WebAssembly modules
* have the most isolation guarantees possible with this library.
*
* The simplest way to start running code is {@link evalCode}. This shortcut
* method will evaluate Javascript safely and return the result as a native
* Javascript value.
*
* For more control over the execution environment, or to interact with values
* inside QuickJS, create a context with {@link newContext} or a runtime with
* {@link newRuntime}.
*/
export declare class QuickJSWASMModule {
/** @private */
protected ffi: EitherFFI;
/** @private */
protected callbacks: QuickJSModuleCallbacks;
/** @private */
protected module: EitherModule;
/** @private */
constructor(module: EitherModule, ffi: EitherFFI);
/**
* Create a runtime.
* Use the runtime to set limits on CPU and memory usage and configure module
* loading for one or more [[QuickJSContext]]s inside the runtime.
*/
newRuntime(options?: RuntimeOptions): QuickJSRuntime;
/**
* A simplified API to create a new [[QuickJSRuntime]] and a
* [[QuickJSContext]] inside that runtime at the same time. The runtime will
* be disposed when the context is disposed.
*/
newContext(options?: ContextOptions): QuickJSContext;
/**
* One-off evaluate code without needing to create a [[QuickJSRuntime]] or
* [[QuickJSContext]] explicitly.
*
* To protect against infinite loops, use the `shouldInterrupt` option. The
* [[shouldInterruptAfterDeadline]] function will create a time-based deadline.
*
* If you need more control over how the code executes, create a
* [[QuickJSRuntime]] (with [[newRuntime]]) or a [[QuickJSContext]] (with
* [[newContext]] or [[QuickJSRuntime.newContext]]), and use its
* [[QuickJSContext.evalCode]] method.
*
* Asynchronous callbacks may not run during the first call to `evalCode`. If
* you need to work with async code inside QuickJS, create a runtime and use
* [[QuickJSRuntime.executePendingJobs]].
*
* @returns The result is coerced to a native Javascript value using JSON
* serialization, so properties and values unsupported by JSON will be dropped.
*
* @throws If `code` throws during evaluation, the exception will be
* converted into a native Javascript value and thrown.
*
* @throws if `options.shouldInterrupt` interrupted execution, will throw a Error
* with name `"InternalError"` and message `"interrupted"`.
*/
evalCode(code: string, options?: ModuleEvalOptions): unknown;
/**
* Get a low-level interface to the QuickJS functions in this WebAssembly
* module.
* @experimental
* @unstable No warranty is provided with this API. It could change at any time.
* @private
*/
getFFI(): EitherFFI;
}
export {};

View File

@@ -0,0 +1,302 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSWASMModule = exports.applyModuleEvalRuntimeOptions = exports.applyBaseRuntimeOptions = exports.QuickJSModuleCallbacks = void 0;
const debug_1 = require("./debug");
const errors_1 = require("./errors");
const lifetime_1 = require("./lifetime");
const runtime_1 = require("./runtime");
const types_1 = require("./types");
class QuickJSEmscriptenModuleCallbacks {
constructor(args) {
this.callFunction = args.callFunction;
this.shouldInterrupt = args.shouldInterrupt;
this.loadModuleSource = args.loadModuleSource;
this.normalizeModule = args.normalizeModule;
}
}
/**
* We use static functions per module to dispatch runtime or context calls from
* C to the host. This class manages the indirection from a specific runtime or
* context pointer to the appropriate callback handler.
*
* @private
*/
class QuickJSModuleCallbacks {
constructor(module) {
this.contextCallbacks = new Map();
this.runtimeCallbacks = new Map();
this.suspendedCount = 0;
this.cToHostCallbacks = new QuickJSEmscriptenModuleCallbacks({
callFunction: (asyncify, ctx, this_ptr, argc, argv, fn_id) => this.handleAsyncify(asyncify, () => {
try {
const vm = this.contextCallbacks.get(ctx);
if (!vm) {
throw new Error(`QuickJSContext(ctx = ${ctx}) not found for C function call "${fn_id}"`);
}
return vm.callFunction(ctx, this_ptr, argc, argv, fn_id);
}
catch (error) {
console.error("[C to host error: returning null]", error);
return 0;
}
}),
shouldInterrupt: (asyncify, rt) => this.handleAsyncify(asyncify, () => {
try {
const vm = this.runtimeCallbacks.get(rt);
if (!vm) {
throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C interrupt`);
}
return vm.shouldInterrupt(rt);
}
catch (error) {
console.error("[C to host interrupt: returning error]", error);
return 1;
}
}),
loadModuleSource: (asyncify, rt, ctx, moduleName) => this.handleAsyncify(asyncify, () => {
try {
const runtimeCallbacks = this.runtimeCallbacks.get(rt);
if (!runtimeCallbacks) {
throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C module loader`);
}
const loadModule = runtimeCallbacks.loadModuleSource;
if (!loadModule) {
throw new Error(`QuickJSRuntime(rt = ${rt}) does not support module loading`);
}
return loadModule(rt, ctx, moduleName);
}
catch (error) {
console.error("[C to host module loader error: returning null]", error);
return 0;
}
}),
normalizeModule: (asyncify, rt, ctx, moduleBaseName, moduleName) => this.handleAsyncify(asyncify, () => {
try {
const runtimeCallbacks = this.runtimeCallbacks.get(rt);
if (!runtimeCallbacks) {
throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C module loader`);
}
const normalizeModule = runtimeCallbacks.normalizeModule;
if (!normalizeModule) {
throw new Error(`QuickJSRuntime(rt = ${rt}) does not support module loading`);
}
return normalizeModule(rt, ctx, moduleBaseName, moduleName);
}
catch (error) {
console.error("[C to host module loader error: returning null]", error);
return 0;
}
}),
});
this.module = module;
this.module.callbacks = this.cToHostCallbacks;
}
setRuntimeCallbacks(rt, callbacks) {
this.runtimeCallbacks.set(rt, callbacks);
}
deleteRuntime(rt) {
this.runtimeCallbacks.delete(rt);
}
setContextCallbacks(ctx, callbacks) {
this.contextCallbacks.set(ctx, callbacks);
}
deleteContext(ctx) {
this.contextCallbacks.delete(ctx);
}
handleAsyncify(asyncify, fn) {
if (asyncify) {
// We must always call asyncify.handleSync around our function.
// This allows asyncify to resume suspended execution on the second call.
// Asyncify internally can detect sync behavior, and avoid suspending.
return asyncify.handleSleep((done) => {
try {
const result = fn();
if (!(result instanceof Promise)) {
(0, debug_1.debugLog)("asyncify.handleSleep: not suspending:", result);
done(result);
return;
}
// Is promise, we intend to suspend.
if (this.suspended) {
throw new errors_1.QuickJSAsyncifyError(`Already suspended at: ${this.suspended.stack}\nAttempted to suspend at:`);
}
else {
this.suspended = new errors_1.QuickJSAsyncifySuspended(`(${this.suspendedCount++})`);
(0, debug_1.debugLog)("asyncify.handleSleep: suspending:", this.suspended);
}
result.then((resolvedResult) => {
this.suspended = undefined;
(0, debug_1.debugLog)("asyncify.handleSleep: resolved:", resolvedResult);
done(resolvedResult);
}, (error) => {
(0, debug_1.debugLog)("asyncify.handleSleep: rejected:", error);
console.error("QuickJS: cannot handle error in suspended function", error);
this.suspended = undefined;
});
}
catch (error) {
(0, debug_1.debugLog)("asyncify.handleSleep: error:", error);
this.suspended = undefined;
throw error;
}
});
}
// No asyncify - we should never return a promise.
const value = fn();
if (value instanceof Promise) {
throw new Error("Promise return value not supported in non-asyncify context.");
}
return value;
}
}
exports.QuickJSModuleCallbacks = QuickJSModuleCallbacks;
/**
* Process RuntimeOptions and apply them to a QuickJSRuntime.
* @private
*/
function applyBaseRuntimeOptions(runtime, options) {
if (options.interruptHandler) {
runtime.setInterruptHandler(options.interruptHandler);
}
if (options.maxStackSizeBytes !== undefined) {
runtime.setMaxStackSize(options.maxStackSizeBytes);
}
if (options.memoryLimitBytes !== undefined) {
runtime.setMemoryLimit(options.memoryLimitBytes);
}
}
exports.applyBaseRuntimeOptions = applyBaseRuntimeOptions;
/**
* Process ModuleEvalOptions and apply them to a QuickJSRuntime.
* @private
*/
function applyModuleEvalRuntimeOptions(runtime, options) {
if (options.moduleLoader) {
runtime.setModuleLoader(options.moduleLoader);
}
if (options.shouldInterrupt) {
runtime.setInterruptHandler(options.shouldInterrupt);
}
if (options.memoryLimitBytes !== undefined) {
runtime.setMemoryLimit(options.memoryLimitBytes);
}
if (options.maxStackSizeBytes !== undefined) {
runtime.setMaxStackSize(options.maxStackSizeBytes);
}
}
exports.applyModuleEvalRuntimeOptions = applyModuleEvalRuntimeOptions;
/**
* This class presents a Javascript interface to QuickJS, a Javascript interpreter
* that supports EcmaScript 2020 (ES2020).
*
* It wraps a single WebAssembly module containing the QuickJS library and
* associated helper C code. WebAssembly modules are completely isolated from
* each other by the host's WebAssembly runtime. Separate WebAssembly modules
* have the most isolation guarantees possible with this library.
*
* The simplest way to start running code is {@link evalCode}. This shortcut
* method will evaluate Javascript safely and return the result as a native
* Javascript value.
*
* For more control over the execution environment, or to interact with values
* inside QuickJS, create a context with {@link newContext} or a runtime with
* {@link newRuntime}.
*/
class QuickJSWASMModule {
/** @private */
constructor(module, ffi) {
this.module = module;
this.ffi = ffi;
this.callbacks = new QuickJSModuleCallbacks(module);
}
/**
* Create a runtime.
* Use the runtime to set limits on CPU and memory usage and configure module
* loading for one or more [[QuickJSContext]]s inside the runtime.
*/
newRuntime(options = {}) {
const rt = new lifetime_1.Lifetime(this.ffi.QTS_NewRuntime(), undefined, (rt_ptr) => {
this.callbacks.deleteRuntime(rt_ptr);
this.ffi.QTS_FreeRuntime(rt_ptr);
});
const runtime = new runtime_1.QuickJSRuntime({
module: this.module,
callbacks: this.callbacks,
ffi: this.ffi,
rt,
});
applyBaseRuntimeOptions(runtime, options);
if (options.moduleLoader) {
runtime.setModuleLoader(options.moduleLoader);
}
return runtime;
}
/**
* A simplified API to create a new [[QuickJSRuntime]] and a
* [[QuickJSContext]] inside that runtime at the same time. The runtime will
* be disposed when the context is disposed.
*/
newContext(options = {}) {
const runtime = this.newRuntime();
const context = runtime.newContext({
...options,
ownedLifetimes: (0, types_1.concat)(runtime, options.ownedLifetimes),
});
runtime.context = context;
return context;
}
/**
* One-off evaluate code without needing to create a [[QuickJSRuntime]] or
* [[QuickJSContext]] explicitly.
*
* To protect against infinite loops, use the `shouldInterrupt` option. The
* [[shouldInterruptAfterDeadline]] function will create a time-based deadline.
*
* If you need more control over how the code executes, create a
* [[QuickJSRuntime]] (with [[newRuntime]]) or a [[QuickJSContext]] (with
* [[newContext]] or [[QuickJSRuntime.newContext]]), and use its
* [[QuickJSContext.evalCode]] method.
*
* Asynchronous callbacks may not run during the first call to `evalCode`. If
* you need to work with async code inside QuickJS, create a runtime and use
* [[QuickJSRuntime.executePendingJobs]].
*
* @returns The result is coerced to a native Javascript value using JSON
* serialization, so properties and values unsupported by JSON will be dropped.
*
* @throws If `code` throws during evaluation, the exception will be
* converted into a native Javascript value and thrown.
*
* @throws if `options.shouldInterrupt` interrupted execution, will throw a Error
* with name `"InternalError"` and message `"interrupted"`.
*/
evalCode(code, options = {}) {
return lifetime_1.Scope.withScope((scope) => {
const vm = scope.manage(this.newContext());
applyModuleEvalRuntimeOptions(vm.runtime, options);
const result = vm.evalCode(code, "eval.js");
if (options.memoryLimitBytes !== undefined) {
// Remove memory limit so we can dump the result without exceeding it.
vm.runtime.setMemoryLimit(-1);
}
if (result.error) {
const error = vm.dump(scope.manage(result.error));
throw error;
}
const value = vm.dump(scope.manage(result.value));
return value;
});
}
/**
* Get a low-level interface to the QuickJS functions in this WebAssembly
* module.
* @experimental
* @unstable No warranty is provided with this API. It could change at any time.
* @private
*/
getFFI() {
return this.ffi;
}
}
exports.QuickJSWASMModule = QuickJSWASMModule;
//# sourceMappingURL=module.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,38 @@
import { Lifetime } from ".";
import { QuickJSAsyncContext } from "./context-asyncify";
import { QuickJSAsyncEmscriptenModule } from "./emscripten-types";
import { QuickJSAsyncFFI } from "./variants";
import { JSContextPointer, JSRuntimePointer } from "./types-ffi";
import { QuickJSModuleCallbacks } from "./module";
import { QuickJSRuntime } from "./runtime";
import { ContextOptions, JSModuleLoaderAsync, JSModuleNormalizerAsync } from "./types";
export declare class QuickJSAsyncRuntime extends QuickJSRuntime {
context: QuickJSAsyncContext | undefined;
/** @private */
protected module: QuickJSAsyncEmscriptenModule;
/** @private */
protected ffi: QuickJSAsyncFFI;
/** @private */
protected rt: Lifetime<JSRuntimePointer>;
/** @private */
protected callbacks: QuickJSModuleCallbacks;
/** @private */
protected contextMap: Map<JSContextPointer, QuickJSAsyncContext>;
/** @private */
constructor(args: {
module: QuickJSAsyncEmscriptenModule;
ffi: QuickJSAsyncFFI;
rt: Lifetime<JSRuntimePointer>;
callbacks: QuickJSModuleCallbacks;
});
newContext(options?: ContextOptions): QuickJSAsyncContext;
setModuleLoader(moduleLoader: JSModuleLoaderAsync, moduleNormalizer?: JSModuleNormalizerAsync): void;
/**
* Set the max stack size for this runtime in bytes.
* To remove the limit, set to `0`.
*
* Setting this limit also adjusts the global `ASYNCIFY_STACK_SIZE` for the entire {@link QuickJSAsyncWASMModule}.
* See the [pull request](https://github.com/justjake/quickjs-emscripten/pull/114) for more details.
*/
setMaxStackSize(stackSize: number): void;
}

View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSAsyncRuntime = void 0;
const _1 = require(".");
const context_asyncify_1 = require("./context-asyncify");
const runtime_1 = require("./runtime");
const types_1 = require("./types");
class QuickJSAsyncRuntime extends runtime_1.QuickJSRuntime {
/** @private */
constructor(args) {
super(args);
}
newContext(options = {}) {
if (options.intrinsics && options.intrinsics !== types_1.DefaultIntrinsics) {
throw new Error("TODO: Custom intrinsics are not supported yet");
}
const ctx = new _1.Lifetime(this.ffi.QTS_NewContext(this.rt.value), undefined, (ctx_ptr) => {
this.contextMap.delete(ctx_ptr);
this.callbacks.deleteContext(ctx_ptr);
this.ffi.QTS_FreeContext(ctx_ptr);
});
const context = new context_asyncify_1.QuickJSAsyncContext({
module: this.module,
ctx,
ffi: this.ffi,
rt: this.rt,
ownedLifetimes: [],
runtime: this,
callbacks: this.callbacks,
});
this.contextMap.set(ctx.value, context);
return context;
}
setModuleLoader(moduleLoader, moduleNormalizer) {
super.setModuleLoader(moduleLoader, moduleNormalizer);
}
/**
* Set the max stack size for this runtime in bytes.
* To remove the limit, set to `0`.
*
* Setting this limit also adjusts the global `ASYNCIFY_STACK_SIZE` for the entire {@link QuickJSAsyncWASMModule}.
* See the [pull request](https://github.com/justjake/quickjs-emscripten/pull/114) for more details.
*/
setMaxStackSize(stackSize) {
return super.setMaxStackSize(stackSize);
}
}
exports.QuickJSAsyncRuntime = QuickJSAsyncRuntime;
//# sourceMappingURL=runtime-asyncify.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"runtime-asyncify.js","sourceRoot":"","sources":["../ts/runtime-asyncify.ts"],"names":[],"mappings":";;;AACA,wBAA4B;AAC5B,yDAAwD;AAKxD,uCAA0C;AAC1C,mCAOgB;AAEhB,MAAa,mBAAoB,SAAQ,wBAAc;IAcrD,eAAe;IACf,YAAY,IAKX;QACC,KAAK,CAAC,IAAI,CAAC,CAAA;IACb,CAAC;IAEQ,UAAU,CAAC,UAA0B,EAAE;QAC9C,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,KAAK,yBAAiB,EAAE;YAClE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;SACjE;QAED,MAAM,GAAG,GAAG,IAAI,WAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;YACtF,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC/B,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;YACrC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;QACnC,CAAC,CAAC,CAAA;QAEF,MAAM,OAAO,GAAG,IAAI,sCAAmB,CAAC;YACtC,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG;YACH,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,cAAc,EAAE,EAAE;YAClB,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAA;QACF,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAEvC,OAAO,OAAO,CAAA;IAChB,CAAC;IAEe,eAAe,CAC7B,YAAiC,EACjC,gBAA0C;QAE1C,KAAK,CAAC,eAAe,CACnB,YAA8B,EAC9B,gBAAkD,CACnD,CAAA;IACH,CAAC;IAED;;;;;;OAMG;IACa,eAAe,CAAC,SAAiB;QAC/C,OAAO,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;IACzC,CAAC;CACF;AArED,kDAqEC","sourcesContent":["import type { QuickJSAsyncWASMModule } from \"./module-asyncify\"\nimport { Lifetime } from \".\"\nimport { QuickJSAsyncContext } from \"./context-asyncify\"\nimport { QuickJSAsyncEmscriptenModule } from \"./emscripten-types\"\nimport { QuickJSAsyncFFI } from \"./variants\"\nimport { JSContextPointer, JSRuntimePointer } from \"./types-ffi\"\nimport { QuickJSModuleCallbacks } from \"./module\"\nimport { QuickJSRuntime } from \"./runtime\"\nimport {\n ContextOptions,\n DefaultIntrinsics,\n JSModuleLoader,\n JSModuleLoaderAsync,\n JSModuleNormalizer,\n JSModuleNormalizerAsync,\n} from \"./types\"\n\nexport class QuickJSAsyncRuntime extends QuickJSRuntime {\n public context: QuickJSAsyncContext | undefined\n\n /** @private */\n protected declare module: QuickJSAsyncEmscriptenModule\n /** @private */\n protected declare ffi: QuickJSAsyncFFI\n /** @private */\n protected declare rt: Lifetime<JSRuntimePointer>\n /** @private */\n protected declare callbacks: QuickJSModuleCallbacks\n /** @private */\n protected declare contextMap: Map<JSContextPointer, QuickJSAsyncContext>\n\n /** @private */\n constructor(args: {\n module: QuickJSAsyncEmscriptenModule\n ffi: QuickJSAsyncFFI\n rt: Lifetime<JSRuntimePointer>\n callbacks: QuickJSModuleCallbacks\n }) {\n super(args)\n }\n\n override newContext(options: ContextOptions = {}): QuickJSAsyncContext {\n if (options.intrinsics && options.intrinsics !== DefaultIntrinsics) {\n throw new Error(\"TODO: Custom intrinsics are not supported yet\")\n }\n\n const ctx = new Lifetime(this.ffi.QTS_NewContext(this.rt.value), undefined, (ctx_ptr) => {\n this.contextMap.delete(ctx_ptr)\n this.callbacks.deleteContext(ctx_ptr)\n this.ffi.QTS_FreeContext(ctx_ptr)\n })\n\n const context = new QuickJSAsyncContext({\n module: this.module,\n ctx,\n ffi: this.ffi,\n rt: this.rt,\n ownedLifetimes: [],\n runtime: this,\n callbacks: this.callbacks,\n })\n this.contextMap.set(ctx.value, context)\n\n return context\n }\n\n public override setModuleLoader(\n moduleLoader: JSModuleLoaderAsync,\n moduleNormalizer?: JSModuleNormalizerAsync\n ): void {\n super.setModuleLoader(\n moduleLoader as JSModuleLoader,\n moduleNormalizer as JSModuleNormalizer | undefined\n )\n }\n\n /**\n * Set the max stack size for this runtime in bytes.\n * To remove the limit, set to `0`.\n *\n * Setting this limit also adjusts the global `ASYNCIFY_STACK_SIZE` for the entire {@link QuickJSAsyncWASMModule}.\n * See the [pull request](https://github.com/justjake/quickjs-emscripten/pull/114) for more details.\n */\n public override setMaxStackSize(stackSize: number): void {\n return super.setMaxStackSize(stackSize)\n }\n}\n"]}

View File

@@ -0,0 +1,174 @@
import { QuickJSContext } from "./context";
import { EitherModule } from "./emscripten-types";
import { JSContextPointer, JSRuntimePointer } from "./types-ffi";
import { Disposable, Lifetime, Scope } from "./lifetime";
import { ModuleMemory } from "./memory";
import { QuickJSModuleCallbacks } from "./module";
import { ContextOptions, EitherFFI, JSModuleLoader, JSModuleNormalizer, QuickJSHandle } from "./types";
import { SuccessOrFail } from "./vm-interface";
/**
* Callback called regularly while the VM executes code.
* Determines if a VM's execution should be interrupted.
*
* @returns `true` to interrupt JS execution inside the VM.
* @returns `false` or `undefined` to continue JS execution inside the VM.
*/
export type InterruptHandler = (runtime: QuickJSRuntime) => boolean | undefined;
/**
* Used as an optional for the results of executing pendingJobs.
* On success, `value` contains the number of async jobs executed
* by the runtime.
* @source
*/
export type ExecutePendingJobsResult = SuccessOrFail<
/** Number of jobs successfully executed. */
number,
/** The error that occurred. */
QuickJSHandle & {
/** The context where the error occurred. */
context: QuickJSContext;
}>;
/**
* A runtime represents a Javascript runtime corresponding to an object heap.
* Several runtimes can exist at the same time but they cannot exchange objects.
* Inside a given runtime, no multi-threading is supported.
*
* You can think of separate runtimes like different domains in a browser, and
* the contexts within a runtime like the different windows open to the same
* domain.
*
* Create a runtime via {@link QuickJSWASMModule.newRuntime}.
*
* You should create separate runtime instances for untrusted code from
* different sources for isolation. However, stronger isolation is also
* available (at the cost of memory usage), by creating separate WebAssembly
* modules to further isolate untrusted code.
* See {@link newQuickJSWASMModule}.
*
* Implement memory and CPU constraints with [[setInterruptHandler]]
* (called regularly while the interpreter runs), [[setMemoryLimit]], and
* [[setMaxStackSize]].
* Use [[computeMemoryUsage]] or [[dumpMemoryUsage]] to guide memory limit
* tuning.
*
* Configure ES module loading with [[setModuleLoader]].
*/
export declare class QuickJSRuntime implements Disposable {
/**
* If this runtime was created as as part of a context, points to the context
* associated with the runtime.
*
* If this runtime was created stand-alone, this may or may not contain a context.
* A context here may be allocated if one is needed by the runtime, eg for [[computeMemoryUsage]].
*/
context: QuickJSContext | undefined;
/** @private */
protected module: EitherModule;
/** @private */
protected memory: ModuleMemory;
/** @private */
protected ffi: EitherFFI;
/** @private */
protected rt: Lifetime<JSRuntimePointer>;
/** @private */
protected callbacks: QuickJSModuleCallbacks;
/** @private */
protected scope: Scope;
/** @private */
protected contextMap: Map<JSContextPointer, QuickJSContext>;
/** @private */
protected moduleLoader: JSModuleLoader | undefined;
/** @private */
protected moduleNormalizer: JSModuleNormalizer | undefined;
/** @private */
constructor(args: {
module: EitherModule;
ffi: EitherFFI;
rt: Lifetime<JSRuntimePointer>;
callbacks: QuickJSModuleCallbacks;
ownedLifetimes?: Disposable[];
});
get alive(): boolean;
dispose(): void;
newContext(options?: ContextOptions): QuickJSContext;
/**
* Set the loader for EcmaScript modules requested by any context in this
* runtime.
*
* The loader can be removed with [[removeModuleLoader]].
*/
setModuleLoader(moduleLoader: JSModuleLoader, moduleNormalizer?: JSModuleNormalizer): void;
/**
* Remove the the loader set by [[setModuleLoader]]. This disables module loading.
*/
removeModuleLoader(): void;
/**
* In QuickJS, promises and async functions create pendingJobs. These do not execute
* immediately and need to be run by calling [[executePendingJobs]].
*
* @return true if there is at least one pendingJob queued up.
*/
hasPendingJob(): boolean;
private interruptHandler;
/**
* Set a callback which is regularly called by the QuickJS engine when it is
* executing code. This callback can be used to implement an execution
* timeout.
*
* The interrupt handler can be removed with [[removeInterruptHandler]].
*/
setInterruptHandler(cb: InterruptHandler): void;
/**
* Remove the interrupt handler, if any.
* See [[setInterruptHandler]].
*/
removeInterruptHandler(): void;
/**
* Execute pendingJobs on the runtime until `maxJobsToExecute` jobs are
* executed (default all pendingJobs), the queue is exhausted, or the runtime
* encounters an exception.
*
* In QuickJS, promises and async functions *inside the runtime* create
* pendingJobs. These do not execute immediately and need to triggered to run.
*
* @param maxJobsToExecute - When negative, run all pending jobs. Otherwise execute
* at most `maxJobsToExecute` before returning.
*
* @return On success, the number of executed jobs. On error, the exception
* that stopped execution, and the context it occurred in. Note that
* executePendingJobs will not normally return errors thrown inside async
* functions or rejected promises. Those errors are available by calling
* [[resolvePromise]] on the promise handle returned by the async function.
*/
executePendingJobs(maxJobsToExecute?: number | void): ExecutePendingJobsResult;
/**
* Set the max memory this runtime can allocate.
* To remove the limit, set to `-1`.
*/
setMemoryLimit(limitBytes: number): void;
/**
* Compute memory usage for this runtime. Returns the result as a handle to a
* JSValue object. Use [[QuickJSContext.dump]] to convert to a native object.
* Calling this method will allocate more memory inside the runtime. The information
* is accurate as of just before the call to `computeMemoryUsage`.
* For a human-digestible representation, see [[dumpMemoryUsage]].
*/
computeMemoryUsage(): QuickJSHandle;
/**
* @returns a human-readable description of memory usage in this runtime.
* For programmatic access to this information, see [[computeMemoryUsage]].
*/
dumpMemoryUsage(): string;
/**
* Set the max stack size for this runtime, in bytes.
* To remove the limit, set to `0`.
*/
setMaxStackSize(stackSize: number): void;
/**
* Assert that `handle` is owned by this runtime.
* @throws QuickJSWrongOwner if owned by a different runtime.
*/
assertOwned(handle: QuickJSHandle): void;
private getSystemContext;
private cToHostCallbacks;
}

View File

@@ -0,0 +1,300 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSRuntime = void 0;
const asyncify_helpers_1 = require("./asyncify-helpers");
const context_1 = require("./context");
const debug_1 = require("./debug");
const errors_1 = require("./errors");
const lifetime_1 = require("./lifetime");
const memory_1 = require("./memory");
const types_1 = require("./types");
/**
* A runtime represents a Javascript runtime corresponding to an object heap.
* Several runtimes can exist at the same time but they cannot exchange objects.
* Inside a given runtime, no multi-threading is supported.
*
* You can think of separate runtimes like different domains in a browser, and
* the contexts within a runtime like the different windows open to the same
* domain.
*
* Create a runtime via {@link QuickJSWASMModule.newRuntime}.
*
* You should create separate runtime instances for untrusted code from
* different sources for isolation. However, stronger isolation is also
* available (at the cost of memory usage), by creating separate WebAssembly
* modules to further isolate untrusted code.
* See {@link newQuickJSWASMModule}.
*
* Implement memory and CPU constraints with [[setInterruptHandler]]
* (called regularly while the interpreter runs), [[setMemoryLimit]], and
* [[setMaxStackSize]].
* Use [[computeMemoryUsage]] or [[dumpMemoryUsage]] to guide memory limit
* tuning.
*
* Configure ES module loading with [[setModuleLoader]].
*/
class QuickJSRuntime {
/** @private */
constructor(args) {
/** @private */
this.scope = new lifetime_1.Scope();
/** @private */
this.contextMap = new Map();
this.cToHostCallbacks = {
shouldInterrupt: (rt) => {
if (rt !== this.rt.value) {
throw new Error("QuickJSContext instance received C -> JS interrupt with mismatched rt");
}
const fn = this.interruptHandler;
if (!fn) {
throw new Error("QuickJSContext had no interrupt handler");
}
return fn(this) ? 1 : 0;
},
loadModuleSource: (0, asyncify_helpers_1.maybeAsyncFn)(this, function* (awaited, rt, ctx, moduleName) {
const moduleLoader = this.moduleLoader;
if (!moduleLoader) {
throw new Error("Runtime has no module loader");
}
if (rt !== this.rt.value) {
throw new Error("Runtime pointer mismatch");
}
const context = this.contextMap.get(ctx) ??
this.newContext({
contextPointer: ctx,
});
try {
const result = yield* awaited(moduleLoader(moduleName, context));
if (typeof result === "object" && "error" in result && result.error) {
(0, debug_1.debugLog)("cToHostLoadModule: loader returned error", result.error);
throw result.error;
}
const moduleSource = typeof result === "string" ? result : "value" in result ? result.value : result;
return this.memory.newHeapCharPointer(moduleSource).value;
}
catch (error) {
(0, debug_1.debugLog)("cToHostLoadModule: caught error", error);
context.throw(error);
return 0;
}
}),
normalizeModule: (0, asyncify_helpers_1.maybeAsyncFn)(this, function* (awaited, rt, ctx, baseModuleName, moduleNameRequest) {
const moduleNormalizer = this.moduleNormalizer;
if (!moduleNormalizer) {
throw new Error("Runtime has no module normalizer");
}
if (rt !== this.rt.value) {
throw new Error("Runtime pointer mismatch");
}
const context = this.contextMap.get(ctx) ??
this.newContext({
/* TODO: Does this happen? Are we responsible for disposing? I don't think so */
contextPointer: ctx,
});
try {
const result = yield* awaited(moduleNormalizer(baseModuleName, moduleNameRequest, context));
if (typeof result === "object" && "error" in result && result.error) {
(0, debug_1.debugLog)("cToHostNormalizeModule: normalizer returned error", result.error);
throw result.error;
}
const name = typeof result === "string" ? result : result.value;
return context.getMemory(this.rt.value).newHeapCharPointer(name).value;
}
catch (error) {
(0, debug_1.debugLog)("normalizeModule: caught error", error);
context.throw(error);
return 0;
}
}),
};
args.ownedLifetimes?.forEach((lifetime) => this.scope.manage(lifetime));
this.module = args.module;
this.memory = new memory_1.ModuleMemory(this.module);
this.ffi = args.ffi;
this.rt = args.rt;
this.callbacks = args.callbacks;
this.scope.manage(this.rt);
this.callbacks.setRuntimeCallbacks(this.rt.value, this.cToHostCallbacks);
this.executePendingJobs = this.executePendingJobs.bind(this);
}
get alive() {
return this.scope.alive;
}
dispose() {
return this.scope.dispose();
}
newContext(options = {}) {
if (options.intrinsics && options.intrinsics !== types_1.DefaultIntrinsics) {
throw new Error("TODO: Custom intrinsics are not supported yet");
}
const ctx = new lifetime_1.Lifetime(options.contextPointer || this.ffi.QTS_NewContext(this.rt.value), undefined, (ctx_ptr) => {
this.contextMap.delete(ctx_ptr);
this.callbacks.deleteContext(ctx_ptr);
this.ffi.QTS_FreeContext(ctx_ptr);
});
const context = new context_1.QuickJSContext({
module: this.module,
ctx,
ffi: this.ffi,
rt: this.rt,
ownedLifetimes: options.ownedLifetimes,
runtime: this,
callbacks: this.callbacks,
});
this.contextMap.set(ctx.value, context);
return context;
}
/**
* Set the loader for EcmaScript modules requested by any context in this
* runtime.
*
* The loader can be removed with [[removeModuleLoader]].
*/
setModuleLoader(moduleLoader, moduleNormalizer) {
this.moduleLoader = moduleLoader;
this.moduleNormalizer = moduleNormalizer;
this.ffi.QTS_RuntimeEnableModuleLoader(this.rt.value, this.moduleNormalizer ? 1 : 0);
}
/**
* Remove the the loader set by [[setModuleLoader]]. This disables module loading.
*/
removeModuleLoader() {
this.moduleLoader = undefined;
this.ffi.QTS_RuntimeDisableModuleLoader(this.rt.value);
}
// Runtime management -------------------------------------------------------
/**
* In QuickJS, promises and async functions create pendingJobs. These do not execute
* immediately and need to be run by calling [[executePendingJobs]].
*
* @return true if there is at least one pendingJob queued up.
*/
hasPendingJob() {
return Boolean(this.ffi.QTS_IsJobPending(this.rt.value));
}
/**
* Set a callback which is regularly called by the QuickJS engine when it is
* executing code. This callback can be used to implement an execution
* timeout.
*
* The interrupt handler can be removed with [[removeInterruptHandler]].
*/
setInterruptHandler(cb) {
const prevInterruptHandler = this.interruptHandler;
this.interruptHandler = cb;
if (!prevInterruptHandler) {
this.ffi.QTS_RuntimeEnableInterruptHandler(this.rt.value);
}
}
/**
* Remove the interrupt handler, if any.
* See [[setInterruptHandler]].
*/
removeInterruptHandler() {
if (this.interruptHandler) {
this.ffi.QTS_RuntimeDisableInterruptHandler(this.rt.value);
this.interruptHandler = undefined;
}
}
/**
* Execute pendingJobs on the runtime until `maxJobsToExecute` jobs are
* executed (default all pendingJobs), the queue is exhausted, or the runtime
* encounters an exception.
*
* In QuickJS, promises and async functions *inside the runtime* create
* pendingJobs. These do not execute immediately and need to triggered to run.
*
* @param maxJobsToExecute - When negative, run all pending jobs. Otherwise execute
* at most `maxJobsToExecute` before returning.
*
* @return On success, the number of executed jobs. On error, the exception
* that stopped execution, and the context it occurred in. Note that
* executePendingJobs will not normally return errors thrown inside async
* functions or rejected promises. Those errors are available by calling
* [[resolvePromise]] on the promise handle returned by the async function.
*/
executePendingJobs(maxJobsToExecute = -1) {
const ctxPtrOut = this.memory.newMutablePointerArray(1);
const valuePtr = this.ffi.QTS_ExecutePendingJob(this.rt.value, maxJobsToExecute ?? -1, ctxPtrOut.value.ptr);
const ctxPtr = ctxPtrOut.value.typedArray[0];
ctxPtrOut.dispose();
if (ctxPtr === 0) {
// No jobs executed.
this.ffi.QTS_FreeValuePointerRuntime(this.rt.value, valuePtr);
return { value: 0 };
}
const context = this.contextMap.get(ctxPtr) ??
this.newContext({
contextPointer: ctxPtr,
});
const resultValue = context.getMemory(this.rt.value).heapValueHandle(valuePtr);
const typeOfRet = context.typeof(resultValue);
if (typeOfRet === "number") {
const executedJobs = context.getNumber(resultValue);
resultValue.dispose();
return { value: executedJobs };
}
else {
const error = Object.assign(resultValue, { context });
return {
error,
};
}
}
/**
* Set the max memory this runtime can allocate.
* To remove the limit, set to `-1`.
*/
setMemoryLimit(limitBytes) {
if (limitBytes < 0 && limitBytes !== -1) {
throw new Error("Cannot set memory limit to negative number. To unset, pass -1");
}
this.ffi.QTS_RuntimeSetMemoryLimit(this.rt.value, limitBytes);
}
/**
* Compute memory usage for this runtime. Returns the result as a handle to a
* JSValue object. Use [[QuickJSContext.dump]] to convert to a native object.
* Calling this method will allocate more memory inside the runtime. The information
* is accurate as of just before the call to `computeMemoryUsage`.
* For a human-digestible representation, see [[dumpMemoryUsage]].
*/
computeMemoryUsage() {
const serviceContextMemory = this.getSystemContext().getMemory(this.rt.value);
return serviceContextMemory.heapValueHandle(this.ffi.QTS_RuntimeComputeMemoryUsage(this.rt.value, serviceContextMemory.ctx.value));
}
/**
* @returns a human-readable description of memory usage in this runtime.
* For programmatic access to this information, see [[computeMemoryUsage]].
*/
dumpMemoryUsage() {
return this.memory.consumeHeapCharPointer(this.ffi.QTS_RuntimeDumpMemoryUsage(this.rt.value));
}
/**
* Set the max stack size for this runtime, in bytes.
* To remove the limit, set to `0`.
*/
setMaxStackSize(stackSize) {
if (stackSize < 0) {
throw new Error("Cannot set memory limit to negative number. To unset, pass 0.");
}
this.ffi.QTS_RuntimeSetMaxStackSize(this.rt.value, stackSize);
}
/**
* Assert that `handle` is owned by this runtime.
* @throws QuickJSWrongOwner if owned by a different runtime.
*/
assertOwned(handle) {
if (handle.owner && handle.owner.rt !== this.rt) {
throw new errors_1.QuickJSWrongOwner(`Handle is not owned by this runtime: ${handle.owner.rt.value} != ${this.rt.value}`);
}
}
getSystemContext() {
if (!this.context) {
// We own this context and should dispose of it.
this.context = this.scope.manage(this.newContext());
}
return this.context;
}
}
exports.QuickJSRuntime = QuickJSRuntime;
//# sourceMappingURL=runtime.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,114 @@
/**
* C pointer to type `CType`. Pointer types are used internally for FFI, but
* are not intended for external use.
*
* @unstable This type is considered private and may change.
*/
type Pointer<CType extends string> = number & {
ctype: CType;
};
type Brand<T, B> = T & {
brand: B;
};
/**
* `JSRuntime*`.
*/
export type JSRuntimePointer = Pointer<"JSRuntime">;
/**
* `JSContext*`.
*/
export type JSContextPointer = Pointer<"JSContext">;
/**
* `JSContext**`. Used internally for execute pending jobs.
*/
export type JSContextPointerPointer = Pointer<"JSContext">;
/**
* `JSModuleDef*`.
*/
export type JSModuleDefPointer = Pointer<"JSModuleDef">;
/**
* `JSValue*`.
* See [[JSValue]].
*/
export type JSValuePointer = Pointer<"JSValue">;
/**
* `JSValueConst*
* See [[JSValueConst]] and [[StaticJSValue]].
*/
export type JSValueConstPointer = Pointer<"JSValueConst">;
/**
* Used internally for Javascript-to-C function calls.
*/
export type JSValuePointerPointer = Pointer<"JSValue[]">;
/**
* Used internally for Javascript-to-C function calls.
*/
export type JSValueConstPointerPointer = Pointer<"JSValueConst[]">;
/**
* Used internally for C-to-Javascript function calls.
*/
/**
* Used internally for C-to-Javascript function calls.
*/
export type QTS_C_To_HostCallbackFuncPointer = Pointer<"C_To_HostCallbackFunc">;
/**
* Used internally for C-to-Javascript interrupt handlers.
*/
export type QTS_C_To_HostInterruptFuncPointer = Pointer<"C_To_HostInterruptFunc">;
/**
* Used internally for C-to-Javascript module loading.
*/
export type QTS_C_To_HostLoadModuleFuncPointer = Pointer<"C_To_HostLoadModuleFunc">;
/**
* Used internally for Javascript-to-C calls that may contain strings too large
* for the Emscripten stack.
*/
export type BorrowedHeapCharPointer = Pointer<"const char" | "char" | "js const char">;
/**
* Used internally for Javascript-to-C calls that may contain strings too large
* for the Emscripten stack.
*/
export type OwnedHeapCharPointer = Pointer<"char">;
/**
* Used internally for Javascript-to-C calls that may contain strings too large
* for the Emscripten stack.
*/
export type JSBorrowedCharPointer = Pointer<"js const char">;
/**
* Opaque pointer that was allocated by js_malloc.
*/
export type JSVoidPointer = Pointer<any>;
/**
* @private
*/
export type EvalFlags = Brand<number, "EvalFlags">;
/**
* @private
*/
export type EvalDetectModule = Brand<number, "EvalDetectModule">;
export declare function assertSync<Args extends any[], R>(fn: (...args: Args) => R): (...args: Args) => R;
/** Bitfield options for JS_Eval() C function. */
export declare const EvalFlags: {
/** global code (default) */
JS_EVAL_TYPE_GLOBAL: number;
/** module code */
JS_EVAL_TYPE_MODULE: number;
/** direct call (internal use) */
JS_EVAL_TYPE_DIRECT: number;
/** indirect call (internal use) */
JS_EVAL_TYPE_INDIRECT: number;
JS_EVAL_TYPE_MASK: number;
/** force 'strict' mode */
JS_EVAL_FLAG_STRICT: number;
/** force 'strip' mode */
JS_EVAL_FLAG_STRIP: number;
/**
* compile but do not run. The result is an object with a
* JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed
* with JS_EvalFunction().
*/
JS_EVAL_FLAG_COMPILE_ONLY: number;
/** don't include the stack frames before this eval in the Error() backtraces */
JS_EVAL_FLAG_BACKTRACE_BARRIER: number;
};
export {};

View File

@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EvalFlags = exports.assertSync = void 0;
function assertSync(fn) {
return function mustBeSync(...args) {
const result = fn(...args);
if (result && typeof result === "object" && result instanceof Promise) {
throw new Error("Function unexpectedly returned a Promise");
}
return result;
};
}
exports.assertSync = assertSync;
/** Bitfield options for JS_Eval() C function. */
exports.EvalFlags = {
/** global code (default) */
JS_EVAL_TYPE_GLOBAL: 0 << 0,
/** module code */
JS_EVAL_TYPE_MODULE: 1 << 0,
/** direct call (internal use) */
JS_EVAL_TYPE_DIRECT: 2 << 0,
/** indirect call (internal use) */
JS_EVAL_TYPE_INDIRECT: 3 << 0,
JS_EVAL_TYPE_MASK: 3 << 0,
/** force 'strict' mode */
JS_EVAL_FLAG_STRICT: 1 << 3,
/** force 'strip' mode */
JS_EVAL_FLAG_STRIP: 1 << 4,
/**
* compile but do not run. The result is an object with a
* JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed
* with JS_EvalFunction().
*/
JS_EVAL_FLAG_COMPILE_ONLY: 1 << 5,
/** don't include the stack frames before this eval in the Error() backtraces */
JS_EVAL_FLAG_BACKTRACE_BARRIER: 1 << 6,
};
//# sourceMappingURL=types-ffi.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types-ffi.js","sourceRoot":"","sources":["../ts/types-ffi.ts"],"names":[],"mappings":";;;AAyGA,SAAgB,UAAU,CAAwB,EAAwB;IACxE,OAAO,SAAS,UAAU,CAAC,GAAG,IAAU;QACtC,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;QAC1B,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,YAAY,OAAO,EAAE;YACrE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;SAC5D;QACD,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;AACH,CAAC;AARD,gCAQC;AAED,iDAAiD;AACpC,QAAA,SAAS,GAAG;IACvB,4BAA4B;IAC5B,mBAAmB,EAAE,CAAC,IAAI,CAAC;IAC3B,kBAAkB;IAClB,mBAAmB,EAAE,CAAC,IAAI,CAAC;IAC3B,iCAAiC;IACjC,mBAAmB,EAAE,CAAC,IAAI,CAAC;IAC3B,mCAAmC;IACnC,qBAAqB,EAAE,CAAC,IAAI,CAAC;IAC7B,iBAAiB,EAAE,CAAC,IAAI,CAAC;IACzB,0BAA0B;IAC1B,mBAAmB,EAAE,CAAC,IAAI,CAAC;IAC3B,yBAAyB;IACzB,kBAAkB,EAAE,CAAC,IAAI,CAAC;IAC1B;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,IAAI,CAAC;IACjC,gFAAgF;IAChF,8BAA8B,EAAE,CAAC,IAAI,CAAC;CACvC,CAAA","sourcesContent":["/**\n * C pointer to type `CType`. Pointer types are used internally for FFI, but\n * are not intended for external use.\n *\n * @unstable This type is considered private and may change.\n */\ntype Pointer<CType extends string> = number & { ctype: CType }\n\ntype Brand<T, B> = T & { brand: B }\n\n/**\n * `JSRuntime*`.\n */\nexport type JSRuntimePointer = Pointer<\"JSRuntime\">\n\n/**\n * `JSContext*`.\n */\nexport type JSContextPointer = Pointer<\"JSContext\">\n\n/**\n * `JSContext**`. Used internally for execute pending jobs.\n */\nexport type JSContextPointerPointer = Pointer<\"JSContext\">\n\n/**\n * `JSModuleDef*`.\n */\nexport type JSModuleDefPointer = Pointer<\"JSModuleDef\">\n\n/**\n * `JSValue*`.\n * See [[JSValue]].\n */\nexport type JSValuePointer = Pointer<\"JSValue\">\n\n/**\n * `JSValueConst*\n * See [[JSValueConst]] and [[StaticJSValue]].\n */\nexport type JSValueConstPointer = Pointer<\"JSValueConst\">\n\n/**\n * Used internally for Javascript-to-C function calls.\n */\nexport type JSValuePointerPointer = Pointer<\"JSValue[]\">\n\n/**\n * Used internally for Javascript-to-C function calls.\n */\nexport type JSValueConstPointerPointer = Pointer<\"JSValueConst[]\">\n\n/**\n * Used internally for C-to-Javascript function calls.\n */\n// type JSCFunctionPointer = Pointer<'JSCFunction'>\n\n/**\n * Used internally for C-to-Javascript function calls.\n */\nexport type QTS_C_To_HostCallbackFuncPointer = Pointer<\"C_To_HostCallbackFunc\">\n\n/**\n * Used internally for C-to-Javascript interrupt handlers.\n */\nexport type QTS_C_To_HostInterruptFuncPointer = Pointer<\"C_To_HostInterruptFunc\">\n\n/**\n * Used internally for C-to-Javascript module loading.\n */\nexport type QTS_C_To_HostLoadModuleFuncPointer = Pointer<\"C_To_HostLoadModuleFunc\">\n\n/**\n * Used internally for Javascript-to-C calls that may contain strings too large\n * for the Emscripten stack.\n */\nexport type BorrowedHeapCharPointer = Pointer<\"const char\" | \"char\" | \"js const char\">\n\n/**\n * Used internally for Javascript-to-C calls that may contain strings too large\n * for the Emscripten stack.\n */\nexport type OwnedHeapCharPointer = Pointer<\"char\">\n\n/**\n * Used internally for Javascript-to-C calls that may contain strings too large\n * for the Emscripten stack.\n */\nexport type JSBorrowedCharPointer = Pointer<\"js const char\">\n\n/**\n * Opaque pointer that was allocated by js_malloc.\n */\nexport type JSVoidPointer = Pointer<any>\n\n/**\n * @private\n */\nexport type EvalFlags = Brand<number, \"EvalFlags\">\n\n/**\n * @private\n */\nexport type EvalDetectModule = Brand<number, \"EvalDetectModule\">\n\nexport function assertSync<Args extends any[], R>(fn: (...args: Args) => R): (...args: Args) => R {\n return function mustBeSync(...args: Args): R {\n const result = fn(...args)\n if (result && typeof result === \"object\" && result instanceof Promise) {\n throw new Error(\"Function unexpectedly returned a Promise\")\n }\n return result\n }\n}\n\n/** Bitfield options for JS_Eval() C function. */\nexport const EvalFlags = {\n /** global code (default) */\n JS_EVAL_TYPE_GLOBAL: 0 << 0,\n /** module code */\n JS_EVAL_TYPE_MODULE: 1 << 0,\n /** direct call (internal use) */\n JS_EVAL_TYPE_DIRECT: 2 << 0,\n /** indirect call (internal use) */\n JS_EVAL_TYPE_INDIRECT: 3 << 0,\n JS_EVAL_TYPE_MASK: 3 << 0,\n /** force 'strict' mode */\n JS_EVAL_FLAG_STRICT: 1 << 3,\n /** force 'strip' mode */\n JS_EVAL_FLAG_STRIP: 1 << 4,\n /**\n * compile but do not run. The result is an object with a\n * JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed\n * with JS_EvalFunction().\n */\n JS_EVAL_FLAG_COMPILE_ONLY: 1 << 5,\n /** don't include the stack frames before this eval in the Error() backtraces */\n JS_EVAL_FLAG_BACKTRACE_BARRIER: 1 << 6,\n}\n"]}

View File

@@ -0,0 +1,158 @@
import type { QuickJSFFI, QuickJSAsyncFFI } from "./variants";
import type { QuickJSContext } from "./context";
import type { SuccessOrFail, VmFunctionImplementation } from "./vm-interface";
import type { Disposable, Lifetime } from "./lifetime";
import type { QuickJSAsyncContext } from "./context-asyncify";
import type { InterruptHandler, QuickJSRuntime } from "./runtime";
import { JSContextPointer, JSValueConstPointer, JSValuePointer } from "./types-ffi";
export type EitherFFI = QuickJSFFI | QuickJSAsyncFFI;
/**
* A QuickJSHandle to a constant that will never change, and does not need to
* be disposed.
*/
export type StaticJSValue = Lifetime<JSValueConstPointer, JSValueConstPointer, QuickJSRuntime>;
/**
* A QuickJSHandle to a borrowed value that does not need to be disposed.
*
* In QuickJS, a JSValueConst is a "borrowed" reference that isn't owned by the
* current scope. That means that the current scope should not `JS_FreeValue`
* it, or retain a reference to it after the scope exits, because it may be
* freed by its owner.
*
* quickjs-emscripten takes care of disposing JSValueConst references.
*/
export type JSValueConst = Lifetime<JSValueConstPointer, JSValuePointer, QuickJSRuntime>;
/**
* A owned QuickJSHandle that should be disposed or returned.
*
* The QuickJS interpreter passes Javascript values between functions as
* `JSValue` structs that references some internal data. Because passing
* structs cross the Empscripten FFI interfaces is bothersome, we use pointers
* to these structs instead.
*
* A JSValue reference is "owned" in its scope. before exiting the scope, it
* should be freed, by calling `JS_FreeValue(ctx, js_value)`) or returned from
* the scope. We extend that contract - a JSValuePointer (`JSValue*`) must also
* be `free`d.
*
* You can do so from Javascript by calling the .dispose() method.
*/
export type JSValue = Lifetime<JSValuePointer, JSValuePointer, QuickJSRuntime>;
/**
* Wraps a C pointer to a QuickJS JSValue, which represents a Javascript value inside
* a QuickJS virtual machine.
*
* Values must not be shared between QuickJSContext instances.
* You must dispose of any handles you create by calling the `.dispose()` method.
*/
export type QuickJSHandle = StaticJSValue | JSValue | JSValueConst;
export type JSModuleExport = {
type: "function";
name: string;
implementation: (vm: QuickJSContext) => VmFunctionImplementation<QuickJSHandle>;
} | {
type: "value";
name: string;
value: (vm: QuickJSContext) => QuickJSHandle;
};
export interface JSModuleDefinition {
name: string;
exports: JSModuleExport[];
}
export type JSModuleLoadSuccess = string;
export type JSModuleLoadFailure = Error | QuickJSHandle;
export type JSModuleLoadResult = JSModuleLoadSuccess | SuccessOrFail<JSModuleLoadSuccess, JSModuleLoadFailure>;
export interface JSModuleLoaderAsync {
/** Load module (async) */
(moduleName: string, context: QuickJSAsyncContext): JSModuleLoadResult | Promise<JSModuleLoadResult>;
}
export interface JSModuleLoader {
/** Load module (sync) */
(moduleName: string, context: QuickJSContext): JSModuleLoadResult;
}
export type JSModuleNormalizeSuccess = string;
export type JSModuleNormalizeFailure = Error | QuickJSHandle;
export type JSModuleNormalizeResult = JSModuleNormalizeSuccess | SuccessOrFail<JSModuleNormalizeSuccess, JSModuleNormalizeFailure>;
export interface JSModuleNormalizerAsync {
(baseModuleName: string, requestedName: string, vm: QuickJSAsyncContext): JSModuleNormalizeResult | Promise<JSModuleNormalizeResult>;
}
export interface JSModuleNormalizer extends JSModuleNormalizerAsync {
(baseModuleName: string, requestedName: string, vm: QuickJSContext): JSModuleNormalizeResult;
}
type TODO<hint extends string = "?", typeHint = unknown> = never;
declare const UnstableSymbol: unique symbol;
export type PartiallyImplemented<T> = never & T & {
[UnstableSymbol]: "This feature may unimplemented, broken, throw errors, etc.";
};
export interface RuntimeOptionsBase {
interruptHandler?: InterruptHandler;
maxStackSizeBytes?: number;
memoryLimitBytes?: number;
promiseRejectionHandler?: TODO<"JSHostPromiseRejectionTracker">;
runtimeInfo?: TODO<"JS_SetRuntimeInfo", string>;
gcThreshold?: TODO<"JS_SetGCThreshold", number>;
sharedArrayBufferFunctions?: TODO<"JS_SetJSSharedArrayBufferFunctions", {
sab_alloc: TODO;
sab_free: TODO;
sab_dup: TODO;
sab_opaque: TODO;
}>;
/**
* Extra lifetimes the runtime should dispose of after it is destroyed.
* @private
*/
ownedLifetimes?: Disposable[];
}
export interface RuntimeOptions extends RuntimeOptionsBase {
moduleLoader?: JSModuleLoader;
}
export interface AsyncRuntimeOptions extends RuntimeOptionsBase {
moduleLoader?: JSModuleLoaderAsync | JSModuleLoader;
}
/**
* Work in progress.
*/
export type Intrinsic = "BaseObjects" | "Date" | "Eval" | "StringNormalize" | "RegExp" | "RegExpCompiler" | "JSON" | "Proxy" | "MapSet" | "TypedArrays" | "Promise" | "BigInt" | "BigFloat" | "BigDecimal" | "OperatorOverloading" | "BignumExt";
/**
* Work in progress.
*/
export declare const DefaultIntrinsics: unique symbol;
export interface ContextOptions {
/**
* What built-in objects and language features to enable?
* If unset, the default intrinsics will be used.
* To omit all intrinsics, pass an empty array.
*/
intrinsics?: PartiallyImplemented<Intrinsic[]> | typeof DefaultIntrinsics;
/**
* Wrap the provided context instead of constructing a new one.
* @private
*/
contextPointer?: JSContextPointer;
/**
* Extra lifetimes the context should dispose of after it is destroyed.
* @private
*/
ownedLifetimes?: Disposable[];
}
export interface ContextEvalOptions {
/** Global code (default) */
type?: "global" | "module";
/** Force "strict" mode */
strict?: boolean;
/** Force "strip" mode */
strip?: boolean;
/**
* compile but do not run. The result is an object with a
* JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed
* with JS_EvalFunction().
*/
compileOnly?: boolean;
/** don't include the stack frames before this eval in the Error() backtraces */
backtraceBarrier?: boolean;
}
/** Convert [[ContextEvalOptions]] to a bitfield flags */
export declare function evalOptionsToFlags(evalOptions: ContextEvalOptions | number | undefined): number;
export type PromiseExecutor<ResolveT, RejectT> = (resolve: (value: ResolveT | PromiseLike<ResolveT>) => void, reject: (reason: RejectT) => void) => void;
export declare function concat<T>(...values: Array<T[] | T | undefined>): T[];
export {};

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.concat = exports.evalOptionsToFlags = exports.DefaultIntrinsics = void 0;
const types_ffi_1 = require("./types-ffi");
const UnstableSymbol = Symbol("Unstable");
// For informational purposes
const DefaultIntrinsicsList = [
"BaseObjects",
"Date",
"Eval",
"StringNormalize",
"RegExp",
"JSON",
"Proxy",
"MapSet",
"TypedArrays",
"Promise",
];
/**
* Work in progress.
*/
exports.DefaultIntrinsics = Symbol("DefaultIntrinsics");
/** Convert [[ContextEvalOptions]] to a bitfield flags */
function evalOptionsToFlags(evalOptions) {
if (typeof evalOptions === "number") {
return evalOptions;
}
if (evalOptions === undefined) {
return 0;
}
const { type, strict, strip, compileOnly, backtraceBarrier } = evalOptions;
let flags = 0;
if (type === "global")
flags |= types_ffi_1.EvalFlags.JS_EVAL_TYPE_GLOBAL;
if (type === "module")
flags |= types_ffi_1.EvalFlags.JS_EVAL_TYPE_MODULE;
if (strict)
flags |= types_ffi_1.EvalFlags.JS_EVAL_FLAG_STRICT;
if (strip)
flags |= types_ffi_1.EvalFlags.JS_EVAL_FLAG_STRIP;
if (compileOnly)
flags |= types_ffi_1.EvalFlags.JS_EVAL_FLAG_COMPILE_ONLY;
if (backtraceBarrier)
flags |= types_ffi_1.EvalFlags.JS_EVAL_FLAG_BACKTRACE_BARRIER;
return flags;
}
exports.evalOptionsToFlags = evalOptionsToFlags;
function concat(...values) {
let result = [];
for (const value of values) {
if (value !== undefined) {
result = result.concat(value);
}
}
return result;
}
exports.concat = concat;
//# sourceMappingURL=types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,113 @@
import type { QuickJSFFI as ReleaseSyncFFI } from "./generated/ffi.WASM_RELEASE_SYNC";
import type { EmscriptenModuleLoader, QuickJSEmscriptenModule, QuickJSAsyncEmscriptenModule } from "./emscripten-types";
import type { QuickJSWASMModule } from "./module";
import type { QuickJSAsyncWASMModule } from "./module-asyncify";
/** @private */
export type QuickJSFFI = ReleaseSyncFFI;
/** @private */
export type QuickJSFFIConstructor = typeof ReleaseSyncFFI;
/** @private */
export type QuickJSAsyncFFI = any;
/** @private */
export type QuickJSAsyncFFIConstructor = any;
/**
* quickjs-emscripten provides multiple build variants of the core WebAssembly
* module. These variants are each intended for a different use case.
*
* To create an instance of the library using a specific build variant, pass the
* build variant to {@link newQuickJSWASMModule} or {@link newQuickJSAsyncWASMModule}.
*
* Synchronous build variants:
*
* - {@link RELEASE_SYNC} - This is the default synchronous variant, for general purpose use.
* - {@link DEBUG_SYNC} - Synchronous build variant for debugging memory leaks.
*/
export interface SyncBuildVariant {
type: "sync";
importFFI: () => Promise<QuickJSFFIConstructor>;
importModuleLoader: () => Promise<EmscriptenModuleLoader<QuickJSEmscriptenModule>>;
}
/**
* quickjs-emscripten provides multiple build variants of the core WebAssembly
* module. These variants are each intended for a different use case.
*
* To create an instance of the library using a specific build variant, pass the
* build variant to {@link newQuickJSWASMModule} or {@link newQuickJSAsyncWASMModule}.
*
* Asyncified build variants:
*
* - {@link RELEASE_ASYNC} - This is the default asyncified build variant, for general purpose use.
* - {@link DEBUG_ASYNC} - Asyncified build variant with debug logging.
*/
export interface AsyncBuildVariant {
type: "async";
importFFI: () => Promise<QuickJSAsyncFFIConstructor>;
importModuleLoader: () => Promise<EmscriptenModuleLoader<QuickJSAsyncEmscriptenModule>>;
}
/**
* Create a new, completely isolated WebAssembly module containing the QuickJS library.
* See the documentation on [[QuickJSWASMModule]].
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
export declare function newQuickJSWASMModule(
/**
* Optionally, pass a {@link SyncBuildVariant} to construct a different WebAssembly module.
*/
variant?: SyncBuildVariant): Promise<QuickJSWASMModule>;
/**
* Create a new, completely isolated WebAssembly module containing a version of the QuickJS library
* compiled with Emscripten's [ASYNCIFY](https://emscripten.org/docs/porting/asyncify.html) transform.
*
* This version of the library offers features that enable synchronous code
* inside the VM to interact with asynchronous code in the host environment.
* See the documentation on [[QuickJSAsyncWASMModule]], [[QuickJSAsyncRuntime]],
* and [[QuickJSAsyncContext]].
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
export declare function newQuickJSAsyncWASMModule(
/**
* Optionally, pass a {@link AsyncBuildVariant} to construct a different WebAssembly module.
*/
variant?: AsyncBuildVariant): Promise<QuickJSAsyncWASMModule>;
/**
* Helper intended to memoize the creation of a WebAssembly module.
* ```typescript
* const getDebugModule = memoizePromiseFactory(() => newQuickJSWASMModule(DEBUG_SYNC))
* ```
*/
export declare function memoizePromiseFactory<T>(fn: () => Promise<T>): () => Promise<T>;
/**
* This build variant is compiled with `-fsanitize=leak`. It instruments all
* memory allocations and when combined with sourcemaps, can present stack trace
* locations where memory leaks occur.
*
* See [[TestQuickJSWASMModule]] which provides access to the leak sanitizer via
* {@link TestQuickJSWASMModule.assertNoMemoryAllocated}.
*
* The downside is that it's 100-1000x slower than the other variants.
* Suggested use case: automated testing, regression testing, and interactive
* debugging.
*/
export declare const DEBUG_SYNC: SyncBuildVariant;
/**
* This is the default (synchronous) build variant.
* {@link getQuickJS} returns a memoized instance of this build variant.
*/
export declare const RELEASE_SYNC: SyncBuildVariant;
/**
* The async debug build variant may or may not have the sanitizer enabled.
* It does print a lot of debug logs.
*
* Suggested use case: interactive debugging only.
*/
export declare const DEBUG_ASYNC: AsyncBuildVariant;
/**
* This is the default asyncified build variant.
*/
export declare const RELEASE_ASYNC: AsyncBuildVariant;

View File

@@ -0,0 +1,169 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RELEASE_ASYNC = exports.DEBUG_ASYNC = exports.RELEASE_SYNC = exports.DEBUG_SYNC = exports.memoizePromiseFactory = exports.newQuickJSAsyncWASMModule = exports.newQuickJSWASMModule = void 0;
const esmHelpers_1 = require("./esmHelpers");
/**
* Create a new, completely isolated WebAssembly module containing the QuickJS library.
* See the documentation on [[QuickJSWASMModule]].
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
async function newQuickJSWASMModule(
/**
* Optionally, pass a {@link SyncBuildVariant} to construct a different WebAssembly module.
*/
variant = exports.RELEASE_SYNC) {
const [wasmModuleLoader, QuickJSFFI, { QuickJSWASMModule }] = await Promise.all([
variant.importModuleLoader(),
variant.importFFI(),
Promise.resolve().then(() => __importStar(require("./module.js"))).then(esmHelpers_1.unwrapTypescript),
]);
const wasmModule = await wasmModuleLoader();
wasmModule.type = "sync";
const ffi = new QuickJSFFI(wasmModule);
return new QuickJSWASMModule(wasmModule, ffi);
}
exports.newQuickJSWASMModule = newQuickJSWASMModule;
/**
* Create a new, completely isolated WebAssembly module containing a version of the QuickJS library
* compiled with Emscripten's [ASYNCIFY](https://emscripten.org/docs/porting/asyncify.html) transform.
*
* This version of the library offers features that enable synchronous code
* inside the VM to interact with asynchronous code in the host environment.
* See the documentation on [[QuickJSAsyncWASMModule]], [[QuickJSAsyncRuntime]],
* and [[QuickJSAsyncContext]].
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
async function newQuickJSAsyncWASMModule(
/**
* Optionally, pass a {@link AsyncBuildVariant} to construct a different WebAssembly module.
*/
variant = exports.RELEASE_ASYNC) {
const [wasmModuleLoader, QuickJSAsyncFFI, { QuickJSAsyncWASMModule }] = await Promise.all([
variant.importModuleLoader(),
variant.importFFI(),
Promise.resolve().then(() => __importStar(require("./module-asyncify.js"))).then(esmHelpers_1.unwrapTypescript),
]);
const wasmModule = await wasmModuleLoader();
wasmModule.type = "async";
const ffi = new QuickJSAsyncFFI(wasmModule);
return new QuickJSAsyncWASMModule(wasmModule, ffi);
}
exports.newQuickJSAsyncWASMModule = newQuickJSAsyncWASMModule;
/**
* Helper intended to memoize the creation of a WebAssembly module.
* ```typescript
* const getDebugModule = memoizePromiseFactory(() => newQuickJSWASMModule(DEBUG_SYNC))
* ```
*/
function memoizePromiseFactory(fn) {
let promise;
return () => {
return (promise ?? (promise = fn()));
};
}
exports.memoizePromiseFactory = memoizePromiseFactory;
/**
* This build variant is compiled with `-fsanitize=leak`. It instruments all
* memory allocations and when combined with sourcemaps, can present stack trace
* locations where memory leaks occur.
*
* See [[TestQuickJSWASMModule]] which provides access to the leak sanitizer via
* {@link TestQuickJSWASMModule.assertNoMemoryAllocated}.
*
* The downside is that it's 100-1000x slower than the other variants.
* Suggested use case: automated testing, regression testing, and interactive
* debugging.
*/
exports.DEBUG_SYNC = {
type: "sync",
async importFFI() {
throw new Error("not implemented");
// const mod = await import("./generated/ffi.WASM_DEBUG_SYNC.js")
// return unwrapTypescript(mod).QuickJSFFI
},
async importModuleLoader() {
throw new Error("not implemented");
// const mod = await import("./generated/emscripten-module.WASM_DEBUG_SYNC.js")
// return unwrapJavascript(mod).default
},
};
/**
* This is the default (synchronous) build variant.
* {@link getQuickJS} returns a memoized instance of this build variant.
*/
exports.RELEASE_SYNC = {
type: "sync",
async importFFI() {
const mod = await Promise.resolve().then(() => __importStar(require("./generated/ffi.WASM_RELEASE_SYNC.js")));
return (0, esmHelpers_1.unwrapTypescript)(mod).QuickJSFFI;
},
async importModuleLoader() {
const mod = await Promise.resolve().then(() => __importStar(require("./generated/emscripten-module.WASM_RELEASE_SYNC.js")));
return (0, esmHelpers_1.unwrapJavascript)(mod);
},
};
/**
* The async debug build variant may or may not have the sanitizer enabled.
* It does print a lot of debug logs.
*
* Suggested use case: interactive debugging only.
*/
exports.DEBUG_ASYNC = {
type: "async",
async importFFI() {
throw new Error("not implemented");
// const mod = await import("./generated/ffi.WASM_DEBUG_ASYNCIFY.js")
// return unwrapTypescript(mod).QuickJSAsyncFFI
},
async importModuleLoader() {
throw new Error("not implemented");
// const mod = await import("./generated/emscripten-module.WASM_DEBUG_ASYNCIFY.js")
// return unwrapJavascript(mod).default
},
};
/**
* This is the default asyncified build variant.
*/
exports.RELEASE_ASYNC = {
type: "async",
async importFFI() {
throw new Error("not implemented");
// const mod = await import("./generated/ffi.WASM_RELEASE_ASYNCIFY.js")
// return unwrapTypescript(mod).QuickJSAsyncFFI
},
async importModuleLoader() {
throw new Error("not implemented");
// const mod = await import("./generated/emscripten-module.WASM_RELEASE_ASYNCIFY.js")
// return unwrapJavascript(mod).default
},
};
//# sourceMappingURL=variants.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,68 @@
/**
* Used as an optional.
* `{ value: S } | { error: E }`.
*/
export type SuccessOrFail<S, F> = {
value: S;
error?: undefined;
} | {
error: F;
};
export declare function isSuccess<S, F>(successOrFail: SuccessOrFail<S, F>): successOrFail is {
value: S;
};
export declare function isFail<S, F>(successOrFail: SuccessOrFail<S, F>): successOrFail is {
error: F;
};
/**
* Used as an optional for results of a Vm call.
* `{ value: VmHandle } | { error: VmHandle }`.
*/
export type VmCallResult<VmHandle> = SuccessOrFail<VmHandle, VmHandle>;
/**
* A VmFunctionImplementation takes handles as arguments.
* It should return a handle, or be void.
*
* To indicate an exception, a VMs can throw either a handle (transferred
* directly) or any other Javascript value (only the poperties `name` and
* `message` will be transferred). Or, the VmFunctionImplementation may return
* a VmCallResult's `{ error: handle }` error variant.
*
* VmFunctionImplementation should not free its arguments or its return value.
* It should not retain a reference to its return value or thrown error.
*/
export type VmFunctionImplementation<VmHandle> = (this: VmHandle, ...args: VmHandle[]) => VmHandle | VmCallResult<VmHandle> | void;
/**
* A minimal interface to a Javascript execution environment.
*
* Higher-level tools should build over the LowLevelJavascriptVm interface to
* share as much as possible between executors.
*
* From https://www.figma.com/blog/how-we-built-the-figma-plugin-system/
*/
export interface LowLevelJavascriptVm<VmHandle> {
global: VmHandle;
undefined: VmHandle;
typeof(handle: VmHandle): string;
getNumber(handle: VmHandle): number;
getString(handle: VmHandle): string;
newNumber(value: number): VmHandle;
newString(value: string): VmHandle;
newObject(prototype?: VmHandle): VmHandle;
newFunction(name: string, value: VmFunctionImplementation<VmHandle>): VmHandle;
getProp(handle: VmHandle, key: string | VmHandle): VmHandle;
setProp(handle: VmHandle, key: string | VmHandle, value: VmHandle): void;
defineProp(handle: VmHandle, key: string | VmHandle, descriptor: VmPropertyDescriptor<VmHandle>): void;
callFunction(func: VmHandle, thisVal: VmHandle, ...args: VmHandle[]): VmCallResult<VmHandle>;
evalCode(code: string, filename?: string): VmCallResult<VmHandle>;
}
/**
* From https://www.figma.com/blog/how-we-built-the-figma-plugin-system/
*/
export interface VmPropertyDescriptor<VmHandle> {
value?: VmHandle;
configurable?: boolean;
enumerable?: boolean;
get?: (this: VmHandle) => VmHandle;
set?: (this: VmHandle, value: VmHandle) => void;
}

View File

@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFail = exports.isSuccess = void 0;
function isSuccess(successOrFail) {
return "error" in successOrFail === false;
}
exports.isSuccess = isSuccess;
function isFail(successOrFail) {
return "error" in successOrFail === true;
}
exports.isFail = isFail;
//# sourceMappingURL=vm-interface.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"vm-interface.js","sourceRoot":"","sources":["../ts/vm-interface.ts"],"names":[],"mappings":";;;AAaA,SAAgB,SAAS,CAAO,aAAkC;IAChE,OAAO,OAAO,IAAI,aAAa,KAAK,KAAK,CAAA;AAC3C,CAAC;AAFD,8BAEC;AAED,SAAgB,MAAM,CAAO,aAAkC;IAC7D,OAAO,OAAO,IAAI,aAAa,KAAK,IAAI,CAAA;AAC1C,CAAC;AAFD,wBAEC","sourcesContent":["/**\n * Used as an optional.\n * `{ value: S } | { error: E }`.\n */\nexport type SuccessOrFail<S, F> =\n | {\n value: S\n error?: undefined\n }\n | {\n error: F\n }\n\nexport function isSuccess<S, F>(successOrFail: SuccessOrFail<S, F>): successOrFail is { value: S } {\n return \"error\" in successOrFail === false\n}\n\nexport function isFail<S, F>(successOrFail: SuccessOrFail<S, F>): successOrFail is { error: F } {\n return \"error\" in successOrFail === true\n}\n\n/**\n * Used as an optional for results of a Vm call.\n * `{ value: VmHandle } | { error: VmHandle }`.\n */\nexport type VmCallResult<VmHandle> = SuccessOrFail<VmHandle, VmHandle>\n\n/**\n * A VmFunctionImplementation takes handles as arguments.\n * It should return a handle, or be void.\n *\n * To indicate an exception, a VMs can throw either a handle (transferred\n * directly) or any other Javascript value (only the poperties `name` and\n * `message` will be transferred). Or, the VmFunctionImplementation may return\n * a VmCallResult's `{ error: handle }` error variant.\n *\n * VmFunctionImplementation should not free its arguments or its return value.\n * It should not retain a reference to its return value or thrown error.\n */\nexport type VmFunctionImplementation<VmHandle> = (\n this: VmHandle,\n ...args: VmHandle[]\n) => VmHandle | VmCallResult<VmHandle> | void\n\n/**\n * A minimal interface to a Javascript execution environment.\n *\n * Higher-level tools should build over the LowLevelJavascriptVm interface to\n * share as much as possible between executors.\n *\n * From https://www.figma.com/blog/how-we-built-the-figma-plugin-system/\n */\nexport interface LowLevelJavascriptVm<VmHandle> {\n global: VmHandle\n undefined: VmHandle\n\n typeof(handle: VmHandle): string\n\n getNumber(handle: VmHandle): number\n getString(handle: VmHandle): string\n\n newNumber(value: number): VmHandle\n newString(value: string): VmHandle\n newObject(prototype?: VmHandle): VmHandle\n newFunction(name: string, value: VmFunctionImplementation<VmHandle>): VmHandle\n\n // For accessing properties of objects\n getProp(handle: VmHandle, key: string | VmHandle): VmHandle\n setProp(handle: VmHandle, key: string | VmHandle, value: VmHandle): void\n defineProp(\n handle: VmHandle,\n key: string | VmHandle,\n descriptor: VmPropertyDescriptor<VmHandle>\n ): void\n\n callFunction(func: VmHandle, thisVal: VmHandle, ...args: VmHandle[]): VmCallResult<VmHandle>\n evalCode(code: string, filename?: string): VmCallResult<VmHandle>\n}\n\n/**\n * From https://www.figma.com/blog/how-we-built-the-figma-plugin-system/\n */\nexport interface VmPropertyDescriptor<VmHandle> {\n value?: VmHandle\n configurable?: boolean\n enumerable?: boolean\n get?: (this: VmHandle) => VmHandle\n set?: (this: VmHandle, value: VmHandle) => void\n}\n"]}

View File

@@ -0,0 +1,60 @@
{
"name": "@tootallnate/quickjs-emscripten",
"version": "0.23.0",
"main": "dist/index.js",
"sideEffects": false,
"license": "MIT",
"keywords": [
"eval",
"quickjs",
"vm",
"interpreter",
"runtime",
"safe",
"emscripten",
"wasm"
],
"repository": {
"type": "git",
"url": "https://github.com/justjake/quickjs-emscripten"
},
"files": [
"c/interface.c",
"dist/**/*",
"!dist/*.test.js",
"!dist/*.tsbuildinfo"
],
"scripts": {
"tarball": "make build/quickjs-emscripten.tgz",
"clean": "make clean",
"tsc": "node_modules/.bin/tsc",
"build": "make dist",
"doc": "typedoc",
"test": "TS_NODE_TRANSPILE_ONLY=true mocha 'ts/**/*.test.ts'",
"test-dist": "cd dist && TS_NODE_TRANSPILE_ONLY=true mocha --require source-map-support/register *.test.js",
"test-fast": "TEST_NO_ASYNC=true yarn test 'ts/**/*.test.ts'",
"test-all": "TEST_LEAK=1 yarn test && TEST_LEAK=1 yarn test-dist",
"prettier": "prettier --write .",
"prettier-check": "prettier --check .",
"update-quickjs": "git subtree pull --prefix=quickjs --squash git@github.com:bellard/quickjs.git master",
"smoketest-node": "yarn tarball && ./scripts/smoketest-node.sh",
"smoketest-cra": "yarn tarball && ./scripts/smoketest-website.sh"
},
"devDependencies": {
"@types/emscripten": "^1.38.0",
"@types/fs-extra": "^9.0.13",
"@types/mocha": "^5.2.7",
"@types/node": "^13.1.4",
"fs-extra": "^10.0.1",
"markserv": "^1.17.4",
"mocha": "7.2.0",
"node-fetch-commonjs": "^3.1.1",
"prettier": "2.8.4",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typedoc": "^0.22.0",
"typedoc-plugin-inline-sources": "^1.0.1",
"typedoc-plugin-markdown": "^3.11.12",
"typescript": "^4.9.5"
}
}