quickjs-emscripten-sync 1.2.0 → 1.5.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 (76) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +71 -44
  3. package/dist/index.d.ts +116 -23
  4. package/dist/quickjs-emscripten-sync.mjs +842 -0
  5. package/dist/quickjs-emscripten-sync.umd.js +64 -2
  6. package/package.json +22 -53
  7. package/src/default.ts +2 -2
  8. package/src/index.test.ts +223 -59
  9. package/src/index.ts +72 -66
  10. package/src/marshal/custom.test.ts +50 -0
  11. package/src/marshal/custom.ts +36 -0
  12. package/src/marshal/function.test.ts +64 -75
  13. package/src/marshal/function.ts +15 -18
  14. package/src/marshal/index.test.ts +122 -77
  15. package/src/marshal/index.ts +19 -15
  16. package/src/marshal/json.test.ts +37 -41
  17. package/src/marshal/json.ts +5 -7
  18. package/src/marshal/object.test.ts +59 -84
  19. package/src/marshal/object.ts +8 -15
  20. package/src/marshal/primitive.test.ts +18 -21
  21. package/src/marshal/primitive.ts +11 -10
  22. package/src/marshal/promise.test.ts +85 -0
  23. package/src/marshal/promise.ts +19 -0
  24. package/src/marshal/properties.test.ts +24 -27
  25. package/src/marshal/properties.ts +15 -17
  26. package/src/unmarshal/custom.test.ts +50 -0
  27. package/src/unmarshal/custom.ts +31 -0
  28. package/src/unmarshal/function.test.ts +63 -86
  29. package/src/unmarshal/function.ts +13 -22
  30. package/src/unmarshal/index.test.ts +114 -109
  31. package/src/unmarshal/index.ts +15 -14
  32. package/src/unmarshal/object.test.ts +42 -46
  33. package/src/unmarshal/object.ts +16 -20
  34. package/src/unmarshal/primitive.test.ts +17 -15
  35. package/src/unmarshal/primitive.ts +11 -18
  36. package/src/unmarshal/promise.test.ts +44 -0
  37. package/src/unmarshal/promise.ts +31 -0
  38. package/src/unmarshal/properties.test.ts +11 -9
  39. package/src/unmarshal/properties.ts +44 -45
  40. package/src/util.test.ts +14 -2
  41. package/src/util.ts +18 -8
  42. package/src/vmmap.test.ts +88 -82
  43. package/src/vmmap.ts +30 -44
  44. package/src/vmutil.test.ts +100 -75
  45. package/src/vmutil.ts +51 -34
  46. package/src/wrapper.test.ts +129 -167
  47. package/src/wrapper.ts +59 -62
  48. package/dist/default.d.ts +0 -4
  49. package/dist/marshal/function.d.ts +0 -2
  50. package/dist/marshal/index.d.ts +0 -11
  51. package/dist/marshal/json.d.ts +0 -2
  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 +0 -2
  57. package/dist/quickjs-emscripten-sync.cjs.map +0 -1
  58. package/dist/quickjs-emscripten-sync.modern.js +0 -2
  59. package/dist/quickjs-emscripten-sync.modern.js.map +0 -1
  60. package/dist/quickjs-emscripten-sync.module.js +0 -2
  61. package/dist/quickjs-emscripten-sync.module.js.map +0 -1
  62. package/dist/quickjs-emscripten-sync.umd.js.map +0 -1
  63. package/dist/unmarshal/function.d.ts +0 -2
  64. package/dist/unmarshal/index.d.ts +0 -9
  65. package/dist/unmarshal/object.d.ts +0 -2
  66. package/dist/unmarshal/primitive.d.ts +0 -2
  67. package/dist/unmarshal/properties.d.ts +0 -2
  68. package/dist/unmarshal/symbol.d.ts +0 -2
  69. package/dist/util.d.ts +0 -7
  70. package/dist/vmmap.d.ts +0 -35
  71. package/dist/vmutil.d.ts +0 -11
  72. package/dist/wrapper.d.ts +0 -11
  73. package/src/marshal/symbol.test.ts +0 -22
  74. package/src/marshal/symbol.ts +0 -20
  75. package/src/unmarshal/symbol.test.ts +0 -23
  76. package/src/unmarshal/symbol.ts +0 -12
package/src/index.test.ts CHANGED
@@ -1,10 +1,82 @@
1
1
  import { getQuickJS } from "quickjs-emscripten";
2
+ import { describe, expect, test, vi } from "vitest";
3
+
4
+ import { isWrapped } from "./wrapper";
5
+
2
6
  import { Arena } from ".";
3
7
 
8
+ describe("readme", () => {
9
+ test("first", async () => {
10
+ class Cls {
11
+ field = 0;
12
+
13
+ method() {
14
+ return ++this.field;
15
+ }
16
+ }
17
+
18
+ const ctx = (await getQuickJS()).newContext();
19
+ const arena = new Arena(ctx, { isMarshalable: true });
20
+
21
+ // We can pass objects to the VM and run code safely
22
+ const exposed = {
23
+ Cls,
24
+ cls: new Cls(),
25
+ syncedCls: arena.sync(new Cls()),
26
+ };
27
+ arena.expose(exposed);
28
+
29
+ expect(arena.evalCode(`cls instanceof Cls`)).toBe(true);
30
+ expect(arena.evalCode(`cls.field`)).toBe(0);
31
+ expect(arena.evalCode(`cls.method()`)).toBe(1);
32
+ expect(arena.evalCode(`cls.field`)).toBe(1);
33
+
34
+ expect(arena.evalCode(`syncedCls.field`)).toBe(0);
35
+ expect(exposed.syncedCls.method()).toBe(1);
36
+ expect(arena.evalCode(`syncedCls.field`)).toBe(1);
37
+
38
+ arena.dispose();
39
+ ctx.dispose();
40
+ });
41
+
42
+ test("usage", async () => {
43
+ const quickjs = await getQuickJS();
44
+ const ctx = quickjs.newContext();
45
+
46
+ // init Arena
47
+ // ⚠️ Marshaling is opt-in for security reasons.
48
+ // ⚠️ Be careful when activating marshalling.
49
+ const arena = new Arena(ctx, { isMarshalable: true });
50
+
51
+ // expose objects as global objects in QuickJS VM
52
+ const log = vi.fn();
53
+ arena.expose({
54
+ console: { log },
55
+ });
56
+ arena.evalCode(`console.log("hello, world");`); // run console.log
57
+ expect(log).toBeCalledWith("hello, world");
58
+ arena.evalCode(`1 + 1`); // 2
59
+
60
+ // expose objects but also enable sync
61
+ const data = arena.sync({ hoge: "foo" });
62
+ arena.expose({ data });
63
+
64
+ arena.evalCode(`data.hoge = "bar"`);
65
+ // eval code and operations to exposed objects are automatically synced
66
+ expect(data.hoge).toBe("bar");
67
+ data.hoge = "changed!";
68
+ expect(arena.evalCode(`data.hoge`)).toBe("changed!");
69
+
70
+ // Don't forget calling arena.dispose() before disposing QuickJS VM!
71
+ arena.dispose();
72
+ ctx.dispose();
73
+ });
74
+ });
75
+
4
76
  describe("evalCode", () => {
5
77
  test("simple object and function", async () => {
6
- const vm = (await getQuickJS()).createVm();
7
- const arena = new Arena(vm, { isMarshalable: true });
78
+ const ctx = (await getQuickJS()).newContext();
79
+ const arena = new Arena(ctx, { isMarshalable: true });
8
80
 
9
81
  const result = arena.evalCode(
10
82
  `({
@@ -18,7 +90,7 @@ describe("evalCode", () => {
18
90
  get e() {
19
91
  return { a: 1 };
20
92
  }
21
- })`
93
+ })`,
22
94
  );
23
95
  expect(result).toEqual({
24
96
  a: 1,
@@ -36,23 +108,36 @@ describe("evalCode", () => {
36
108
  expect(result.e).toStrictEqual({ a: 1 });
37
109
 
38
110
  arena.dispose();
39
- vm.dispose();
111
+ ctx.dispose();
40
112
  });
41
113
 
42
114
  test("Math", async () => {
43
- const vm = (await getQuickJS()).createVm();
44
- const arena = new Arena(vm, { isMarshalable: true });
115
+ const ctx = (await getQuickJS()).newContext();
116
+ const arena = new Arena(ctx, { isMarshalable: true });
45
117
 
46
118
  const VMMath = arena.evalCode(`Math`) as Math;
47
119
  expect(VMMath.floor(1.1)).toBe(1);
48
120
 
49
121
  arena.dispose();
50
- vm.dispose();
122
+ ctx.dispose();
123
+ });
124
+
125
+ test("Date", async () => {
126
+ const ctx = (await getQuickJS()).newContext();
127
+ const arena = new Arena(ctx, { isMarshalable: true });
128
+
129
+ const date = new Date(2022, 7, 26);
130
+ expect(arena.evalCode("new Date(2022, 7, 26)")).toEqual(date);
131
+ expect(arena.evalCode("d => d instanceof Date")(date)).toBe(true);
132
+ expect(arena.evalCode("d => d.getTime()")(date)).toBe(date.getTime());
133
+
134
+ arena.dispose();
135
+ ctx.dispose();
51
136
  });
52
137
 
53
138
  test("class", async () => {
54
- const vm = (await getQuickJS()).createVm();
55
- const arena = new Arena(vm, { isMarshalable: true });
139
+ const ctx = (await getQuickJS()).newContext();
140
+ const arena = new Arena(ctx, { isMarshalable: true });
56
141
 
57
142
  const instance = arena.evalCode(`{
58
143
  globalThis.Cls = class D {
@@ -73,12 +158,12 @@ describe("evalCode", () => {
73
158
  expect(instance.a).toBe(102);
74
159
 
75
160
  arena.dispose();
76
- vm.dispose();
161
+ ctx.dispose();
77
162
  });
78
163
 
79
164
  test("obj", async () => {
80
- const vm = (await getQuickJS()).createVm();
81
- const arena = new Arena(vm, { isMarshalable: true });
165
+ const ctx = (await getQuickJS()).newContext();
166
+ const arena = new Arena(ctx, { isMarshalable: true });
82
167
 
83
168
  const obj = arena.evalCode(`globalThis.AAA = { a: 1 }`);
84
169
 
@@ -89,14 +174,56 @@ describe("evalCode", () => {
89
174
  expect(arena.evalCode(`AAA.a`)).toBe(2);
90
175
 
91
176
  arena.dispose();
92
- vm.dispose();
177
+ ctx.dispose();
178
+ });
179
+
180
+ test("promise", async () => {
181
+ const ctx = (await getQuickJS()).newContext();
182
+ const arena = new Arena(ctx, { isMarshalable: true });
183
+
184
+ const [promise, resolve] = arena.evalCode<[Promise<string>, (d: string) => void]>(`
185
+ let resolve;
186
+ const promise = new Promise(r => {
187
+ resolve = r;
188
+ }).then(d => d + "!");
189
+ [promise, resolve]
190
+ `);
191
+ expect(promise).instanceOf(Promise);
192
+ expect(isWrapped(arena._unwrapIfNotSynced(promise), arena._symbol)).toBe(false);
193
+
194
+ resolve("hoge");
195
+ expect(arena.executePendingJobs()).toBe(2);
196
+ expect(await promise).toBe("hoge!");
197
+
198
+ arena.dispose();
199
+ ctx.dispose();
200
+ });
201
+
202
+ test("promise2", async () => {
203
+ const ctx = (await getQuickJS()).newContext();
204
+ const arena = new Arena(ctx, { isMarshalable: true });
205
+
206
+ const deferred: { resolve?: (s: string) => void } = {};
207
+ const promise = new Promise(resolve => {
208
+ deferred.resolve = resolve;
209
+ });
210
+ const res = vi.fn();
211
+ arena.evalCode(`(p, r) => { p.then(d => { r(d + "!"); }); }`)(promise, res);
212
+
213
+ deferred.resolve?.("hoge");
214
+ await promise;
215
+ expect(arena.executePendingJobs()).toBe(1);
216
+ expect(res).toBeCalledWith("hoge!");
217
+
218
+ arena.dispose();
219
+ ctx.dispose();
93
220
  });
94
221
  });
95
222
 
96
223
  describe("expose without sync", () => {
97
224
  test("simple object and function", async () => {
98
- const vm = (await getQuickJS()).createVm();
99
- const arena = new Arena(vm, { isMarshalable: true });
225
+ const ctx = (await getQuickJS()).newContext();
226
+ const arena = new Arena(ctx, { isMarshalable: true });
100
227
 
101
228
  const obj = {
102
229
  a: 1,
@@ -127,12 +254,12 @@ describe("expose without sync", () => {
127
254
  expect(arena.evalCode(`obj.e`)).toStrictEqual({ a: 1 });
128
255
 
129
256
  arena.dispose();
130
- vm.dispose();
257
+ ctx.dispose();
131
258
  });
132
259
 
133
260
  test("Math", async () => {
134
- const vm = (await getQuickJS()).createVm();
135
- const arena = new Arena(vm, { isMarshalable: true });
261
+ const ctx = (await getQuickJS()).newContext();
262
+ const arena = new Arena(ctx, { isMarshalable: true });
136
263
 
137
264
  arena.expose({ Math2: Math });
138
265
  expect(arena.evalCode(`Math`)).not.toBe(Math);
@@ -140,12 +267,12 @@ describe("expose without sync", () => {
140
267
  expect(arena.evalCode(`Math2.floor(1.1)`)).toBe(1);
141
268
 
142
269
  arena.dispose();
143
- vm.dispose();
270
+ ctx.dispose();
144
271
  });
145
272
 
146
273
  test("class", async () => {
147
- const vm = (await getQuickJS()).createVm();
148
- const arena = new Arena(vm, { isMarshalable: true });
274
+ const ctx = (await getQuickJS()).newContext();
275
+ const arena = new Arena(ctx, { isMarshalable: true });
149
276
 
150
277
  class D {
151
278
  a: number;
@@ -169,12 +296,12 @@ describe("expose without sync", () => {
169
296
  expect(arena.evalCode(`d.a`)).toBe(102);
170
297
 
171
298
  arena.dispose();
172
- vm.dispose();
299
+ ctx.dispose();
173
300
  });
174
301
 
175
302
  test("object and function", async () => {
176
- const vm = (await getQuickJS()).createVm();
177
- const arena = new Arena(vm, { isMarshalable: true });
303
+ const ctx = (await getQuickJS()).newContext();
304
+ const arena = new Arena(ctx, { isMarshalable: true });
178
305
 
179
306
  const obj = {
180
307
  a: 1,
@@ -206,14 +333,14 @@ describe("expose without sync", () => {
206
333
  expect(arena.evalCode(`obj.a`)).toBe(100);
207
334
 
208
335
  arena.dispose();
209
- vm.dispose();
336
+ ctx.dispose();
210
337
  });
211
338
  });
212
339
 
213
340
  describe("expose with sync", () => {
214
341
  test("sync before expose", async () => {
215
- const vm = (await getQuickJS()).createVm();
216
- const arena = new Arena(vm, { isMarshalable: true });
342
+ const ctx = (await getQuickJS()).newContext();
343
+ const arena = new Arena(ctx, { isMarshalable: true });
217
344
 
218
345
  const obj = {
219
346
  a: 1,
@@ -252,12 +379,12 @@ describe("expose with sync", () => {
252
379
  expect(arena.evalCode(`obj.a`)).toBe(100);
253
380
 
254
381
  arena.dispose();
255
- vm.dispose();
382
+ ctx.dispose();
256
383
  });
257
384
 
258
385
  test("sync after expose", async () => {
259
- const vm = (await getQuickJS()).createVm();
260
- const arena = new Arena(vm, { isMarshalable: true });
386
+ const ctx = (await getQuickJS()).newContext();
387
+ const arena = new Arena(ctx, { isMarshalable: true });
261
388
 
262
389
  const obj = {
263
390
  a: 1,
@@ -297,13 +424,13 @@ describe("expose with sync", () => {
297
424
  expect(arena.evalCode(`obj.a`)).toBe(100);
298
425
 
299
426
  arena.dispose();
300
- vm.dispose();
427
+ ctx.dispose();
301
428
  });
302
429
  });
303
430
 
304
431
  test("evalCode -> expose", async () => {
305
- const vm = (await getQuickJS()).createVm();
306
- const arena = new Arena(vm, { isMarshalable: true });
432
+ const ctx = (await getQuickJS()).newContext();
433
+ const arena = new Arena(ctx, { isMarshalable: true });
307
434
 
308
435
  const obj = arena.evalCode(`({ a: 1, b: 1 })`);
309
436
  arena.expose({ obj });
@@ -329,12 +456,12 @@ test("evalCode -> expose", async () => {
329
456
  expect(arena.evalCode(`obj.b`)).toBe(2);
330
457
 
331
458
  arena.dispose();
332
- vm.dispose();
459
+ ctx.dispose();
333
460
  });
334
461
 
335
462
  test("expose -> evalCode", async () => {
336
- const vm = (await getQuickJS()).createVm();
337
- const arena = new Arena(vm, { isMarshalable: true });
463
+ const ctx = (await getQuickJS()).newContext();
464
+ const arena = new Arena(ctx, { isMarshalable: true });
338
465
 
339
466
  const obj = { a: 1 };
340
467
  arena.expose({ obj });
@@ -351,12 +478,12 @@ test("expose -> evalCode", async () => {
351
478
  expect(arena.evalCode(`obj.a`)).toBe(3);
352
479
 
353
480
  arena.dispose();
354
- vm.dispose();
481
+ ctx.dispose();
355
482
  });
356
483
 
357
484
  test("evalCode -> expose -> evalCode", async () => {
358
- const vm = (await getQuickJS()).createVm();
359
- const arena = new Arena(vm, { isMarshalable: true });
485
+ const ctx = (await getQuickJS()).newContext();
486
+ const arena = new Arena(ctx, { isMarshalable: true });
360
487
 
361
488
  const obj = [1];
362
489
  expect(arena.evalCode("a => a[0] + 10")(obj)).toBe(11);
@@ -364,12 +491,12 @@ test("evalCode -> expose -> evalCode", async () => {
364
491
  expect(arena.evalCode("obj")).toBe(obj);
365
492
 
366
493
  arena.dispose();
367
- vm.dispose();
494
+ ctx.dispose();
368
495
  });
369
496
 
370
497
  test("register and unregister", async () => {
371
- const vm = (await getQuickJS()).createVm();
372
- const arena = new Arena(vm, { isMarshalable: true, registeredObjects: [] });
498
+ const ctx = (await getQuickJS()).newContext();
499
+ const arena = new Arena(ctx, { isMarshalable: true, registeredObjects: [] });
373
500
 
374
501
  arena.register(Math, `Math`);
375
502
  expect(arena.evalCode(`Math`)).toBe(Math);
@@ -384,29 +511,27 @@ test("register and unregister", async () => {
384
511
  expect(arena.evalCode(`new Error()`)).toBeInstanceOf(Error);
385
512
 
386
513
  arena.dispose();
387
- vm.dispose();
514
+ ctx.dispose();
388
515
  });
389
516
 
390
517
  test("registeredObjects option", async () => {
391
- const vm = (await getQuickJS()).createVm();
392
- const arena = new Arena(vm, {
518
+ const ctx = (await getQuickJS()).newContext();
519
+ const arena = new Arena(ctx, {
393
520
  isMarshalable: true,
394
521
  registeredObjects: [[Symbol.iterator, "Symbol.iterator"]],
395
522
  });
396
523
 
397
524
  expect(arena.evalCode(`Symbol.iterator`)).toBe(Symbol.iterator);
398
- expect(arena.evalCode(`s => s === Symbol.iterator`)(Symbol.iterator)).toBe(
399
- true
400
- );
525
+ expect(arena.evalCode(`s => s === Symbol.iterator`)(Symbol.iterator)).toBe(true);
401
526
 
402
527
  arena.dispose();
403
- vm.dispose();
528
+ ctx.dispose();
404
529
  });
405
530
 
406
531
  describe("isMarshalable option", () => {
407
532
  test("false", async () => {
408
- const vm = (await getQuickJS()).createVm();
409
- const arena = new Arena(vm, { isMarshalable: false });
533
+ const ctx = (await getQuickJS()).newContext();
534
+ const arena = new Arena(ctx, { isMarshalable: false });
410
535
 
411
536
  expect(arena.evalCode(`s => s === undefined`)(globalThis)).toBe(true);
412
537
  expect(arena.evalCode(`s => s === undefined`)({})).toBe(true);
@@ -414,12 +539,12 @@ describe("isMarshalable option", () => {
414
539
  expect(arena.evalCode(`aaa`)).toBeUndefined();
415
540
 
416
541
  arena.dispose();
417
- vm.dispose();
542
+ ctx.dispose();
418
543
  });
419
544
 
420
545
  test("json", async () => {
421
- const vm = (await getQuickJS()).createVm();
422
- const arena = new Arena(vm, { isMarshalable: "json" });
546
+ const ctx = (await getQuickJS()).newContext();
547
+ const arena = new Arena(ctx, { isMarshalable: "json" });
423
548
 
424
549
  const obj = { a: () => {}, b: new Date(), c: [() => {}, 1] };
425
550
  const objJSON = { b: obj.b.toISOString(), c: [null, 1] };
@@ -431,13 +556,13 @@ describe("isMarshalable option", () => {
431
556
  expect(exposedObj).not.toBe(objJSON2);
432
557
 
433
558
  arena.dispose();
434
- vm.dispose();
559
+ ctx.dispose();
435
560
  });
436
561
 
437
562
  test("conditional", async () => {
438
- const vm = (await getQuickJS()).createVm();
439
- const arena = new Arena(vm, {
440
- isMarshalable: (o) => o !== globalThis,
563
+ const ctx = (await getQuickJS()).newContext();
564
+ const arena = new Arena(ctx, {
565
+ isMarshalable: o => o !== globalThis,
441
566
  });
442
567
 
443
568
  const obj = { a: 1 };
@@ -448,6 +573,45 @@ describe("isMarshalable option", () => {
448
573
  expect(arena.evalCode(`bbb`)).toBe(obj);
449
574
 
450
575
  arena.dispose();
451
- vm.dispose();
576
+ ctx.dispose();
577
+ });
578
+ });
579
+
580
+ describe("edge cases", () => {
581
+ test("getter", async () => {
582
+ const ctx = (await getQuickJS()).newContext();
583
+ const arena = new Arena(ctx, { isMarshalable: true });
584
+
585
+ const called: string[] = [];
586
+ const obj = { c: 0 };
587
+ const exposed = {
588
+ get a() {
589
+ called.push("a");
590
+ return {
591
+ get b() {
592
+ called.push("b");
593
+ return obj;
594
+ },
595
+ };
596
+ },
597
+ };
598
+ const cb: { current?: () => any } = {};
599
+ const register = (fn: () => any) => {
600
+ cb.current = fn;
601
+ };
602
+
603
+ arena.expose({ exposed, register });
604
+ expect(called).toEqual([]);
605
+
606
+ arena.evalCode(`register(() => exposed.a.b.c);`);
607
+ expect(cb.current?.()).toBe(0);
608
+ expect(called).toEqual(["a", "b"]);
609
+
610
+ obj.c = 1;
611
+ expect(cb.current?.()).toBe(1); // this line causes an error when context is disposed
612
+ expect(called).toEqual(["a", "b", "a", "b"]);
613
+
614
+ arena.dispose();
615
+ // ctx.dispose(); // reports an error
452
616
  });
453
617
  });