quickjs-emscripten-sync 1.1.0 → 1.4.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.
Files changed (71) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +59 -49
  3. package/dist/index.d.ts +121 -26
  4. package/dist/quickjs-emscripten-sync.es.js +962 -0
  5. package/dist/quickjs-emscripten-sync.umd.js +64 -0
  6. package/package.json +28 -40
  7. package/src/default.ts +2 -2
  8. package/src/index.test.ts +371 -69
  9. package/src/index.ts +135 -72
  10. package/src/marshal/function.test.ts +70 -61
  11. package/src/marshal/function.ts +10 -9
  12. package/src/marshal/index.test.ts +135 -62
  13. package/src/marshal/index.ts +33 -16
  14. package/src/marshal/json.test.ts +94 -0
  15. package/src/marshal/json.ts +16 -0
  16. package/src/marshal/object.test.ts +65 -58
  17. package/src/marshal/object.ts +6 -5
  18. package/src/marshal/primitive.test.ts +23 -17
  19. package/src/marshal/primitive.ts +10 -9
  20. package/src/marshal/promise.test.ts +86 -0
  21. package/src/marshal/promise.ts +26 -0
  22. package/src/marshal/properties.test.ts +23 -19
  23. package/src/marshal/properties.ts +11 -10
  24. package/src/marshal/symbol.test.ts +9 -7
  25. package/src/marshal/symbol.ts +5 -4
  26. package/src/unmarshal/function.test.ts +70 -61
  27. package/src/unmarshal/function.ts +39 -30
  28. package/src/unmarshal/index.test.ts +101 -100
  29. package/src/unmarshal/index.ts +13 -9
  30. package/src/unmarshal/object.test.ts +45 -41
  31. package/src/unmarshal/object.ts +14 -13
  32. package/src/unmarshal/primitive.test.ts +20 -15
  33. package/src/unmarshal/primitive.ts +10 -10
  34. package/src/unmarshal/promise.test.ts +44 -0
  35. package/src/unmarshal/promise.ts +38 -0
  36. package/src/unmarshal/properties.test.ts +10 -8
  37. package/src/unmarshal/properties.ts +47 -42
  38. package/src/unmarshal/symbol.test.ts +9 -7
  39. package/src/unmarshal/symbol.ts +4 -4
  40. package/src/util.test.ts +23 -5
  41. package/src/util.ts +15 -0
  42. package/src/vmmap.test.ts +88 -82
  43. package/src/vmmap.ts +21 -17
  44. package/src/vmutil.test.ts +159 -53
  45. package/src/vmutil.ts +91 -19
  46. package/src/wrapper.test.ts +151 -115
  47. package/src/wrapper.ts +69 -48
  48. package/dist/default.d.ts +0 -4
  49. package/dist/index.js +0 -8
  50. package/dist/marshal/function.d.ts +0 -2
  51. package/dist/marshal/index.d.ts +0 -11
  52. package/dist/marshal/object.d.ts +0 -2
  53. package/dist/marshal/primitive.d.ts +0 -2
  54. package/dist/marshal/properties.d.ts +0 -2
  55. package/dist/marshal/symbol.d.ts +0 -2
  56. package/dist/quickjs-emscripten-sync.cjs.development.js +0 -1289
  57. package/dist/quickjs-emscripten-sync.cjs.development.js.map +0 -1
  58. package/dist/quickjs-emscripten-sync.cjs.production.min.js +0 -2
  59. package/dist/quickjs-emscripten-sync.cjs.production.min.js.map +0 -1
  60. package/dist/quickjs-emscripten-sync.esm.js +0 -1272
  61. package/dist/quickjs-emscripten-sync.esm.js.map +0 -1
  62. package/dist/unmarshal/function.d.ts +0 -2
  63. package/dist/unmarshal/index.d.ts +0 -9
  64. package/dist/unmarshal/object.d.ts +0 -2
  65. package/dist/unmarshal/primitive.d.ts +0 -2
  66. package/dist/unmarshal/properties.d.ts +0 -2
  67. package/dist/unmarshal/symbol.d.ts +0 -2
  68. package/dist/util.d.ts +0 -7
  69. package/dist/vmmap.d.ts +0 -35
  70. package/dist/vmutil.d.ts +0 -7
  71. package/dist/wrapper.d.ts +0 -11
package/src/index.ts CHANGED
@@ -1,19 +1,28 @@
1
- import { QuickJSHandle, QuickJSVm } from "quickjs-emscripten";
2
- import {
1
+ import type {
2
+ QuickJSDeferredPromise,
3
+ QuickJSHandle,
4
+ QuickJSContext,
3
5
  SuccessOrFail,
4
6
  VmCallResult,
5
- } from "quickjs-emscripten/dist/vm-interface";
7
+ } from "quickjs-emscripten";
6
8
 
7
9
  import VMMap from "./vmmap";
8
10
  import marshal from "./marshal";
9
11
  import unmarshal from "./unmarshal";
10
12
  import { wrap, wrapHandle, unwrap, unwrapHandle, Wrapped } from "./wrapper";
11
13
  import { complexity, isES2015Class, isObject, walkObject } from "./util";
12
- import { call, eq, isHandleObject, send, consumeAll } from "./vmutil";
14
+ import {
15
+ call,
16
+ eq,
17
+ isHandleObject,
18
+ json,
19
+ consumeAll,
20
+ mayConsume,
21
+ handleFrom,
22
+ } from "./vmutil";
13
23
  import { defaultRegisteredObjects } from "./default";
14
24
 
15
25
  export {
16
- Arena,
17
26
  VMMap,
18
27
  defaultRegisteredObjects,
19
28
  marshal,
@@ -25,25 +34,27 @@ export {
25
34
  call,
26
35
  eq,
27
36
  isHandleObject,
28
- send,
37
+ json,
29
38
  consumeAll,
30
39
  };
31
40
 
32
41
  export type Options = {
33
42
  /** 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. */
34
- isMarshalable?: (target: any) => boolean;
43
+ isMarshalable?: boolean | "json" | ((target: any) => boolean | "json");
35
44
  /** You can pre-register a pair of objects that will be considered the same between the host and the QuickJS VM. This will be used automatically during the conversion. By default, it will be registered automatically with `defaultRegisteredObjects`.
36
45
  *
37
46
  * Instead of a string, you can also pass a QuickJSHandle directly. In that case, however, you have to dispose of them manually when destroying the VM.
38
47
  */
39
48
  registeredObjects?: Iterable<[any, QuickJSHandle | string]>;
49
+ /** Compatibility with quickjs-emscripten prior to v0.15. Inject code for compatibility into context at Arena class initialization time. */
50
+ compat?: boolean;
40
51
  };
41
52
 
42
53
  /**
43
54
  * The Arena class manages all generated handles at once by quickjs-emscripten and automatically converts objects between the host and the QuickJS VM.
44
55
  */
45
- export default class Arena {
46
- vm: QuickJSVm;
56
+ export class Arena {
57
+ context: QuickJSContext;
47
58
  _map: VMMap;
48
59
  _registeredMap: VMMap;
49
60
  _registeredMapDispose: Set<any> = new Set();
@@ -53,13 +64,21 @@ export default class Arena {
53
64
  _symbolHandle: QuickJSHandle;
54
65
  _options?: Options;
55
66
 
56
- /** Constructs a new Arena instance. It requires a quickjs-emscripten VM initialized with `quickjs.createVM()`. */
57
- constructor(vm: QuickJSVm, options?: Options) {
58
- this.vm = vm;
67
+ /** Constructs a new Arena instance. It requires a quickjs-emscripten context initialized with `quickjs.newContext()`. */
68
+ constructor(ctx: QuickJSContext, options?: Options) {
69
+ if (options?.compat && !("runtime" in ctx)) {
70
+ (ctx as any).runtime = {
71
+ hasPendingJob: () => (ctx as any).hasPendingJob(),
72
+ executePendingJobs: (maxJobsToExecute?: number | void) =>
73
+ (ctx as any).executePendingJobs(maxJobsToExecute),
74
+ };
75
+ }
76
+
77
+ this.context = ctx;
59
78
  this._options = options;
60
- this._symbolHandle = vm.unwrapResult(vm.evalCode(`Symbol()`));
61
- this._map = new VMMap(vm);
62
- this._registeredMap = new VMMap(vm);
79
+ this._symbolHandle = ctx.unwrapResult(ctx.evalCode(`Symbol()`));
80
+ this._map = new VMMap(ctx);
81
+ this._registeredMap = new VMMap(ctx);
63
82
  this.registerAll(options?.registeredObjects ?? defaultRegisteredObjects);
64
83
  }
65
84
 
@@ -76,7 +95,7 @@ export default class Arena {
76
95
  * 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.
77
96
  */
78
97
  evalCode<T = any>(code: string): T {
79
- let handle = this.vm.evalCode(code);
98
+ const handle = this.context.evalCode(code);
80
99
  return this._unwrapResultAndUnmarshal(handle);
81
100
  }
82
101
 
@@ -84,11 +103,11 @@ export default class Arena {
84
103
  * Almost same as `vm.executePendingJobs()`, but it converts and re-throws error objects when an error is thrown during evaluation.
85
104
  */
86
105
  executePendingJobs(maxJobsToExecute?: number): number {
87
- const result = this.vm.executePendingJobs(maxJobsToExecute);
106
+ const result = this.context.runtime.executePendingJobs(maxJobsToExecute);
88
107
  if ("value" in result) {
89
108
  return result.value;
90
109
  }
91
- throw result.error.consume(err => this._unmarshal(err));
110
+ throw this._unwrapIfNotSynced(result.error.consume(this._unmarshal));
92
111
  }
93
112
 
94
113
  /**
@@ -99,8 +118,9 @@ export default class Arena {
99
118
  */
100
119
  expose(obj: { [k: string]: any }) {
101
120
  for (const [key, value] of Object.entries(obj)) {
102
- const handle = this._marshal(value);
103
- this.vm.setProp(this.vm.global, key, handle);
121
+ mayConsume(this._marshal(value), (handle) => {
122
+ this.context.setProp(this.context.global, key, handle);
123
+ });
104
124
  }
105
125
  }
106
126
 
@@ -112,8 +132,9 @@ export default class Arena {
112
132
  sync<T>(target: T): T {
113
133
  const wrapped = this._wrap(target);
114
134
  if (typeof wrapped === "undefined") return target;
115
- walkObject(wrapped, v => {
116
- this._sync.add(this._unwrap(v));
135
+ walkObject(wrapped, (v) => {
136
+ const u = this._unwrap(v);
137
+ this._sync.add(u);
117
138
  });
118
139
  return wrapped;
119
140
  }
@@ -127,9 +148,9 @@ export default class Arena {
127
148
  if (this._registeredMap.has(target)) return;
128
149
  const handle =
129
150
  typeof handleOrCode === "string"
130
- ? this._unwrapResult(this.vm.evalCode(handleOrCode))
151
+ ? this._unwrapResult(this.context.evalCode(handleOrCode))
131
152
  : handleOrCode;
132
- if (eq(this.vm, handle, this.vm.undefined)) return;
153
+ if (eq(this.context, handle, this.context.undefined)) return;
133
154
  if (typeof handleOrCode === "string") {
134
155
  this._registeredMapDispose.add(target);
135
156
  }
@@ -167,7 +188,8 @@ export default class Arena {
167
188
 
168
189
  startSync(target: any) {
169
190
  if (!isObject(target)) return;
170
- this._sync.add(this._unwrap(target));
191
+ const u = this._unwrap(target);
192
+ this._sync.add(u);
171
193
  }
172
194
 
173
195
  endSync(target: any) {
@@ -178,52 +200,85 @@ export default class Arena {
178
200
  if ("value" in result) {
179
201
  return result.value;
180
202
  }
181
- throw result.error.consume(err => this._unmarshal(err));
203
+ throw this._unwrapIfNotSynced(result.error.consume(this._unmarshal));
182
204
  }
183
205
 
184
206
  _unwrapResultAndUnmarshal(
185
207
  result: VmCallResult<QuickJSHandle> | undefined
186
208
  ): any {
187
209
  if (!result) return;
188
- return this._unwrapResult(result).consume(h => this._unmarshal(h));
210
+ return this._unwrapIfNotSynced(
211
+ this._unwrapResult(result).consume(this._unmarshal)
212
+ );
189
213
  }
190
214
 
191
- _marshal(target: any): QuickJSHandle {
215
+ _isMarshalable = (t: unknown): boolean | "json" => {
216
+ const im = this._options?.isMarshalable;
217
+ return (typeof im === "function" ? im(this._unwrap(t)) : im) ?? "json";
218
+ };
219
+
220
+ _marshalFind = (t: unknown) => {
221
+ const unwrappedT = this._unwrap(t);
222
+ const handle =
223
+ this._registeredMap.get(t) ??
224
+ (unwrappedT !== t ? this._registeredMap.get(unwrappedT) : undefined) ??
225
+ this._map.get(t) ??
226
+ (unwrappedT !== t ? this._map.get(unwrappedT) : undefined);
227
+ return handle;
228
+ };
229
+
230
+ _marshalPre = (
231
+ t: unknown,
232
+ h: QuickJSHandle | QuickJSDeferredPromise,
233
+ mode: true | "json" | undefined
234
+ ): Wrapped<QuickJSHandle> | undefined => {
235
+ if (mode === "json") return;
236
+ return this._register(t, handleFrom(h), this._map)?.[1];
237
+ };
238
+
239
+ _marshalPreApply = (
240
+ target: Function,
241
+ that: unknown,
242
+ args: unknown[]
243
+ ): void => {
244
+ const unwrapped = isObject(that) ? this._unwrap(that) : undefined;
245
+ // override sync mode of this object while calling the function
246
+ if (unwrapped) this._temporalSync.add(unwrapped);
247
+ try {
248
+ return target.apply(that, args);
249
+ } finally {
250
+ // restore sync mode
251
+ if (unwrapped) this._temporalSync.delete(unwrapped);
252
+ }
253
+ };
254
+
255
+ _marshal = (target: any): [QuickJSHandle, boolean] => {
192
256
  const registered = this._registeredMap.get(target);
193
257
  if (registered) {
194
- return registered;
258
+ return [registered, false];
195
259
  }
196
260
 
197
- const map = new VMMap(this.vm);
198
- map.merge(this._map);
199
-
200
261
  const handle = marshal(this._wrap(target) ?? target, {
201
- vm: this.vm,
202
- unmarshal: h => this._unmarshal(h),
203
- isMarshalable: t =>
204
- this._options?.isMarshalable?.(this._unwrap(t)) ?? true,
205
- find: t => this._registeredMap.get(t) ?? map.get(t),
206
- pre: (t, h) => this._register(t, h, map)?.[1],
207
- preApply: (target, that, args) => {
208
- const unwrapped = isObject(that) ? this._unwrap(that) : undefined;
209
- // override sync mode of this object while calling the function
210
- if (unwrapped) this._temporalSync.add(unwrapped);
211
- try {
212
- return target.apply(that, args);
213
- } finally {
214
- // restore sync mode
215
- if (unwrapped) this._temporalSync.delete(unwrapped);
216
- }
217
- },
262
+ ctx: this.context,
263
+ unmarshal: this._unmarshal,
264
+ isMarshalable: this._isMarshalable,
265
+ find: this._marshalFind,
266
+ pre: this._marshalPre,
267
+ preApply: this._marshalPreApply,
218
268
  });
219
269
 
220
- this._map.merge(map);
221
- map.clear();
222
- map.dispose();
223
- return handle;
224
- }
270
+ return [handle, !this._map.hasHandle(handle)];
271
+ };
272
+
273
+ _preUnmarshal = (t: any, h: QuickJSHandle): Wrapped<any> => {
274
+ return this._register(t, h, undefined, true)?.[0];
275
+ };
276
+
277
+ _unmarshalFind = (h: QuickJSHandle): unknown => {
278
+ return this._registeredMap.getByHandle(h) ?? this._map.getByHandle(h);
279
+ };
225
280
 
226
- _unmarshal(handle: QuickJSHandle): any {
281
+ _unmarshal = (handle: QuickJSHandle): any => {
227
282
  const registered = this._registeredMap.getByHandle(handle);
228
283
  if (typeof registered !== "undefined") {
229
284
  return registered;
@@ -231,13 +286,12 @@ export default class Arena {
231
286
 
232
287
  const [wrappedHandle] = this._wrapHandle(handle);
233
288
  return unmarshal(wrappedHandle ?? handle, {
234
- vm: this.vm,
235
- marshal: (v: any) => this._marshal(v),
236
- find: h => this._registeredMap.getByHandle(h) ?? this._map.getByHandle(h),
237
- pre: (t: any, h: QuickJSHandle) =>
238
- this._register(t, h, undefined, true)?.[0],
289
+ ctx: this.context,
290
+ marshal: this._marshal,
291
+ find: this._unmarshalFind,
292
+ pre: this._preUnmarshal,
239
293
  });
240
- }
294
+ };
241
295
 
242
296
  _register(
243
297
  t: any,
@@ -249,9 +303,11 @@ export default class Arena {
249
303
  return;
250
304
  }
251
305
 
252
- const wrappedT = this._wrap(t);
306
+ let wrappedT = this._wrap(t);
253
307
  const [wrappedH] = this._wrapHandle(h);
254
- if (!wrappedT || !wrappedH) return; // t or h is not an object
308
+ const isPromise = t instanceof Promise;
309
+ if (!wrappedH || (!wrappedT && !isPromise)) return; // t or h is not an object
310
+ if (isPromise) wrappedT = t;
255
311
 
256
312
  const unwrappedT = this._unwrap(t);
257
313
  const [unwrappedH, unwrapped] = this._unwrapHandle(h);
@@ -268,21 +324,21 @@ export default class Arena {
268
324
  return [wrappedT, wrappedH];
269
325
  }
270
326
 
271
- _syncMode(obj: any) {
327
+ _syncMode = (obj: any): "both" | undefined => {
272
328
  const obj2 = this._unwrap(obj);
273
329
  return this._sync.has(obj2) || this._temporalSync.has(obj2)
274
330
  ? "both"
275
331
  : undefined;
276
- }
332
+ };
277
333
 
278
334
  _wrap<T>(target: T): Wrapped<T> | undefined {
279
335
  return wrap(
280
- this.vm,
336
+ this.context,
281
337
  target,
282
338
  this._symbol,
283
339
  this._symbolHandle,
284
- t => this._marshal(t),
285
- t => this._syncMode(t)
340
+ this._marshal,
341
+ this._syncMode
286
342
  );
287
343
  }
288
344
 
@@ -290,20 +346,27 @@ export default class Arena {
290
346
  return unwrap(target, this._symbol);
291
347
  }
292
348
 
349
+ _unwrapIfNotSynced = <T>(target: T): T => {
350
+ const unwrapped = this._unwrap(target);
351
+ return unwrapped instanceof Promise || !this._sync.has(unwrapped)
352
+ ? unwrapped
353
+ : target;
354
+ };
355
+
293
356
  _wrapHandle(
294
357
  handle: QuickJSHandle
295
358
  ): [Wrapped<QuickJSHandle> | undefined, boolean] {
296
359
  return wrapHandle(
297
- this.vm,
360
+ this.context,
298
361
  handle,
299
362
  this._symbol,
300
363
  this._symbolHandle,
301
- h => this._unmarshal(h),
302
- t => this._syncMode(t)
364
+ this._unmarshal,
365
+ this._syncMode
303
366
  );
304
367
  }
305
368
 
306
369
  _unwrapHandle(target: QuickJSHandle): [QuickJSHandle, boolean] {
307
- return unwrapHandle(this.vm, target, this._symbolHandle);
370
+ return unwrapHandle(this.context, target, this._symbolHandle);
308
371
  }
309
372
  }
@@ -1,32 +1,34 @@
1
1
  import { getQuickJS, QuickJSHandle } from "quickjs-emscripten";
2
- import { send, eq, call } from "../vmutil";
2
+
3
+ import { json, eq, call } from "../vmutil";
3
4
  import marshalFunction from "./function";
5
+ import { expect, test, vi } from "vitest";
4
6
 
5
7
  test("normal func", async () => {
6
- const vm = (await getQuickJS()).createVm();
8
+ const ctx = (await getQuickJS()).newContext();
7
9
 
8
- const marshal = jest.fn(v => send(vm, v));
9
- const unmarshal = jest.fn(v =>
10
- eq(vm, v, vm.global) ? undefined : vm.dump(v)
10
+ const marshal = vi.fn((v) => json(ctx, v));
11
+ const unmarshal = vi.fn((v) =>
12
+ eq(ctx, v, ctx.global) ? undefined : ctx.dump(v)
11
13
  );
12
- const preMarshal = jest.fn((_, a) => a);
13
- const innerfn = jest.fn((..._args: any[]) => "hoge");
14
+ const preMarshal = vi.fn((_, a) => a);
15
+ const innerfn = vi.fn((..._args: any[]) => "hoge");
14
16
  const fn = (...args: any[]) => innerfn(...args);
15
17
 
16
- const handle = marshalFunction(vm, fn, marshal, unmarshal, preMarshal);
18
+ const handle = marshalFunction(ctx, fn, marshal, unmarshal, preMarshal);
17
19
  if (!handle) throw new Error("handle is undefined");
18
20
 
19
21
  expect(marshal.mock.calls).toEqual([["length"], [0], ["name"], ["fn"]]); // fn.length, fn.name
20
22
  expect(preMarshal.mock.calls).toEqual([[fn, handle]]); // fn.length, fn.name
21
- expect(vm.typeof(handle)).toBe("function");
22
- expect(vm.dump(vm.getProp(handle, "length"))).toBe(0);
23
- expect(vm.dump(vm.getProp(handle, "name"))).toBe("fn");
23
+ expect(ctx.typeof(handle)).toBe("function");
24
+ expect(ctx.dump(ctx.getProp(handle, "length"))).toBe(0);
25
+ expect(ctx.dump(ctx.getProp(handle, "name"))).toBe("fn");
24
26
 
25
- const result = vm.unwrapResult(
26
- vm.callFunction(handle, vm.undefined, vm.newNumber(1), vm.true)
27
+ const result = ctx.unwrapResult(
28
+ ctx.callFunction(handle, ctx.undefined, ctx.newNumber(1), ctx.true)
27
29
  );
28
30
 
29
- expect(vm.dump(result)).toBe("hoge");
31
+ expect(ctx.dump(result)).toBe("hoge");
30
32
  expect(innerfn).toBeCalledWith(1, true);
31
33
  expect(marshal).toHaveBeenLastCalledWith("hoge");
32
34
  expect(unmarshal).toBeCalledTimes(3);
@@ -35,48 +37,48 @@ test("normal func", async () => {
35
37
  expect(unmarshal.mock.results[2].value).toBe(true);
36
38
 
37
39
  handle.dispose();
38
- vm.dispose();
40
+ ctx.dispose();
39
41
  });
40
42
 
41
43
  test("func which has properties", async () => {
42
- const vm = (await getQuickJS()).createVm();
43
- const marshal = jest.fn(v => send(vm, v));
44
+ const ctx = (await getQuickJS()).newContext();
45
+ const marshal = vi.fn((v) => json(ctx, v));
44
46
 
45
47
  const fn = () => {};
46
48
  fn.hoge = "foo";
47
49
 
48
50
  const handle = marshalFunction(
49
- vm,
51
+ ctx,
50
52
  fn,
51
53
  marshal,
52
- v => vm.dump(v),
54
+ (v) => ctx.dump(v),
53
55
  (_, a) => a
54
56
  );
55
57
  if (!handle) throw new Error("handle is undefined");
56
58
 
57
- expect(vm.typeof(handle)).toBe("function");
58
- expect(vm.dump(vm.getProp(handle, "hoge"))).toBe("foo");
59
+ expect(ctx.typeof(handle)).toBe("function");
60
+ expect(ctx.dump(ctx.getProp(handle, "hoge"))).toBe("foo");
59
61
  expect(marshal).toBeCalledWith("foo");
60
62
 
61
63
  handle.dispose();
62
- vm.dispose();
64
+ ctx.dispose();
63
65
  });
64
66
 
65
67
  test("class", async () => {
66
- const vm = (await getQuickJS()).createVm();
68
+ const ctx = (await getQuickJS()).newContext();
67
69
 
68
70
  const disposables: QuickJSHandle[] = [];
69
71
  const marshal = (v: any) => {
70
- if (typeof v === "string") return vm.newString(v);
71
- if (typeof v === "number") return vm.newNumber(v);
72
+ if (typeof v === "string") return ctx.newString(v);
73
+ if (typeof v === "number") return ctx.newNumber(v);
72
74
  if (typeof v === "object") {
73
- const obj = vm.newObject();
75
+ const obj = ctx.newObject();
74
76
  disposables.push(obj);
75
77
  return obj;
76
78
  }
77
- return vm.null;
79
+ return ctx.null;
78
80
  };
79
- const unmarshal = (v: QuickJSHandle) => vm.dump(v);
81
+ const unmarshal = (v: QuickJSHandle) => ctx.dump(v);
80
82
 
81
83
  class A {
82
84
  a: number;
@@ -86,47 +88,49 @@ test("class", async () => {
86
88
  }
87
89
  }
88
90
 
89
- const handle = marshalFunction(vm, A, marshal, unmarshal, (_, a) => a);
91
+ const handle = marshalFunction(ctx, A, marshal, unmarshal, (_, a) => a);
90
92
  if (!handle) throw new Error("handle is undefined");
91
93
 
92
- const newA = vm.unwrapResult(vm.evalCode(`A => new A(100)`));
93
- const instance = vm.unwrapResult(vm.callFunction(newA, vm.undefined, handle));
94
+ const newA = ctx.unwrapResult(ctx.evalCode(`A => new A(100)`));
95
+ const instance = ctx.unwrapResult(
96
+ ctx.callFunction(newA, ctx.undefined, handle)
97
+ );
94
98
 
95
- expect(vm.dump(vm.getProp(handle, "name"))).toBe("A");
96
- expect(vm.dump(vm.getProp(handle, "length"))).toBe(1);
99
+ expect(ctx.dump(ctx.getProp(handle, "name"))).toBe("A");
100
+ expect(ctx.dump(ctx.getProp(handle, "length"))).toBe(1);
97
101
  expect(
98
- vm.dump(
99
- call(vm, "(cls, i) => i instanceof cls", undefined, handle, instance)
102
+ ctx.dump(
103
+ call(ctx, "(cls, i) => i instanceof cls", undefined, handle, instance)
100
104
  )
101
105
  ).toBe(true);
102
- expect(vm.dump(vm.getProp(instance, "a"))).toBe(100);
106
+ expect(ctx.dump(ctx.getProp(instance, "a"))).toBe(100);
103
107
 
104
- disposables.forEach(d => d.dispose());
108
+ disposables.forEach((d) => d.dispose());
105
109
  instance.dispose();
106
110
  newA.dispose();
107
111
  handle.dispose();
108
- vm.dispose();
112
+ ctx.dispose();
109
113
  });
110
114
 
111
115
  test("preApply", async () => {
112
- const vm = (await getQuickJS()).createVm();
116
+ const ctx = (await getQuickJS()).newContext();
113
117
 
114
118
  const marshal = (v: any) => {
115
- if (typeof v === "string") return vm.newString(v);
116
- if (typeof v === "number") return vm.newNumber(v);
117
- return vm.null;
119
+ if (typeof v === "string") return ctx.newString(v);
120
+ if (typeof v === "number") return ctx.newNumber(v);
121
+ return ctx.null;
118
122
  };
119
123
  const unmarshal = (v: QuickJSHandle) =>
120
- vm.typeof(v) === "object" ? that : vm.dump(v);
121
- const preApply = jest.fn(
124
+ ctx.typeof(v) === "object" ? that : ctx.dump(v);
125
+ const preApply = vi.fn(
122
126
  (a: Function, b: any, c: any[]) => a.apply(b, c) + "!"
123
127
  );
124
128
  const that = {};
125
- const thatHandle = vm.newObject();
129
+ const thatHandle = ctx.newObject();
126
130
 
127
131
  const fn = () => "foo";
128
132
  const handle = marshalFunction(
129
- vm,
133
+ ctx,
130
134
  fn,
131
135
  marshal,
132
136
  unmarshal,
@@ -137,31 +141,36 @@ test("preApply", async () => {
137
141
 
138
142
  expect(preApply).toBeCalledTimes(0);
139
143
 
140
- const res = vm.unwrapResult(
141
- vm.callFunction(handle, thatHandle, vm.newNumber(100), vm.newString("hoge"))
144
+ const res = ctx.unwrapResult(
145
+ ctx.callFunction(
146
+ handle,
147
+ thatHandle,
148
+ ctx.newNumber(100),
149
+ ctx.newString("hoge")
150
+ )
142
151
  );
143
152
 
144
153
  expect(preApply).toBeCalledTimes(1);
145
154
  expect(preApply).toBeCalledWith(fn, that, [100, "hoge"]);
146
- expect(vm.dump(res)).toBe("foo!");
155
+ expect(ctx.dump(res)).toBe("foo!");
147
156
 
148
157
  thatHandle.dispose();
149
158
  handle.dispose();
150
- vm.dispose();
159
+ ctx.dispose();
151
160
  });
152
161
 
153
162
  test("undefined", async () => {
154
- const vm = (await getQuickJS()).createVm();
155
- const f = jest.fn();
156
-
157
- expect(marshalFunction(vm, undefined, f, f, f)).toBe(undefined);
158
- expect(marshalFunction(vm, null, f, f, f)).toBe(undefined);
159
- expect(marshalFunction(vm, false, f, f, f)).toBe(undefined);
160
- expect(marshalFunction(vm, true, f, f, f)).toBe(undefined);
161
- expect(marshalFunction(vm, 1, f, f, f)).toBe(undefined);
162
- expect(marshalFunction(vm, [1], f, f, f)).toBe(undefined);
163
- expect(marshalFunction(vm, { a: 1 }, f, f, f)).toBe(undefined);
163
+ const ctx = (await getQuickJS()).newContext();
164
+ const f = vi.fn();
165
+
166
+ expect(marshalFunction(ctx, undefined, f, f, f)).toBe(undefined);
167
+ expect(marshalFunction(ctx, null, f, f, f)).toBe(undefined);
168
+ expect(marshalFunction(ctx, false, f, f, f)).toBe(undefined);
169
+ expect(marshalFunction(ctx, true, f, f, f)).toBe(undefined);
170
+ expect(marshalFunction(ctx, 1, f, f, f)).toBe(undefined);
171
+ expect(marshalFunction(ctx, [1], f, f, f)).toBe(undefined);
172
+ expect(marshalFunction(ctx, { a: 1 }, f, f, f)).toBe(undefined);
164
173
  expect(f).toBeCalledTimes(0);
165
174
 
166
- vm.dispose();
175
+ ctx.dispose();
167
176
  });
@@ -1,10 +1,11 @@
1
- import { QuickJSVm, QuickJSHandle } from "quickjs-emscripten";
1
+ import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
2
+
2
3
  import { isES2015Class, isObject } from "../util";
3
4
  import { call } from "../vmutil";
4
5
  import marshalProperties from "./properties";
5
6
 
6
7
  export default function marshalFunction(
7
- vm: QuickJSVm,
8
+ ctx: QuickJSContext,
8
9
  target: unknown,
9
10
  marshal: (target: unknown) => QuickJSHandle,
10
11
  unmarshal: (handle: QuickJSHandle) => unknown,
@@ -16,16 +17,16 @@ export default function marshalFunction(
16
17
  ): QuickJSHandle | undefined {
17
18
  if (typeof target !== "function") return;
18
19
 
19
- const raw = vm
20
- .newFunction(target.name, function(...argHandles) {
20
+ const raw = ctx
21
+ .newFunction(target.name, function (...argHandles) {
21
22
  const that = unmarshal(this);
22
- const args = argHandles.map(a => unmarshal(a));
23
+ const args = argHandles.map((a) => unmarshal(a));
23
24
 
24
25
  if (isES2015Class(target) && isObject(that)) {
25
26
  // Class constructors cannot be invoked without new expression, and new.target is not changed
26
27
  const result = new target(...args);
27
28
  Object.entries(result).forEach(([key, value]) => {
28
- vm.setProp(this, key, marshal(value));
29
+ ctx.setProp(this, key, marshal(value));
29
30
  });
30
31
  return this;
31
32
  }
@@ -34,10 +35,10 @@ export default function marshalFunction(
34
35
  preApply ? preApply(target, that, args) : target.apply(that, args)
35
36
  );
36
37
  })
37
- .consume(handle2 =>
38
+ .consume((handle2) =>
38
39
  // fucntions created by vm.newFunction are not callable as a class constrcutor
39
40
  call(
40
- vm,
41
+ ctx,
41
42
  `Cls => {
42
43
  const fn = function(...args) { return Cls.apply(this, args); };
43
44
  fn.name = Cls.name;
@@ -50,7 +51,7 @@ export default function marshalFunction(
50
51
  );
51
52
 
52
53
  const handle = preMarshal(target, raw) ?? raw;
53
- marshalProperties(vm, target, raw, marshal);
54
+ marshalProperties(ctx, target, raw, marshal);
54
55
 
55
56
  return handle;
56
57
  }