This commit is contained in:
nik
2025-10-03 22:27:28 +03:00
parent 829fad0e17
commit 871cf7e792
16520 changed files with 2967597 additions and 3 deletions

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"]}