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.test.ts CHANGED
@@ -1,40 +1,129 @@
1
1
  import { getQuickJS } from "quickjs-emscripten";
2
- import Arena from ".";
2
+ import { describe, expect, test, vi } from "vitest";
3
+
4
+ import { Arena } from ".";
5
+ import { isWrapped } from "./wrapper";
6
+
7
+ describe("readme", () => {
8
+ test("first", async () => {
9
+ class Cls {
10
+ field = 0;
11
+
12
+ method() {
13
+ return ++this.field;
14
+ }
15
+ }
16
+
17
+ const ctx = (await getQuickJS()).newContext();
18
+ const arena = new Arena(ctx, { isMarshalable: true });
19
+
20
+ // We can pass objects to the VM and run code safely
21
+ const exposed = {
22
+ Cls,
23
+ cls: new Cls(),
24
+ syncedCls: arena.sync(new Cls()),
25
+ };
26
+ arena.expose(exposed);
27
+
28
+ expect(arena.evalCode(`cls instanceof Cls`)).toBe(true);
29
+ expect(arena.evalCode(`cls.field`)).toBe(0);
30
+ expect(arena.evalCode(`cls.method()`)).toBe(1);
31
+ expect(arena.evalCode(`cls.field`)).toBe(1);
32
+
33
+ expect(arena.evalCode(`syncedCls.field`)).toBe(0);
34
+ expect(exposed.syncedCls.method()).toBe(1);
35
+ expect(arena.evalCode(`syncedCls.field`)).toBe(1);
36
+
37
+ arena.dispose();
38
+ ctx.dispose();
39
+ });
40
+
41
+ test("usage", async () => {
42
+ const quickjs = await getQuickJS();
43
+ const ctx = quickjs.newContext();
44
+
45
+ // init Arena
46
+ // ⚠️ Marshaling is opt-in for security reasons.
47
+ // ⚠️ Be careful when activating marshalling.
48
+ const arena = new Arena(ctx, { isMarshalable: true });
49
+
50
+ // expose objects as global objects in QuickJS VM
51
+ const log = vi.fn();
52
+ arena.expose({
53
+ console: { log },
54
+ });
55
+ arena.evalCode(`console.log("hello, world");`); // run console.log
56
+ expect(log).toBeCalledWith("hello, world");
57
+ arena.evalCode(`1 + 1`); // 2
58
+
59
+ // expose objects but also enable sync
60
+ const data = arena.sync({ hoge: "foo" });
61
+ arena.expose({ data });
62
+
63
+ arena.evalCode(`data.hoge = "bar"`);
64
+ // eval code and operations to exposed objects are automatically synced
65
+ expect(data.hoge).toBe("bar");
66
+ data.hoge = "changed!";
67
+ expect(arena.evalCode(`data.hoge`)).toBe("changed!");
68
+
69
+ // Don't forget calling arena.dispose() before disposing QuickJS VM!
70
+ arena.dispose();
71
+ ctx.dispose();
72
+ });
73
+ });
3
74
 
4
75
  describe("evalCode", () => {
5
76
  test("simple object and function", async () => {
6
- const vm = (await getQuickJS()).createVm();
7
- const arena = new Arena(vm);
77
+ const ctx = (await getQuickJS()).newContext();
78
+ const arena = new Arena(ctx, { isMarshalable: true });
8
79
 
9
80
  const result = arena.evalCode(
10
- `({ a: 1, b: a => Math.floor(a), c: () => { throw new Error("hoge") } })`
81
+ `({
82
+ a: 1,
83
+ b: a => Math.floor(a),
84
+ c: () => { throw new Error("hoge") },
85
+ d: (yourFavoriteNumber) => ({
86
+ myFavoriteNumber: 42,
87
+ yourFavoriteNumber,
88
+ }),
89
+ get e() {
90
+ return { a: 1 };
91
+ }
92
+ })`
11
93
  );
12
94
  expect(result).toEqual({
13
95
  a: 1,
14
96
  b: expect.any(Function),
15
97
  c: expect.any(Function),
98
+ d: expect.any(Function),
99
+ e: { a: 1 },
16
100
  });
17
101
  expect(result.b(1.1)).toBe(1);
18
102
  expect(() => result.c()).toThrow("hoge");
103
+ expect(result.d(1)).toStrictEqual({
104
+ myFavoriteNumber: 42,
105
+ yourFavoriteNumber: 1,
106
+ });
107
+ expect(result.e).toStrictEqual({ a: 1 });
19
108
 
20
109
  arena.dispose();
21
- vm.dispose();
110
+ ctx.dispose();
22
111
  });
23
112
 
24
113
  test("Math", async () => {
25
- const vm = (await getQuickJS()).createVm();
26
- const arena = new Arena(vm);
114
+ const ctx = (await getQuickJS()).newContext();
115
+ const arena = new Arena(ctx, { isMarshalable: true });
27
116
 
28
117
  const VMMath = arena.evalCode(`Math`) as Math;
29
118
  expect(VMMath.floor(1.1)).toBe(1);
30
119
 
31
120
  arena.dispose();
32
- vm.dispose();
121
+ ctx.dispose();
33
122
  });
34
123
 
35
124
  test("class", async () => {
36
- const vm = (await getQuickJS()).createVm();
37
- const arena = new Arena(vm);
125
+ const ctx = (await getQuickJS()).newContext();
126
+ const arena = new Arena(ctx, { isMarshalable: true });
38
127
 
39
128
  const instance = arena.evalCode(`{
40
129
  globalThis.Cls = class D {
@@ -45,7 +134,7 @@ describe("evalCode", () => {
45
134
  return ++this.a;
46
135
  }
47
136
  };
48
-
137
+
49
138
  new Cls(100);
50
139
  }`);
51
140
  const Cls = arena.evalCode(`globalThis.Cls`);
@@ -55,12 +144,12 @@ describe("evalCode", () => {
55
144
  expect(instance.a).toBe(102);
56
145
 
57
146
  arena.dispose();
58
- vm.dispose();
147
+ ctx.dispose();
59
148
  });
60
149
 
61
150
  test("obj", async () => {
62
- const vm = (await getQuickJS()).createVm();
63
- const arena = new Arena(vm);
151
+ const ctx = (await getQuickJS()).newContext();
152
+ const arena = new Arena(ctx, { isMarshalable: true });
64
153
 
65
154
  const obj = arena.evalCode(`globalThis.AAA = { a: 1 }`);
66
155
 
@@ -71,47 +160,109 @@ describe("evalCode", () => {
71
160
  expect(arena.evalCode(`AAA.a`)).toBe(2);
72
161
 
73
162
  arena.dispose();
74
- vm.dispose();
163
+ ctx.dispose();
164
+ });
165
+
166
+ test("promise", async () => {
167
+ const ctx = (await getQuickJS()).newContext();
168
+ const arena = new Arena(ctx, { isMarshalable: true });
169
+
170
+ const [promise, resolve] = arena.evalCode<
171
+ [Promise<string>, (d: string) => void]
172
+ >(`
173
+ let resolve;
174
+ const promise = new Promise(r => {
175
+ resolve = r;
176
+ }).then(d => d + "!");
177
+ [promise, resolve]
178
+ `);
179
+ expect(promise).instanceOf(Promise);
180
+ expect(isWrapped(arena._unwrapIfNotSynced(promise), arena._symbol)).toBe(
181
+ false
182
+ );
183
+
184
+ resolve("hoge");
185
+ expect(arena.executePendingJobs()).toBe(2);
186
+ expect(await promise).toBe("hoge!");
187
+
188
+ arena.dispose();
189
+ ctx.dispose();
190
+ });
191
+
192
+ test("promise2", async () => {
193
+ const ctx = (await getQuickJS()).newContext();
194
+ const arena = new Arena(ctx, { isMarshalable: true });
195
+
196
+ const deferred: { resolve?: (s: string) => void } = {};
197
+ const promise = new Promise((resolve) => {
198
+ deferred.resolve = resolve;
199
+ });
200
+ const res = vi.fn();
201
+ arena.evalCode(`(p, r) => { p.then(d => { r(d + "!"); }); }`)(promise, res);
202
+
203
+ deferred.resolve?.("hoge");
204
+ await promise;
205
+ expect(arena.executePendingJobs()).toBe(1);
206
+ expect(res).toBeCalledWith("hoge!");
207
+
208
+ arena.dispose();
209
+ ctx.dispose();
75
210
  });
76
211
  });
77
212
 
78
213
  describe("expose without sync", () => {
79
214
  test("simple object and function", async () => {
80
- const vm = (await getQuickJS()).createVm();
81
- const arena = new Arena(vm);
215
+ const ctx = (await getQuickJS()).newContext();
216
+ const arena = new Arena(ctx, { isMarshalable: true });
82
217
 
83
- arena.expose({
84
- obj: {
85
- a: 1,
86
- b: (a: number) => Math.floor(a),
87
- c: () => {
88
- throw new Error("hoge");
89
- },
218
+ const obj = {
219
+ a: 1,
220
+ b: (a: number) => Math.floor(a),
221
+ c: () => {
222
+ throw new Error("hoge");
223
+ },
224
+ d: (yourFavoriteNumber: number) => ({
225
+ myFavoriteNumber: 42,
226
+ yourFavoriteNumber,
227
+ }),
228
+ get e() {
229
+ return { a: 1 };
90
230
  },
231
+ };
232
+ arena.expose({
233
+ obj,
91
234
  });
92
235
 
236
+ expect(arena.evalCode(`obj`)).toBe(obj);
93
237
  expect(arena.evalCode(`obj.a`)).toBe(1);
94
238
  expect(arena.evalCode(`obj.b(1.1)`)).toBe(1);
95
239
  expect(() => arena.evalCode(`obj.c()`)).toThrow("hoge");
240
+ expect(arena.evalCode(`obj.d(1)`)).toStrictEqual({
241
+ myFavoriteNumber: 42,
242
+ yourFavoriteNumber: 1,
243
+ });
244
+ expect(arena.evalCode(`obj.e`)).toStrictEqual({ a: 1 });
96
245
 
97
246
  arena.dispose();
98
- vm.dispose();
247
+ ctx.dispose();
99
248
  });
100
249
 
101
250
  test("Math", async () => {
102
- const vm = (await getQuickJS()).createVm();
103
- const arena = new Arena(vm);
251
+ const ctx = (await getQuickJS()).newContext();
252
+ const arena = new Arena(ctx, { isMarshalable: true });
104
253
 
105
- arena.expose({ Math });
106
- expect(arena.evalCode(`Math.floor(1.1)`)).toBe(1);
254
+ arena.expose({ Math2: Math });
255
+ expect(arena.evalCode(`Math`)).not.toBe(Math);
256
+ expect(arena.evalCode(`Math2`)).toBe(Math);
257
+ expect(arena.evalCode(`Math2.floor(1.1)`)).toBe(1);
107
258
 
108
259
  arena.dispose();
109
- vm.dispose();
260
+ ctx.dispose();
110
261
  });
111
262
 
112
263
  test("class", async () => {
113
- const vm = (await getQuickJS()).createVm();
114
- const arena = new Arena(vm);
264
+ const ctx = (await getQuickJS()).newContext();
265
+ const arena = new Arena(ctx, { isMarshalable: true });
115
266
 
116
267
  class D {
117
268
  a: number;
@@ -125,19 +276,22 @@ describe("expose without sync", () => {
125
276
  }
126
277
  }
127
278
 
128
- arena.expose({ D, d: new D(100) });
279
+ const d = new D(100);
280
+ arena.expose({ D, d });
281
+ expect(arena.evalCode(`D`)).toBe(D);
282
+ expect(arena.evalCode(`d`)).toBe(d);
129
283
  expect(arena.evalCode(`d instanceof D`)).toBe(true);
130
284
  expect(arena.evalCode(`d.a`)).toBe(101);
131
285
  expect(arena.evalCode(`d.foo()`)).toBe(102);
132
286
  expect(arena.evalCode(`d.a`)).toBe(102);
133
287
 
134
288
  arena.dispose();
135
- vm.dispose();
289
+ ctx.dispose();
136
290
  });
137
291
 
138
292
  test("object and function", async () => {
139
- const vm = (await getQuickJS()).createVm();
140
- const arena = new Arena(vm);
293
+ const ctx = (await getQuickJS()).newContext();
294
+ const arena = new Arena(ctx, { isMarshalable: true });
141
295
 
142
296
  const obj = {
143
297
  a: 1,
@@ -148,7 +302,11 @@ describe("expose without sync", () => {
148
302
  };
149
303
  arena.expose({ obj });
150
304
 
305
+ expect(arena.evalCode(`obj`)).toBe(obj);
306
+ expect(arena.evalCode(`obj.a`)).toBe(1);
307
+ expect(arena.evalCode(`obj.b`)).toBe(obj.b);
151
308
  expect(arena.evalCode(`obj.b(1.1)`)).toBe(1);
309
+ expect(arena.evalCode(`obj.c`)).toBe(obj.c);
152
310
  expect(arena.evalCode(`obj.c()`)).toBe(1);
153
311
  expect(arena.evalCode(`obj.a`)).toBe(2);
154
312
  expect(obj.a).toBe(2);
@@ -165,14 +323,14 @@ describe("expose without sync", () => {
165
323
  expect(arena.evalCode(`obj.a`)).toBe(100);
166
324
 
167
325
  arena.dispose();
168
- vm.dispose();
326
+ ctx.dispose();
169
327
  });
170
328
  });
171
329
 
172
330
  describe("expose with sync", () => {
173
- test("object and function", async () => {
174
- const vm = (await getQuickJS()).createVm();
175
- const arena = new Arena(vm);
331
+ test("sync before expose", async () => {
332
+ const ctx = (await getQuickJS()).newContext();
333
+ const arena = new Arena(ctx, { isMarshalable: true });
176
334
 
177
335
  const obj = {
178
336
  a: 1,
@@ -184,7 +342,61 @@ describe("expose with sync", () => {
184
342
  const obj2 = arena.sync(obj);
185
343
  arena.expose({ obj: obj2 });
186
344
 
345
+ const obj3 = arena.evalCode(`obj`);
346
+ expect(obj3).toBe(obj2);
347
+ expect(arena.evalCode(`obj.c`)).not.toBe(obj.c); // wrapped object
348
+ expect(arena.evalCode(`obj.b`)).not.toBe(obj2.b); // wrapped object
349
+ expect(arena.evalCode(`obj.b`)).not.toBe(obj3.b); // wrapped object
350
+ expect(arena.evalCode(`obj.b(1.1)`)).toBe(1);
351
+ expect(arena.evalCode(`obj.a`)).toBe(1);
352
+ expect(arena.evalCode(`obj.c`)).not.toBe(obj.c); // wrapped object
353
+ expect(arena.evalCode(`obj.c`)).not.toBe(obj2.c); // wrapped object
354
+ expect(arena.evalCode(`obj.c`)).not.toBe(obj3.c); // wrapped object
355
+ expect(arena.evalCode(`obj.c()`)).toBe(1);
356
+ expect(arena.evalCode(`obj.a`)).toBe(2);
357
+ expect(obj.a).toBe(2);
358
+ expect(arena.evalCode(`obj.c()`)).toBe(2);
359
+ expect(arena.evalCode(`obj.a`)).toBe(3);
360
+ expect(obj.a).toBe(3);
361
+
362
+ expect(obj).not.toBe(obj2);
363
+ obj2.a = 10;
364
+ expect(obj.a).toBe(10);
365
+ expect(arena.evalCode(`obj.a`)).toBe(10); // affected
366
+
367
+ arena.evalCode(`obj.a = 100`);
368
+ expect(obj.a).toBe(100); // affected
369
+ expect(arena.evalCode(`obj.a`)).toBe(100);
370
+
371
+ arena.dispose();
372
+ ctx.dispose();
373
+ });
374
+
375
+ test("sync after expose", async () => {
376
+ const ctx = (await getQuickJS()).newContext();
377
+ const arena = new Arena(ctx, { isMarshalable: true });
378
+
379
+ const obj = {
380
+ a: 1,
381
+ b: (a: number) => Math.floor(a),
382
+ c() {
383
+ return this.a++;
384
+ },
385
+ };
386
+ arena.expose({ obj });
387
+ const obj2 = arena.sync(obj);
388
+
389
+ const obj3 = arena.evalCode(`obj`);
390
+ expect(obj3).not.toBe(obj); // wrapped object
391
+ expect(obj3).not.toBe(obj2); // wrapped object
392
+ expect(arena.evalCode(`obj.c`)).not.toBe(obj.c); // wrapped object
393
+ expect(arena.evalCode(`obj.b`)).not.toBe(obj2.b); // wrapped object
394
+ expect(arena.evalCode(`obj.b`)).not.toBe(obj3.b); // wrapped object
187
395
  expect(arena.evalCode(`obj.b(1.1)`)).toBe(1);
396
+ expect(arena.evalCode(`obj.a`)).toBe(1);
397
+ expect(arena.evalCode(`obj.c`)).not.toBe(obj.c); // wrapped object
398
+ expect(arena.evalCode(`obj.c`)).not.toBe(obj2.c); // wrapped object
399
+ expect(arena.evalCode(`obj.c`)).not.toBe(obj3.c); // wrapped object
188
400
  expect(arena.evalCode(`obj.c()`)).toBe(1);
189
401
  expect(arena.evalCode(`obj.a`)).toBe(2);
190
402
  expect(obj.a).toBe(2);
@@ -202,17 +414,18 @@ describe("expose with sync", () => {
202
414
  expect(arena.evalCode(`obj.a`)).toBe(100);
203
415
 
204
416
  arena.dispose();
205
- vm.dispose();
417
+ ctx.dispose();
206
418
  });
207
419
  });
208
420
 
209
421
  test("evalCode -> expose", async () => {
210
- const vm = (await getQuickJS()).createVm();
211
- const arena = new Arena(vm);
422
+ const ctx = (await getQuickJS()).newContext();
423
+ const arena = new Arena(ctx, { isMarshalable: true });
212
424
 
213
425
  const obj = arena.evalCode(`({ a: 1, b: 1 })`);
214
426
  arena.expose({ obj });
215
427
 
428
+ expect(obj).toBe(obj);
216
429
  expect(obj.a).toBe(1);
217
430
  expect(arena.evalCode(`obj.a`)).toBe(1);
218
431
  expect(obj.b).toBe(1);
@@ -233,35 +446,47 @@ test("evalCode -> expose", async () => {
233
446
  expect(arena.evalCode(`obj.b`)).toBe(2);
234
447
 
235
448
  arena.dispose();
236
- vm.dispose();
449
+ ctx.dispose();
237
450
  });
238
451
 
239
452
  test("expose -> evalCode", async () => {
240
- const vm = (await getQuickJS()).createVm();
241
- const arena = new Arena(vm);
453
+ const ctx = (await getQuickJS()).newContext();
454
+ const arena = new Arena(ctx, { isMarshalable: true });
242
455
 
243
456
  const obj = { a: 1 };
244
- const obj2 = arena.sync(obj);
245
- arena.expose({ obj: obj2 });
246
- const obj3 = arena.evalCode(`obj`);
457
+ arena.expose({ obj });
458
+ const obj2 = arena.evalCode(`obj`);
247
459
 
248
- expect(obj3).toBe(obj2);
460
+ expect(obj2).toBe(obj);
249
461
 
250
- obj3.a = 2;
462
+ obj2.a = 2;
251
463
  expect(obj.a).toBe(2);
252
- expect(arena.evalCode(`obj.a`)).toBe(2);
464
+ expect(arena.evalCode(`obj.a`)).toBe(1);
465
+
466
+ arena.evalCode("obj.a = 3");
467
+ expect(obj.a).toBe(2);
468
+ expect(arena.evalCode(`obj.a`)).toBe(3);
253
469
 
254
- arena.evalCode("obj.a = 4");
255
- expect(obj.a).toBe(4);
256
- expect(arena.evalCode(`obj.a`)).toBe(4);
470
+ arena.dispose();
471
+ ctx.dispose();
472
+ });
473
+
474
+ test("evalCode -> expose -> evalCode", async () => {
475
+ const ctx = (await getQuickJS()).newContext();
476
+ const arena = new Arena(ctx, { isMarshalable: true });
477
+
478
+ const obj = [1];
479
+ expect(arena.evalCode("a => a[0] + 10")(obj)).toBe(11);
480
+ arena.expose({ obj });
481
+ expect(arena.evalCode("obj")).toBe(obj);
257
482
 
258
483
  arena.dispose();
259
- vm.dispose();
484
+ ctx.dispose();
260
485
  });
261
486
 
262
487
  test("register and unregister", async () => {
263
- const vm = (await getQuickJS()).createVm();
264
- const arena = new Arena(vm, { registeredObjects: [] });
488
+ const ctx = (await getQuickJS()).newContext();
489
+ const arena = new Arena(ctx, { isMarshalable: true, registeredObjects: [] });
265
490
 
266
491
  arena.register(Math, `Math`);
267
492
  expect(arena.evalCode(`Math`)).toBe(Math);
@@ -276,12 +501,13 @@ test("register and unregister", async () => {
276
501
  expect(arena.evalCode(`new Error()`)).toBeInstanceOf(Error);
277
502
 
278
503
  arena.dispose();
279
- vm.dispose();
504
+ ctx.dispose();
280
505
  });
281
506
 
282
507
  test("registeredObjects option", async () => {
283
- const vm = (await getQuickJS()).createVm();
284
- const arena = new Arena(vm, {
508
+ const ctx = (await getQuickJS()).newContext();
509
+ const arena = new Arena(ctx, {
510
+ isMarshalable: true,
285
511
  registeredObjects: [[Symbol.iterator, "Symbol.iterator"]],
286
512
  });
287
513
 
@@ -291,17 +517,93 @@ test("registeredObjects option", async () => {
291
517
  );
292
518
 
293
519
  arena.dispose();
294
- vm.dispose();
520
+ ctx.dispose();
295
521
  });
296
522
 
297
- test("isMarshalable option", async () => {
298
- const vm = (await getQuickJS()).createVm();
299
- const arena = new Arena(vm, {
300
- isMarshalable: o => o !== globalThis,
523
+ describe("isMarshalable option", () => {
524
+ test("false", async () => {
525
+ const ctx = (await getQuickJS()).newContext();
526
+ const arena = new Arena(ctx, { isMarshalable: false });
527
+
528
+ expect(arena.evalCode(`s => s === undefined`)(globalThis)).toBe(true);
529
+ expect(arena.evalCode(`s => s === undefined`)({})).toBe(true);
530
+ arena.expose({ aaa: globalThis });
531
+ expect(arena.evalCode(`aaa`)).toBeUndefined();
532
+
533
+ arena.dispose();
534
+ ctx.dispose();
301
535
  });
302
536
 
303
- expect(arena.evalCode(`s => s === undefined`)(globalThis)).toBe(true);
537
+ test("json", async () => {
538
+ const ctx = (await getQuickJS()).newContext();
539
+ const arena = new Arena(ctx, { isMarshalable: "json" });
304
540
 
305
- arena.dispose();
306
- vm.dispose();
541
+ const obj = { a: () => {}, b: new Date(), c: [() => {}, 1] };
542
+ const objJSON = { b: obj.b.toISOString(), c: [null, 1] };
543
+ const objJSON2 = arena.evalCode(`a => a`)(obj);
544
+ expect(objJSON2).toStrictEqual(objJSON);
545
+ arena.expose({ obj });
546
+ const exposedObj = arena.evalCode(`obj`);
547
+ expect(exposedObj).toStrictEqual(objJSON);
548
+ expect(exposedObj).not.toBe(objJSON2);
549
+
550
+ arena.dispose();
551
+ ctx.dispose();
552
+ });
553
+
554
+ test("conditional", async () => {
555
+ const ctx = (await getQuickJS()).newContext();
556
+ const arena = new Arena(ctx, {
557
+ isMarshalable: (o) => o !== globalThis,
558
+ });
559
+
560
+ const obj = { a: 1 };
561
+ expect(arena.evalCode(`s => s === undefined`)(globalThis)).toBe(true);
562
+ expect(arena.evalCode(`s => s === undefined`)(obj)).toBe(false);
563
+ arena.expose({ aaa: globalThis, bbb: obj });
564
+ expect(arena.evalCode(`aaa`)).toBeUndefined();
565
+ expect(arena.evalCode(`bbb`)).toBe(obj);
566
+
567
+ arena.dispose();
568
+ ctx.dispose();
569
+ });
570
+ });
571
+
572
+ describe("edge cases", () => {
573
+ test("getter", async () => {
574
+ const ctx = (await getQuickJS()).newContext();
575
+ const arena = new Arena(ctx, { isMarshalable: true });
576
+
577
+ const called: string[] = [];
578
+ const obj = { c: 0 };
579
+ const exposed = {
580
+ get a() {
581
+ called.push("a");
582
+ return {
583
+ get b() {
584
+ called.push("b");
585
+ return obj;
586
+ },
587
+ };
588
+ },
589
+ };
590
+ const cb: { current?: () => any } = {};
591
+ const register = (fn: () => any) => {
592
+ cb.current = fn;
593
+ };
594
+
595
+ arena.expose({ exposed, register });
596
+ expect(called).toEqual([]);
597
+
598
+ arena.evalCode(`register(() => exposed.a.b.c);`);
599
+ expect(cb.current?.()).toBe(0);
600
+ expect(called).toEqual(["a", "b"]);
601
+
602
+ obj.c = 1;
603
+ expect(cb.current?.()).toBe(1); // this line causes an error when context is disposed
604
+ expect(called).toEqual(["a", "b", "a", "b"]);
605
+
606
+ arena.dispose();
607
+ // ctx.dispose(); // reports an error
608
+ });
307
609
  });