quickjs-emscripten-sync 1.3.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 (44) hide show
  1. package/README.md +36 -34
  2. package/dist/index.d.ts +17 -15
  3. package/dist/quickjs-emscripten-sync.es.js +146 -135
  4. package/dist/quickjs-emscripten-sync.umd.js +6 -6
  5. package/package.json +3 -3
  6. package/src/index.test.ts +146 -60
  7. package/src/index.ts +40 -26
  8. package/src/marshal/function.test.ts +61 -53
  9. package/src/marshal/function.ts +7 -6
  10. package/src/marshal/index.test.ts +73 -65
  11. package/src/marshal/index.ts +12 -11
  12. package/src/marshal/json.test.ts +30 -30
  13. package/src/marshal/json.ts +4 -3
  14. package/src/marshal/object.test.ts +55 -50
  15. package/src/marshal/object.ts +6 -5
  16. package/src/marshal/primitive.test.ts +21 -17
  17. package/src/marshal/primitive.ts +10 -9
  18. package/src/marshal/promise.test.ts +14 -14
  19. package/src/marshal/promise.ts +3 -3
  20. package/src/marshal/properties.test.ts +17 -15
  21. package/src/marshal/properties.ts +10 -9
  22. package/src/marshal/symbol.test.ts +6 -6
  23. package/src/marshal/symbol.ts +5 -4
  24. package/src/unmarshal/function.test.ts +45 -43
  25. package/src/unmarshal/function.ts +9 -8
  26. package/src/unmarshal/index.test.ts +41 -40
  27. package/src/unmarshal/index.ts +9 -8
  28. package/src/unmarshal/object.test.ts +33 -31
  29. package/src/unmarshal/object.ts +12 -11
  30. package/src/unmarshal/primitive.test.ts +18 -15
  31. package/src/unmarshal/primitive.ts +9 -9
  32. package/src/unmarshal/promise.test.ts +11 -11
  33. package/src/unmarshal/promise.ts +9 -11
  34. package/src/unmarshal/properties.test.ts +6 -6
  35. package/src/unmarshal/properties.ts +47 -44
  36. package/src/unmarshal/symbol.test.ts +6 -6
  37. package/src/unmarshal/symbol.ts +4 -4
  38. package/src/util.test.ts +1 -0
  39. package/src/vmmap.test.ts +72 -88
  40. package/src/vmmap.ts +16 -16
  41. package/src/vmutil.test.ts +71 -73
  42. package/src/vmutil.ts +22 -18
  43. package/src/wrapper.test.ts +111 -88
  44. package/src/wrapper.ts +31 -27
package/src/index.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  import type {
2
2
  QuickJSDeferredPromise,
3
3
  QuickJSHandle,
4
- QuickJSVm,
5
- } from "quickjs-emscripten";
6
- import type {
4
+ QuickJSContext,
7
5
  SuccessOrFail,
8
6
  VmCallResult,
9
- } from "quickjs-emscripten/dist/vm-interface";
7
+ } from "quickjs-emscripten";
10
8
 
11
9
  import VMMap from "./vmmap";
12
10
  import marshal from "./marshal";
@@ -48,13 +46,15 @@ export type Options = {
48
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.
49
47
  */
50
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;
51
51
  };
52
52
 
53
53
  /**
54
54
  * The Arena class manages all generated handles at once by quickjs-emscripten and automatically converts objects between the host and the QuickJS VM.
55
55
  */
56
56
  export class Arena {
57
- vm: QuickJSVm;
57
+ context: QuickJSContext;
58
58
  _map: VMMap;
59
59
  _registeredMap: VMMap;
60
60
  _registeredMapDispose: Set<any> = new Set();
@@ -64,13 +64,21 @@ export class Arena {
64
64
  _symbolHandle: QuickJSHandle;
65
65
  _options?: Options;
66
66
 
67
- /** Constructs a new Arena instance. It requires a quickjs-emscripten VM initialized with `quickjs.createVM()`. */
68
- constructor(vm: QuickJSVm, options?: Options) {
69
- 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;
70
78
  this._options = options;
71
- this._symbolHandle = vm.unwrapResult(vm.evalCode(`Symbol()`));
72
- this._map = new VMMap(vm);
73
- 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);
74
82
  this.registerAll(options?.registeredObjects ?? defaultRegisteredObjects);
75
83
  }
76
84
 
@@ -87,7 +95,7 @@ export class Arena {
87
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.
88
96
  */
89
97
  evalCode<T = any>(code: string): T {
90
- const handle = this.vm.evalCode(code);
98
+ const handle = this.context.evalCode(code);
91
99
  return this._unwrapResultAndUnmarshal(handle);
92
100
  }
93
101
 
@@ -95,7 +103,7 @@ export class Arena {
95
103
  * Almost same as `vm.executePendingJobs()`, but it converts and re-throws error objects when an error is thrown during evaluation.
96
104
  */
97
105
  executePendingJobs(maxJobsToExecute?: number): number {
98
- const result = this.vm.executePendingJobs(maxJobsToExecute);
106
+ const result = this.context.runtime.executePendingJobs(maxJobsToExecute);
99
107
  if ("value" in result) {
100
108
  return result.value;
101
109
  }
@@ -111,7 +119,7 @@ export class Arena {
111
119
  expose(obj: { [k: string]: any }) {
112
120
  for (const [key, value] of Object.entries(obj)) {
113
121
  mayConsume(this._marshal(value), (handle) => {
114
- this.vm.setProp(this.vm.global, key, handle);
122
+ this.context.setProp(this.context.global, key, handle);
115
123
  });
116
124
  }
117
125
  }
@@ -125,7 +133,8 @@ export class Arena {
125
133
  const wrapped = this._wrap(target);
126
134
  if (typeof wrapped === "undefined") return target;
127
135
  walkObject(wrapped, (v) => {
128
- this._sync.add(this._unwrap(v));
136
+ const u = this._unwrap(v);
137
+ this._sync.add(u);
129
138
  });
130
139
  return wrapped;
131
140
  }
@@ -139,9 +148,9 @@ export class Arena {
139
148
  if (this._registeredMap.has(target)) return;
140
149
  const handle =
141
150
  typeof handleOrCode === "string"
142
- ? this._unwrapResult(this.vm.evalCode(handleOrCode))
151
+ ? this._unwrapResult(this.context.evalCode(handleOrCode))
143
152
  : handleOrCode;
144
- if (eq(this.vm, handle, this.vm.undefined)) return;
153
+ if (eq(this.context, handle, this.context.undefined)) return;
145
154
  if (typeof handleOrCode === "string") {
146
155
  this._registeredMapDispose.add(target);
147
156
  }
@@ -179,7 +188,8 @@ export class Arena {
179
188
 
180
189
  startSync(target: any) {
181
190
  if (!isObject(target)) return;
182
- this._sync.add(this._unwrap(target));
191
+ const u = this._unwrap(target);
192
+ this._sync.add(u);
183
193
  }
184
194
 
185
195
  endSync(target: any) {
@@ -249,7 +259,7 @@ export class Arena {
249
259
  }
250
260
 
251
261
  const handle = marshal(this._wrap(target) ?? target, {
252
- vm: this.vm,
262
+ ctx: this.context,
253
263
  unmarshal: this._unmarshal,
254
264
  isMarshalable: this._isMarshalable,
255
265
  find: this._marshalFind,
@@ -276,7 +286,7 @@ export class Arena {
276
286
 
277
287
  const [wrappedHandle] = this._wrapHandle(handle);
278
288
  return unmarshal(wrappedHandle ?? handle, {
279
- vm: this.vm,
289
+ ctx: this.context,
280
290
  marshal: this._marshal,
281
291
  find: this._unmarshalFind,
282
292
  pre: this._preUnmarshal,
@@ -293,9 +303,11 @@ export class Arena {
293
303
  return;
294
304
  }
295
305
 
296
- const wrappedT = this._wrap(t);
306
+ let wrappedT = this._wrap(t);
297
307
  const [wrappedH] = this._wrapHandle(h);
298
- 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;
299
311
 
300
312
  const unwrappedT = this._unwrap(t);
301
313
  const [unwrappedH, unwrapped] = this._unwrapHandle(h);
@@ -321,7 +333,7 @@ export class Arena {
321
333
 
322
334
  _wrap<T>(target: T): Wrapped<T> | undefined {
323
335
  return wrap(
324
- this.vm,
336
+ this.context,
325
337
  target,
326
338
  this._symbol,
327
339
  this._symbolHandle,
@@ -336,14 +348,16 @@ export class Arena {
336
348
 
337
349
  _unwrapIfNotSynced = <T>(target: T): T => {
338
350
  const unwrapped = this._unwrap(target);
339
- return this._sync.has(unwrapped) ? target : unwrapped;
351
+ return unwrapped instanceof Promise || !this._sync.has(unwrapped)
352
+ ? unwrapped
353
+ : target;
340
354
  };
341
355
 
342
356
  _wrapHandle(
343
357
  handle: QuickJSHandle
344
358
  ): [Wrapped<QuickJSHandle> | undefined, boolean] {
345
359
  return wrapHandle(
346
- this.vm,
360
+ this.context,
347
361
  handle,
348
362
  this._symbol,
349
363
  this._symbolHandle,
@@ -353,6 +367,6 @@ export class Arena {
353
367
  }
354
368
 
355
369
  _unwrapHandle(target: QuickJSHandle): [QuickJSHandle, boolean] {
356
- return unwrapHandle(this.vm, target, this._symbolHandle);
370
+ return unwrapHandle(this.context, target, this._symbolHandle);
357
371
  }
358
372
  }
@@ -1,33 +1,34 @@
1
1
  import { getQuickJS, QuickJSHandle } from "quickjs-emscripten";
2
+
2
3
  import { json, eq, call } from "../vmutil";
3
4
  import marshalFunction from "./function";
4
5
  import { expect, test, vi } from "vitest";
5
6
 
6
7
  test("normal func", async () => {
7
- const vm = (await getQuickJS()).createVm();
8
+ const ctx = (await getQuickJS()).newContext();
8
9
 
9
- const marshal = vi.fn((v) => json(vm, v));
10
+ const marshal = vi.fn((v) => json(ctx, v));
10
11
  const unmarshal = vi.fn((v) =>
11
- eq(vm, v, vm.global) ? undefined : vm.dump(v)
12
+ eq(ctx, v, ctx.global) ? undefined : ctx.dump(v)
12
13
  );
13
14
  const preMarshal = vi.fn((_, a) => a);
14
15
  const innerfn = vi.fn((..._args: any[]) => "hoge");
15
16
  const fn = (...args: any[]) => innerfn(...args);
16
17
 
17
- const handle = marshalFunction(vm, fn, marshal, unmarshal, preMarshal);
18
+ const handle = marshalFunction(ctx, fn, marshal, unmarshal, preMarshal);
18
19
  if (!handle) throw new Error("handle is undefined");
19
20
 
20
21
  expect(marshal.mock.calls).toEqual([["length"], [0], ["name"], ["fn"]]); // fn.length, fn.name
21
22
  expect(preMarshal.mock.calls).toEqual([[fn, handle]]); // fn.length, fn.name
22
- expect(vm.typeof(handle)).toBe("function");
23
- expect(vm.dump(vm.getProp(handle, "length"))).toBe(0);
24
- 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");
25
26
 
26
- const result = vm.unwrapResult(
27
- 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)
28
29
  );
29
30
 
30
- expect(vm.dump(result)).toBe("hoge");
31
+ expect(ctx.dump(result)).toBe("hoge");
31
32
  expect(innerfn).toBeCalledWith(1, true);
32
33
  expect(marshal).toHaveBeenLastCalledWith("hoge");
33
34
  expect(unmarshal).toBeCalledTimes(3);
@@ -36,48 +37,48 @@ test("normal func", async () => {
36
37
  expect(unmarshal.mock.results[2].value).toBe(true);
37
38
 
38
39
  handle.dispose();
39
- vm.dispose();
40
+ ctx.dispose();
40
41
  });
41
42
 
42
43
  test("func which has properties", async () => {
43
- const vm = (await getQuickJS()).createVm();
44
- const marshal = vi.fn((v) => json(vm, v));
44
+ const ctx = (await getQuickJS()).newContext();
45
+ const marshal = vi.fn((v) => json(ctx, v));
45
46
 
46
47
  const fn = () => {};
47
48
  fn.hoge = "foo";
48
49
 
49
50
  const handle = marshalFunction(
50
- vm,
51
+ ctx,
51
52
  fn,
52
53
  marshal,
53
- (v) => vm.dump(v),
54
+ (v) => ctx.dump(v),
54
55
  (_, a) => a
55
56
  );
56
57
  if (!handle) throw new Error("handle is undefined");
57
58
 
58
- expect(vm.typeof(handle)).toBe("function");
59
- 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");
60
61
  expect(marshal).toBeCalledWith("foo");
61
62
 
62
63
  handle.dispose();
63
- vm.dispose();
64
+ ctx.dispose();
64
65
  });
65
66
 
66
67
  test("class", async () => {
67
- const vm = (await getQuickJS()).createVm();
68
+ const ctx = (await getQuickJS()).newContext();
68
69
 
69
70
  const disposables: QuickJSHandle[] = [];
70
71
  const marshal = (v: any) => {
71
- if (typeof v === "string") return vm.newString(v);
72
- 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);
73
74
  if (typeof v === "object") {
74
- const obj = vm.newObject();
75
+ const obj = ctx.newObject();
75
76
  disposables.push(obj);
76
77
  return obj;
77
78
  }
78
- return vm.null;
79
+ return ctx.null;
79
80
  };
80
- const unmarshal = (v: QuickJSHandle) => vm.dump(v);
81
+ const unmarshal = (v: QuickJSHandle) => ctx.dump(v);
81
82
 
82
83
  class A {
83
84
  a: number;
@@ -87,47 +88,49 @@ test("class", async () => {
87
88
  }
88
89
  }
89
90
 
90
- const handle = marshalFunction(vm, A, marshal, unmarshal, (_, a) => a);
91
+ const handle = marshalFunction(ctx, A, marshal, unmarshal, (_, a) => a);
91
92
  if (!handle) throw new Error("handle is undefined");
92
93
 
93
- const newA = vm.unwrapResult(vm.evalCode(`A => new A(100)`));
94
- 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
+ );
95
98
 
96
- expect(vm.dump(vm.getProp(handle, "name"))).toBe("A");
97
- 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);
98
101
  expect(
99
- vm.dump(
100
- call(vm, "(cls, i) => i instanceof cls", undefined, handle, instance)
102
+ ctx.dump(
103
+ call(ctx, "(cls, i) => i instanceof cls", undefined, handle, instance)
101
104
  )
102
105
  ).toBe(true);
103
- expect(vm.dump(vm.getProp(instance, "a"))).toBe(100);
106
+ expect(ctx.dump(ctx.getProp(instance, "a"))).toBe(100);
104
107
 
105
108
  disposables.forEach((d) => d.dispose());
106
109
  instance.dispose();
107
110
  newA.dispose();
108
111
  handle.dispose();
109
- vm.dispose();
112
+ ctx.dispose();
110
113
  });
111
114
 
112
115
  test("preApply", async () => {
113
- const vm = (await getQuickJS()).createVm();
116
+ const ctx = (await getQuickJS()).newContext();
114
117
 
115
118
  const marshal = (v: any) => {
116
- if (typeof v === "string") return vm.newString(v);
117
- if (typeof v === "number") return vm.newNumber(v);
118
- 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;
119
122
  };
120
123
  const unmarshal = (v: QuickJSHandle) =>
121
- vm.typeof(v) === "object" ? that : vm.dump(v);
124
+ ctx.typeof(v) === "object" ? that : ctx.dump(v);
122
125
  const preApply = vi.fn(
123
126
  (a: Function, b: any, c: any[]) => a.apply(b, c) + "!"
124
127
  );
125
128
  const that = {};
126
- const thatHandle = vm.newObject();
129
+ const thatHandle = ctx.newObject();
127
130
 
128
131
  const fn = () => "foo";
129
132
  const handle = marshalFunction(
130
- vm,
133
+ ctx,
131
134
  fn,
132
135
  marshal,
133
136
  unmarshal,
@@ -138,31 +141,36 @@ test("preApply", async () => {
138
141
 
139
142
  expect(preApply).toBeCalledTimes(0);
140
143
 
141
- const res = vm.unwrapResult(
142
- 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
+ )
143
151
  );
144
152
 
145
153
  expect(preApply).toBeCalledTimes(1);
146
154
  expect(preApply).toBeCalledWith(fn, that, [100, "hoge"]);
147
- expect(vm.dump(res)).toBe("foo!");
155
+ expect(ctx.dump(res)).toBe("foo!");
148
156
 
149
157
  thatHandle.dispose();
150
158
  handle.dispose();
151
- vm.dispose();
159
+ ctx.dispose();
152
160
  });
153
161
 
154
162
  test("undefined", async () => {
155
- const vm = (await getQuickJS()).createVm();
163
+ const ctx = (await getQuickJS()).newContext();
156
164
  const f = vi.fn();
157
165
 
158
- expect(marshalFunction(vm, undefined, f, f, f)).toBe(undefined);
159
- expect(marshalFunction(vm, null, f, f, f)).toBe(undefined);
160
- expect(marshalFunction(vm, false, f, f, f)).toBe(undefined);
161
- expect(marshalFunction(vm, true, f, f, f)).toBe(undefined);
162
- expect(marshalFunction(vm, 1, f, f, f)).toBe(undefined);
163
- expect(marshalFunction(vm, [1], f, f, f)).toBe(undefined);
164
- expect(marshalFunction(vm, { a: 1 }, f, f, f)).toBe(undefined);
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);
165
173
  expect(f).toBeCalledTimes(0);
166
174
 
167
- vm.dispose();
175
+ ctx.dispose();
168
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,7 +17,7 @@ export default function marshalFunction(
16
17
  ): QuickJSHandle | undefined {
17
18
  if (typeof target !== "function") return;
18
19
 
19
- const raw = vm
20
+ const raw = ctx
20
21
  .newFunction(target.name, function (...argHandles) {
21
22
  const that = unmarshal(this);
22
23
  const args = argHandles.map((a) => unmarshal(a));
@@ -25,7 +26,7 @@ export default function marshalFunction(
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
  }
@@ -37,7 +38,7 @@ export default function marshalFunction(
37
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
  }