quickjs-emscripten-sync 1.9.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -6,13 +6,17 @@ import type {
6
6
  SuccessOrFail,
7
7
  VmCallResult,
8
8
  Intrinsics,
9
+ ContextEvalOptions,
10
+ InterruptHandler,
9
11
  } from "quickjs-emscripten";
10
12
 
11
- import { wrapContext, QuickJSContextEx } from "./contextex";
13
+ import type { QuickJSContextEx } from "./contextex";
14
+ import { wrapContext } from "./contextex";
12
15
  import { defaultRegisteredObjects } from "./default";
13
16
  import marshal from "./marshal";
14
17
  import unmarshal from "./unmarshal";
15
18
  import unmarshalHostRef from "./unmarshal/hostref";
19
+ import unmarshalPrimitive from "./unmarshal/primitive";
16
20
  import { complexity, isES2015Class, isObject, walkObject } from "./util";
17
21
  import VMMap from "./vmmap";
18
22
  import {
@@ -26,7 +30,8 @@ import {
26
30
  enableFnCache,
27
31
  disposeFnCache,
28
32
  } from "./vmutil";
29
- import { wrap, wrapHandle, unwrap, unwrapHandle, Wrapped } from "./wrapper";
33
+ import type { Wrapped, WrapHandle } from "./wrapper";
34
+ import { wrap, createWrapHandle, unwrap, unwrapHandle } from "./wrapper";
30
35
 
31
36
  export {
32
37
  VMMap,
@@ -43,7 +48,7 @@ export {
43
48
  consumeAll,
44
49
  };
45
50
 
46
- export type { Intrinsics };
51
+ export type { Intrinsics, ContextEvalOptions, InterruptHandler };
47
52
 
48
53
  export type Options = {
49
54
  /** A callback that returns a boolean value that determines whether an object is marshalled or not. If false, no marshaling will be done and undefined will be passed to the QuickJS VM, otherwise marshaling will be done. By default, all objects will be marshalled. */
@@ -69,6 +74,34 @@ export type Options = {
69
74
  syncEnabled?: boolean;
70
75
  /** A callback that returns whether an object should be passed to the VM by reference (as an opaque HostRef) instead of being marshalled by value/proxy. The guest cannot read such objects, but can hold them and pass them back to the host, where they resolve to the original object. */
71
76
  marshalByReference?: (target: any) => boolean;
77
+ /**
78
+ * Marshal selected host functions as *asyncified* functions. Normally an async
79
+ * host function is marshalled like any other function, so the guest receives a
80
+ * marshalled `Promise`. When a function is asyncified, the QuickJS VM instead
81
+ * suspends until the host promise settles and the guest receives the resolved
82
+ * value synchronously (as if the call were blocking).
83
+ *
84
+ * - `true`: asyncify every host function that is *declared* async, auto-detected
85
+ * via `Object.prototype.toString.call(fn) === "[object AsyncFunction]"`. Note
86
+ * that functions transpiled to ES5 (e.g. by Babel/TypeScript targeting older
87
+ * runtimes) are plain functions returning a promise and are **not** detected —
88
+ * use the predicate form for those.
89
+ * - predicate: asyncify exactly the functions for which it returns `true`. It is
90
+ * called with the *unwrapped* target (the original function, not a sync proxy),
91
+ * like `isMarshalable`.
92
+ * - absent/`false`: current behavior; async functions yield a `Promise` in the guest.
93
+ *
94
+ * Constraints (Emscripten Asyncify):
95
+ * - Asyncified functions only work when evaluation is entered through an async
96
+ * entry point ({@link AsyncArena.evalCodeAsync}). Calling one during a
97
+ * synchronous `evalCode` fails with `Error: Function unexpectedly returned a Promise`.
98
+ * - Only ONE suspended call is allowed at a time; concurrent suspension throws a
99
+ * runtime error from quickjs-emscripten.
100
+ * - If the context does not support `newAsyncifiedFunction` (a plain sync context
101
+ * from `getQuickJS().newContext()`), this option is silently ignored and async
102
+ * functions behave as today (Promise).
103
+ */
104
+ asyncify?: boolean | ((target: (...args: any[]) => any) => boolean);
72
105
  };
73
106
 
74
107
  /**
@@ -91,6 +124,7 @@ export class Arena {
91
124
  _temporalSync = new Set<any>();
92
125
  _symbol = Symbol();
93
126
  _symbolHandle: QuickJSHandle;
127
+ _wrapHandleImpl: WrapHandle;
94
128
  _options?: Options;
95
129
 
96
130
  /** Constructs a new Arena instance. It requires a quickjs-emscripten context initialized with `quickjs.newContext()`. */
@@ -107,6 +141,17 @@ export class Arena {
107
141
  enableFnCache(this.context);
108
142
  this._options = options;
109
143
  this._symbolHandle = ctx.unwrapResult(ctx.evalCode(`Symbol()`));
144
+ // One proxyFuncs handle shared by every wrapped handle for the Arena's
145
+ // lifetime, instead of allocating a fresh VM function per wrap.
146
+ this._wrapHandleImpl = createWrapHandle(
147
+ this.context,
148
+ this._symbol,
149
+ this._symbolHandle,
150
+ this._unmarshal,
151
+ this._syncMode,
152
+ this._options?.isHandleWrappable,
153
+ this._options?.syncEnabled ?? true,
154
+ );
110
155
  this._map = new VMMap(ctx);
111
156
  this._registeredMap = new VMMap(ctx);
112
157
  this.registerAll(options?.registeredObjects ?? defaultRegisteredObjects);
@@ -122,6 +167,7 @@ export class Arena {
122
167
  this._transientHandles.clear();
123
168
  this._map.dispose();
124
169
  this._registeredMap.dispose();
170
+ this._wrapHandleImpl.dispose();
125
171
  this._symbolHandle.dispose();
126
172
  disposeFnCache(this.context);
127
173
  this.context.disposeEx?.();
@@ -134,9 +180,15 @@ export class Arena {
134
180
 
135
181
  /**
136
182
  * Evaluate JS code in the VM and get the result as an object on the host side. It also converts and re-throws error objects when an error is thrown during evaluation.
183
+ *
184
+ * @param code - The JS code to evaluate
185
+ * @param filename - Optional filename used in error stacks and for debugging
186
+ * @param options - Optional eval options forwarded to quickjs-emscripten's
187
+ * `evalCode`, either an `EvalFlags` bitmask (`number`) or a
188
+ * {@link ContextEvalOptions} object (e.g. `{ strict: true }`)
137
189
  */
138
- evalCode<T = any>(code: string): T {
139
- const handle = this.context.evalCode(code);
190
+ evalCode<T = any>(code: string, filename?: string, options?: number | ContextEvalOptions): T {
191
+ const handle = this.context.evalCode(code, filename, options);
140
192
  return this._unwrapResultAndUnmarshal(handle);
141
193
  }
142
194
 
@@ -147,6 +199,10 @@ export class Arena {
147
199
  *
148
200
  * @param code - The ES module code to evaluate
149
201
  * @param filename - Optional filename for debugging purposes (default: "module.js")
202
+ * @param options - Optional {@link ContextEvalOptions}. `type` is always forced
203
+ * to `"module"`, so any `type` in `options` is overridden. A numeric
204
+ * `EvalFlags` bitmask cannot be merged with the forced module type and is
205
+ * therefore not supported here; if a number is passed it is ignored.
150
206
  * @returns The module's exports object, or a Promise resolving to exports if using top-level await
151
207
  *
152
208
  * @example
@@ -172,8 +228,15 @@ export class Arena {
172
228
  * console.log(exports.data); // 123
173
229
  * ```
174
230
  */
175
- evalModule<T = any>(code: string, filename = "module.js"): T | Promise<T> {
176
- const handle = this.context.evalCode(code, filename, { type: "module" });
231
+ evalModule<T = any>(
232
+ code: string,
233
+ filename = "module.js",
234
+ options?: number | ContextEvalOptions,
235
+ ): T | Promise<T> {
236
+ const handle = this.context.evalCode(code, filename, {
237
+ ...(typeof options === "number" ? {} : options),
238
+ type: "module",
239
+ });
177
240
  return this._unwrapResultAndUnmarshal(handle);
178
241
  }
179
242
 
@@ -236,6 +299,45 @@ export class Arena {
236
299
  this.context.runtime.setMaxStackSize(stackSize);
237
300
  }
238
301
 
302
+ /**
303
+ * Set a callback which is regularly called by the QuickJS engine when it is
304
+ * executing code. This callback can be used to implement an execution
305
+ * timeout: return `true` from the handler to interrupt the running code.
306
+ *
307
+ * This is useful for preventing runaway CPU usage (e.g. infinite loops) in
308
+ * untrusted code.
309
+ *
310
+ * @param cb - The interrupt handler. Return `true` to interrupt execution.
311
+ *
312
+ * @example
313
+ * ```js
314
+ * // Interrupt evaluation after 1 second
315
+ * const deadline = Date.now() + 1000;
316
+ * arena.setInterruptHandler(() => Date.now() > deadline);
317
+ *
318
+ * try {
319
+ * arena.evalCode(`while (true) {}`);
320
+ * } catch (e) {
321
+ * console.log("Execution interrupted");
322
+ * }
323
+ * ```
324
+ */
325
+ setInterruptHandler(cb: InterruptHandler): void {
326
+ this.context.runtime.setInterruptHandler(cb);
327
+ }
328
+
329
+ /**
330
+ * Remove the interrupt handler previously set with `setInterruptHandler`.
331
+ *
332
+ * @example
333
+ * ```js
334
+ * arena.removeInterruptHandler();
335
+ * ```
336
+ */
337
+ removeInterruptHandler(): void {
338
+ this.context.runtime.removeInterruptHandler();
339
+ }
340
+
239
341
  /**
240
342
  * Get detailed memory usage statistics for this runtime.
241
343
  *
@@ -407,6 +509,15 @@ export class Arena {
407
509
  return !!this._options?.marshalByReference?.(this._unwrap(t));
408
510
  };
409
511
 
512
+ _asyncify = (t: unknown): boolean => {
513
+ const a = this._options?.asyncify;
514
+ if (!a) return false;
515
+ const target = this._unwrap(t);
516
+ if (typeof a === "function") return typeof target === "function" && a(target as any);
517
+ // `a === true`: auto-detect functions declared with `async`.
518
+ return Object.prototype.toString.call(target) === "[object AsyncFunction]";
519
+ };
520
+
410
521
  // Register an opaque HostRef handle by its host value without wrapping it.
411
522
  _registerHostRef = (t: unknown, handle: QuickJSHandle): QuickJSHandle => {
412
523
  const u = this._unwrap(t);
@@ -490,6 +601,9 @@ export class Arena {
490
601
  registerTransient: this._registerTransient,
491
602
  disposeTransient: this._disposeTransient,
492
603
  preApply: this._marshalPreApply,
604
+ prepareReturn: this._prepareMarshalReturn,
605
+ unwrap: t => this._unwrap(t),
606
+ asyncify: this._options?.asyncify ? this._asyncify : undefined,
493
607
  custom: this._options?.customMarshaller,
494
608
  });
495
609
 
@@ -498,7 +612,27 @@ export class Arena {
498
612
  this._transientHandles.delete(handle);
499
613
 
500
614
  const syncEnabled = this._options?.syncEnabled ?? true;
501
- return [handle, !syncEnabled || !this._map.hasHandle(handle)];
615
+ // A non-object (primitive) handle is never retained in `_map` (registered
616
+ // primitives already returned above via `_registeredMap`), so skip the
617
+ // `hasHandle` VM roundtrip and mark it disposable directly.
618
+ if (!syncEnabled || !isObject(target)) return [handle, true];
619
+ return [handle, !this._map.hasHandle(handle)];
620
+ };
621
+
622
+ _prepareMarshalReturn = (h: QuickJSHandle): QuickJSHandle => {
623
+ // A host function's return value is disposed by the VM once it is consumed.
624
+ // When sync is on, the VMMap retains object handles for identity, so the
625
+ // handle we hand back is the one the map owns: returning it directly would
626
+ // let the VM dispose the map's copy, leaving a stale entry that breaks
627
+ // `x === fn()` identity across calls. Hand the VM a dup instead and keep
628
+ // ours alive. With sync off, handles are not retained, so this is a no-op.
629
+ const syncEnabled = this._options?.syncEnabled ?? true;
630
+ // Only object handles are retained in `_map`; a primitive return can never
631
+ // be in the map, so `isHandleObject` (a cheap typeof) skips the `hasHandle`
632
+ // VM roundtrip for primitive returns from host functions.
633
+ return syncEnabled && h.alive && isHandleObject(this.context, h) && this._map.hasHandle(h)
634
+ ? h.dup()
635
+ : h;
502
636
  };
503
637
 
504
638
  _preUnmarshal = (t: any, h: QuickJSHandle): Wrapped<any> => {
@@ -510,6 +644,14 @@ export class Arena {
510
644
  };
511
645
 
512
646
  _unmarshal = (handle: QuickJSHandle): any => {
647
+ // Primitives (undefined/number/string/boolean/bigint/null) are resolved by a
648
+ // cheap host-side `ctx.typeof`, skipping the `_registeredMap` VM lookup,
649
+ // HostRef resolution, and `_wrapHandle`. The VMMap only ever keys objects and
650
+ // symbols, so a primitive handle can never match `getByHandle`; symbols fall
651
+ // through `unmarshalPrimitive` and still reach the lookup below.
652
+ const [primitive, ok] = unmarshalPrimitive(this.context, handle);
653
+ if (ok) return primitive;
654
+
513
655
  const registered = this._registeredMap.getByHandle(handle);
514
656
  if (typeof registered !== "undefined") {
515
657
  return registered;
@@ -521,15 +663,29 @@ export class Arena {
521
663
  if (ref) return ref.value;
522
664
  }
523
665
 
524
- const [wrappedHandle] = this._wrapHandle(handle);
525
- return unmarshal(wrappedHandle ?? handle, {
526
- ctx: this.context,
527
- marshal: this._marshal,
528
- find: this._unmarshalFind,
529
- pre: this._preUnmarshal,
530
- custom: this._options?.customUnmarshaller,
531
- hostRef: !!this._options?.marshalByReference,
532
- });
666
+ const [wrappedHandle, wrapped] = this._wrapHandle(handle);
667
+ let done = false;
668
+ try {
669
+ const result = unmarshal(wrappedHandle ?? handle, {
670
+ ctx: this.context,
671
+ marshal: this._marshal,
672
+ find: this._unmarshalFind,
673
+ pre: this._preUnmarshal,
674
+ custom: this._options?.customUnmarshaller,
675
+ hostRef: !!this._options?.marshalByReference,
676
+ });
677
+ done = true;
678
+ return result;
679
+ } finally {
680
+ // A mid-flight abort (e.g. an OOM inside the unmarshal chain) can throw
681
+ // before the freshly created proxy handle was registered in the map,
682
+ // orphaning it. Dispose it here; if it was already registered the map
683
+ // skips it (alive check) at dispose. Disposing a handle never allocates,
684
+ // so this is safe even under an active memory limit.
685
+ if (!done && wrapped && wrappedHandle?.alive && wrappedHandle !== handle) {
686
+ wrappedHandle.dispose();
687
+ }
688
+ }
533
689
  };
534
690
 
535
691
  _register(
@@ -543,24 +699,41 @@ export class Arena {
543
699
  }
544
700
 
545
701
  let wrappedT = this._wrap(t);
546
- const [wrappedH] = this._wrapHandle(h);
702
+ // `wrappedHNew` is true when `_wrapHandle` allocated a fresh VM proxy (which
703
+ // we own) rather than returning the original handle. If registration fails
704
+ // or throws mid-flight (e.g. an OOM in `map.set`), that proxy would be
705
+ // orphaned, so the finally below disposes it.
706
+ const [wrappedH, wrappedHNew] = this._wrapHandle(h);
547
707
  const isPromise = t instanceof Promise;
548
708
  if (!wrappedH || (!wrappedT && !isPromise)) return; // t or h is not an object
549
709
  if (isPromise) wrappedT = t;
550
710
 
551
711
  const unwrappedT = this._unwrap(t);
552
- const [unwrappedH, unwrapped] = this._unwrapHandle(h);
553
-
554
- const res = map.set(wrappedT, wrappedH, unwrappedT, unwrappedH);
555
- if (!res) {
556
- // already registered
557
- if (unwrapped) unwrappedH.dispose();
558
- throw new Error("already registered");
559
- } else if (sync) {
560
- this._sync.add(unwrappedT);
712
+ let unwrappedH: QuickJSHandle | undefined;
713
+ let unwrapped = false;
714
+ let done = false;
715
+ try {
716
+ [unwrappedH, unwrapped] = this._unwrapHandle(h);
717
+
718
+ const res = map.set(wrappedT, wrappedH, unwrappedT, unwrappedH);
719
+ if (!res) {
720
+ // already registered
721
+ throw new Error("already registered");
722
+ } else if (sync) {
723
+ this._sync.add(unwrappedT);
724
+ }
725
+
726
+ done = true;
727
+ return [wrappedT, wrappedH];
728
+ } finally {
729
+ if (!done) {
730
+ // `map.set` did not take ownership (it threw or returned false): dispose
731
+ // the handles we created so they are not orphaned. A non-new `wrappedH`
732
+ // and a non-wrapped `unwrappedH` are the caller's handle `h`, left alone.
733
+ if (wrappedHNew && wrappedH.alive) wrappedH.dispose();
734
+ if (unwrapped && unwrappedH?.alive) unwrappedH.dispose();
735
+ }
561
736
  }
562
-
563
- return [wrappedT, wrappedH];
564
737
  }
565
738
 
566
739
  _syncMode = (obj: any): "both" | undefined => {
@@ -591,16 +764,7 @@ export class Arena {
591
764
  };
592
765
 
593
766
  _wrapHandle(handle: QuickJSHandle): [Wrapped<QuickJSHandle> | undefined, boolean] {
594
- return wrapHandle(
595
- this.context,
596
- handle,
597
- this._symbol,
598
- this._symbolHandle,
599
- this._unmarshal,
600
- this._syncMode,
601
- this._options?.isHandleWrappable,
602
- this._options?.syncEnabled ?? true,
603
- );
767
+ return this._wrapHandleImpl.wrapHandle(handle);
604
768
  }
605
769
 
606
770
  _unwrapHandle(target: QuickJSHandle): [QuickJSHandle, boolean] {
@@ -626,9 +790,19 @@ export class AsyncArena extends Arena {
626
790
  * Evaluate JS code asynchronously in the VM and get the result on the host
627
791
  * side. Like `evalCode`, it converts and re-throws errors thrown during
628
792
  * evaluation. Use this when the code awaits host-provided promises.
793
+ *
794
+ * @param code - The JS code to evaluate
795
+ * @param filename - Optional filename used in error stacks and for debugging
796
+ * @param options - Optional eval options forwarded to quickjs-emscripten's
797
+ * `evalCodeAsync`, either an `EvalFlags` bitmask (`number`) or a
798
+ * {@link ContextEvalOptions} object (e.g. `{ strict: true }`)
629
799
  */
630
- async evalCodeAsync<T = any>(code: string, filename?: string): Promise<T> {
631
- const result = await this.asyncContext.evalCodeAsync(code, filename);
800
+ async evalCodeAsync<T = any>(
801
+ code: string,
802
+ filename?: string,
803
+ options?: number | ContextEvalOptions,
804
+ ): Promise<T> {
805
+ const result = await this.asyncContext.evalCodeAsync(code, filename, options);
632
806
  return this._unwrapResultAndUnmarshal(result);
633
807
  }
634
808
  }
@@ -1,6 +1,6 @@
1
1
  import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
2
2
 
3
- import { call, consumeAll } from "../vmutil";
3
+ import { call, consume, consumeAll } from "../vmutil";
4
4
 
5
5
  export default function marshalCustom(
6
6
  ctx: QuickJSContext,
@@ -13,24 +13,33 @@ export default function marshalCustom(
13
13
  handle = c(target, ctx);
14
14
  if (handle) break;
15
15
  }
16
- return handle ? preMarshal(target, handle) ?? handle : undefined;
16
+ if (!handle) return undefined;
17
+ // Own the custom-marshalled handle until `preMarshal` registers it; dispose it
18
+ // if `preMarshal` throws mid-flight so it is not orphaned.
19
+ let owned = true;
20
+ try {
21
+ const result = preMarshal(target, handle) ?? handle;
22
+ owned = false;
23
+ return result;
24
+ } finally {
25
+ if (owned && handle.alive) handle.dispose();
26
+ }
17
27
  }
18
28
 
19
29
  export function symbol(target: unknown, ctx: QuickJSContext): QuickJSHandle | undefined {
20
30
  if (typeof target !== "symbol") return;
21
- const handle = call(
22
- ctx,
23
- "d => Symbol(d)",
24
- undefined,
25
- target.description ? ctx.newString(target.description) : ctx.undefined,
26
- );
27
- return handle;
31
+ // `call` does not dispose its arguments, so the description string handle must
32
+ // be consumed here or it leaks on every symbol marshal.
33
+ return target.description
34
+ ? consume(ctx.newString(target.description), d => call(ctx, "d => Symbol(d)", undefined, d))
35
+ : call(ctx, "d => Symbol(d)", undefined, ctx.undefined);
28
36
  }
29
37
 
30
38
  export function date(target: unknown, ctx: QuickJSContext): QuickJSHandle | undefined {
31
39
  if (!(target instanceof Date)) return;
32
- const handle = call(ctx, "d => new Date(d)", undefined, ctx.newNumber(target.getTime()));
33
- return handle;
40
+ // `call` does not dispose its arguments, so the time number handle is consumed
41
+ // here rather than leaked.
42
+ return consume(ctx.newNumber(target.getTime()), d => call(ctx, "d => new Date(d)", undefined, d));
34
43
  }
35
44
 
36
45
  export function arrayBuffer(target: unknown, ctx: QuickJSContext): QuickJSHandle | undefined {
@@ -1,4 +1,5 @@
1
- import { getQuickJS, QuickJSHandle } from "quickjs-emscripten";
1
+ import type { QuickJSHandle } from "quickjs-emscripten";
2
+ import { getQuickJS } from "quickjs-emscripten";
2
3
  import { expect, test, vi } from "vitest";
3
4
 
4
5
  import { json, call } from "../vmutil";
@@ -8,8 +9,10 @@ import marshalFunction from "./function";
8
9
  test("normal func", async () => {
9
10
  const ctx = (await getQuickJS()).newContext();
10
11
 
11
- const marshal = vi.fn(v => json(ctx, v));
12
- const unmarshal = vi.fn(v => (ctx.sameValue(v, ctx.global) ? undefined : ctx.dump(v)));
12
+ const marshal = vi.fn((v) => json(ctx, v));
13
+ const unmarshal = vi.fn((v) =>
14
+ ctx.sameValue(v, ctx.global) ? undefined : ctx.dump(v),
15
+ );
13
16
  const preMarshal = vi.fn((_, a) => a);
14
17
  const innerfn = vi.fn((..._args: any[]) => "hoge");
15
18
  const fn = (...args: any[]) => innerfn(...args);
@@ -30,10 +33,12 @@ test("normal func", async () => {
30
33
  expect(ctx.dump(result)).toBe("hoge");
31
34
  expect(innerfn).toBeCalledWith(1, true);
32
35
  expect(marshal).toHaveBeenLastCalledWith("hoge");
33
- expect(unmarshal).toBeCalledTimes(3);
34
- expect(unmarshal.mock.results[0].value).toBe(undefined); // this
35
- expect(unmarshal.mock.results[1].value).toBe(1);
36
- expect(unmarshal.mock.results[2].value).toBe(true);
36
+ // `this` coerces to the VM global for a plain call, which marshalFunction
37
+ // treats as undefined without unmarshalling it, so only the two args are
38
+ // unmarshalled.
39
+ expect(unmarshal).toBeCalledTimes(2);
40
+ expect(unmarshal.mock.results[0].value).toBe(1);
41
+ expect(unmarshal.mock.results[1].value).toBe(true);
37
42
 
38
43
  handle.dispose();
39
44
  ctx.dispose();
@@ -41,7 +46,7 @@ test("normal func", async () => {
41
46
 
42
47
  test("func which has properties", async () => {
43
48
  const ctx = (await getQuickJS()).newContext();
44
- const marshal = vi.fn(v => json(ctx, v));
49
+ const marshal = vi.fn((v) => json(ctx, v));
45
50
 
46
51
  const fn = () => {};
47
52
  fn.hoge = "foo";
@@ -50,7 +55,7 @@ test("func which has properties", async () => {
50
55
  ctx,
51
56
  fn,
52
57
  marshal,
53
- v => ctx.dump(v),
58
+ (v) => ctx.dump(v),
54
59
  (_, a) => a,
55
60
  );
56
61
  if (!handle) throw new Error("handle is undefined");
@@ -91,16 +96,20 @@ test("class", async () => {
91
96
  if (!handle) throw new Error("handle is undefined");
92
97
 
93
98
  const newA = ctx.unwrapResult(ctx.evalCode(`A => new A(100)`));
94
- const instance = ctx.unwrapResult(ctx.callFunction(newA, ctx.undefined, handle));
99
+ const instance = ctx.unwrapResult(
100
+ ctx.callFunction(newA, ctx.undefined, handle),
101
+ );
95
102
 
96
103
  expect(ctx.dump(ctx.getProp(handle, "name"))).toBe("A");
97
104
  expect(ctx.dump(ctx.getProp(handle, "length"))).toBe(1);
98
- expect(ctx.dump(call(ctx, "(cls, i) => i instanceof cls", undefined, handle, instance))).toBe(
99
- true,
100
- );
105
+ expect(
106
+ ctx.dump(
107
+ call(ctx, "(cls, i) => i instanceof cls", undefined, handle, instance),
108
+ ),
109
+ ).toBe(true);
101
110
  expect(ctx.dump(ctx.getProp(instance, "a"))).toBe(100);
102
111
 
103
- disposables.forEach(d => d.dispose());
112
+ disposables.forEach((d) => d.dispose());
104
113
  instance.dispose();
105
114
  newA.dispose();
106
115
  handle.dispose();
@@ -115,19 +124,34 @@ test("preApply", async () => {
115
124
  if (typeof v === "number") return ctx.newNumber(v);
116
125
  return ctx.null;
117
126
  };
118
- const unmarshal = (v: QuickJSHandle) => (ctx.typeof(v) === "object" ? that : ctx.dump(v));
119
- const preApply = vi.fn((a: (...args: any[]) => any, b: any, c: any[]) => a.apply(b, c) + "!");
127
+ const unmarshal = (v: QuickJSHandle) =>
128
+ ctx.typeof(v) === "object" ? that : ctx.dump(v);
129
+ const preApply = vi.fn(
130
+ (a: (...args: any[]) => any, b: any, c: any[]) => a.apply(b, c) + "!",
131
+ );
120
132
  const that = {};
121
133
  const thatHandle = ctx.newObject();
122
134
 
123
135
  const fn = () => "foo";
124
- const handle = marshalFunction(ctx, fn, marshal, unmarshal, (_, a) => a, preApply);
136
+ const handle = marshalFunction(
137
+ ctx,
138
+ fn,
139
+ marshal,
140
+ unmarshal,
141
+ (_, a) => a,
142
+ preApply,
143
+ );
125
144
  if (!handle) throw new Error("handle is undefined");
126
145
 
127
146
  expect(preApply).toBeCalledTimes(0);
128
147
 
129
148
  const res = ctx.unwrapResult(
130
- ctx.callFunction(handle, thatHandle, ctx.newNumber(100), ctx.newString("hoge")),
149
+ ctx.callFunction(
150
+ handle,
151
+ thatHandle,
152
+ ctx.newNumber(100),
153
+ ctx.newString("hoge"),
154
+ ),
131
155
  );
132
156
 
133
157
  expect(preApply).toBeCalledTimes(1);