quickjs-emscripten-sync 1.5.1 → 1.6.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.test.ts CHANGED
@@ -218,6 +218,42 @@ describe("evalCode", () => {
218
218
  arena.dispose();
219
219
  ctx.dispose();
220
220
  });
221
+
222
+ test("async function", async () => {
223
+ const ctx = (await getQuickJS()).newContext();
224
+ const arena = new Arena(ctx, { isMarshalable: true });
225
+
226
+ const consolelog = vi.fn();
227
+ arena.expose({
228
+ console: {
229
+ log: consolelog,
230
+ },
231
+ });
232
+
233
+ arena.evalCode(`
234
+ const someAsyncOperation = async () => "hello";
235
+ const execute = async () => {
236
+ try {
237
+ const res = await someAsyncOperation();
238
+ console.log(res);
239
+ } catch (e) {
240
+ console.log(e);
241
+ }
242
+ };
243
+ execute();
244
+ `);
245
+ expect(consolelog).toBeCalledTimes(0);
246
+ expect(arena.executePendingJobs()).toBe(2);
247
+
248
+ arena.executePendingJobs();
249
+
250
+ expect(consolelog).toBeCalledTimes(1);
251
+ expect(consolelog).toBeCalledWith("hello");
252
+ expect(arena.executePendingJobs()).toBe(0);
253
+
254
+ arena.dispose();
255
+ ctx.dispose();
256
+ });
221
257
  });
222
258
 
223
259
  describe("expose without sync", () => {
@@ -577,41 +613,301 @@ describe("isMarshalable option", () => {
577
613
  });
578
614
  });
579
615
 
580
- describe("edge cases", () => {
581
- test("getter", async () => {
616
+ describe("evalModule", () => {
617
+ test("module can modify exposed globals", async () => {
582
618
  const ctx = (await getQuickJS()).newContext();
583
619
  const arena = new Arena(ctx, { isMarshalable: true });
584
620
 
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
- };
621
+ const data = arena.sync({ count: 0, message: "" });
622
+ arena.expose({ data });
602
623
 
603
- arena.expose({ exposed, register });
604
- expect(called).toEqual([]);
624
+ // Module code can have side effects on exposed globals
625
+ arena.evalModule(`
626
+ data.count = 42;
627
+ data.message = "Hello from module";
628
+ `);
605
629
 
606
- arena.evalCode(`register(() => exposed.a.b.c);`);
607
- expect(cb.current?.()).toBe(0);
608
- expect(called).toEqual(["a", "b"]);
630
+ expect(data.count).toBe(42);
631
+ expect(data.message).toBe("Hello from module");
609
632
 
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"]);
633
+ arena.dispose();
634
+ ctx.dispose();
635
+ });
636
+
637
+ test("module with function side effects", async () => {
638
+ const ctx = (await getQuickJS()).newContext();
639
+ const arena = new Arena(ctx, { isMarshalable: true });
640
+
641
+ const results: number[] = [];
642
+ arena.expose({
643
+ results,
644
+ push: (value: number) => results.push(value),
645
+ });
646
+
647
+ arena.evalModule(`
648
+ push(1);
649
+ push(2);
650
+ push(3);
651
+ `);
652
+
653
+ expect(results).toEqual([1, 2, 3]);
613
654
 
614
655
  arena.dispose();
615
- // ctx.dispose(); // reports an error
656
+ ctx.dispose();
657
+ });
658
+
659
+ test("module can define and use internal exports", async () => {
660
+ const ctx = (await getQuickJS()).newContext();
661
+ const arena = new Arena(ctx, { isMarshalable: true });
662
+
663
+ const output = arena.sync({ value: 0 });
664
+ arena.expose({ output });
665
+
666
+ // Module can export and use its own exports internally
667
+ arena.evalModule(`
668
+ export function double(x) {
669
+ return x * 2;
670
+ }
671
+
672
+ export const value = 21;
673
+
674
+ // Use the exports internally
675
+ output.value = double(value);
676
+ `);
677
+
678
+ expect(output.value).toBe(42);
679
+
680
+ arena.dispose();
681
+ ctx.dispose();
682
+ });
683
+
684
+ test("module with custom filename", async () => {
685
+ const ctx = (await getQuickJS()).newContext();
686
+ const arena = new Arena(ctx, { isMarshalable: true });
687
+
688
+ const state = arena.sync({ executed: false });
689
+ arena.expose({ state });
690
+
691
+ // Test that custom filename doesn't break functionality
692
+ arena.evalModule(`state.executed = true;`, "custom-module.js");
693
+
694
+ expect(state.executed).toBe(true);
695
+
696
+ arena.dispose();
697
+ ctx.dispose();
698
+ });
699
+
700
+ test("module with strict mode", async () => {
701
+ const ctx = (await getQuickJS()).newContext();
702
+ const arena = new Arena(ctx, { isMarshalable: true });
703
+
704
+ const result = arena.sync({ ok: false });
705
+ arena.expose({ result });
706
+
707
+ // Modules are strict by default
708
+ arena.evalModule(`
709
+ // This would fail in strict mode if we tried: undeclaredVariable = 1;
710
+ result.ok = true;
711
+ `);
712
+
713
+ expect(result.ok).toBe(true);
714
+
715
+ arena.dispose();
716
+ ctx.dispose();
717
+ });
718
+ });
719
+
720
+ describe("memory management", () => {
721
+ test("getMemoryUsage returns statistics", async () => {
722
+ const ctx = (await getQuickJS()).newContext();
723
+ const arena = new Arena(ctx, { isMarshalable: true });
724
+
725
+ const stats = arena.getMemoryUsage();
726
+
727
+ // Check that key properties exist
728
+ expect(stats).toHaveProperty("memory_used_size");
729
+ expect(stats).toHaveProperty("malloc_limit");
730
+ expect(stats).toHaveProperty("obj_count");
731
+ expect(stats).toHaveProperty("malloc_count");
732
+
733
+ // Memory should be used
734
+ expect(stats.memory_used_size).toBeGreaterThan(0);
735
+ expect(stats.malloc_count).toBeGreaterThan(0);
736
+
737
+ arena.dispose();
738
+ ctx.dispose();
739
+ });
740
+
741
+ test("getMemoryUsage tracks allocations", async () => {
742
+ const ctx = (await getQuickJS()).newContext();
743
+ const arena = new Arena(ctx, { isMarshalable: true });
744
+
745
+ const before = arena.getMemoryUsage();
746
+
747
+ // Allocate some objects
748
+ arena.evalCode(`
749
+ const arr = [];
750
+ for (let i = 0; i < 100; i++) {
751
+ arr.push({ id: i, data: "test".repeat(10) });
752
+ }
753
+ `);
754
+
755
+ const after = arena.getMemoryUsage();
756
+
757
+ // Memory usage should increase
758
+ expect(after.memory_used_size).toBeGreaterThan(before.memory_used_size);
759
+ expect(after.obj_count).toBeGreaterThan(before.obj_count);
760
+
761
+ arena.dispose();
762
+ ctx.dispose();
763
+ });
764
+
765
+ test("dumpMemoryUsage returns formatted string", async () => {
766
+ const ctx = (await getQuickJS()).newContext();
767
+ const arena = new Arena(ctx, { isMarshalable: true });
768
+
769
+ const dump = arena.dumpMemoryUsage();
770
+
771
+ // Should be a non-empty string
772
+ expect(typeof dump).toBe("string");
773
+ expect(dump.length).toBeGreaterThan(0);
774
+
775
+ // Should contain key memory metrics (the format varies)
776
+ expect(dump).toMatch(/memory|malloc/i);
777
+
778
+ arena.dispose();
779
+ ctx.dispose();
780
+ });
781
+
782
+ test("setMemoryLimit enforces memory constraints", async () => {
783
+ const ctx = (await getQuickJS()).newContext();
784
+ const arena = new Arena(ctx, { isMarshalable: true });
785
+
786
+ // Set a low memory limit (100KB)
787
+ arena.setMemoryLimit(100 * 1024);
788
+
789
+ // Verify limit is set
790
+ const stats = arena.getMemoryUsage();
791
+ expect(stats.malloc_limit).toBe(100 * 1024);
792
+
793
+ // Try to allocate too much memory
794
+ expect(() => {
795
+ arena.evalCode(`
796
+ const huge = [];
797
+ for (let i = 0; i < 1000000; i++) {
798
+ huge.push({ data: "x".repeat(1000) });
799
+ }
800
+ `);
801
+ }).toThrow();
802
+
803
+ arena.dispose();
804
+ ctx.dispose();
805
+ });
806
+
807
+ test("setMemoryLimit can be removed with -1", async () => {
808
+ const ctx = (await getQuickJS()).newContext();
809
+ const arena = new Arena(ctx, { isMarshalable: true });
810
+
811
+ // Set a limit
812
+ arena.setMemoryLimit(100 * 1024);
813
+ expect(arena.getMemoryUsage().malloc_limit).toBe(100 * 1024);
814
+
815
+ // Remove the limit
816
+ arena.setMemoryLimit(-1);
817
+ const stats = arena.getMemoryUsage();
818
+ expect(stats.malloc_limit).toBeGreaterThan(100 * 1024);
819
+
820
+ arena.dispose();
821
+ ctx.dispose();
822
+ });
823
+
824
+ test("setMaxStackSize prevents stack overflow", async () => {
825
+ const ctx = (await getQuickJS()).newContext();
826
+ const arena = new Arena(ctx, { isMarshalable: true });
827
+
828
+ // Set a small stack size (256KB)
829
+ arena.setMaxStackSize(256 * 1024);
830
+
831
+ // Try to cause stack overflow with deep recursion
832
+ expect(() => {
833
+ arena.evalCode(`
834
+ function recurse(n) {
835
+ if (n > 0) return recurse(n - 1);
836
+ return n;
837
+ }
838
+ recurse(100000);
839
+ `);
840
+ }).toThrow();
841
+
842
+ arena.dispose();
843
+ ctx.dispose();
844
+ });
845
+
846
+ test("setMaxStackSize can be removed with 0", async () => {
847
+ const ctx = (await getQuickJS()).newContext();
848
+ const arena = new Arena(ctx, { isMarshalable: true });
849
+
850
+ // Set a very small stack
851
+ arena.setMaxStackSize(128 * 1024);
852
+
853
+ // This should fail with small stack
854
+ expect(() => {
855
+ arena.evalCode(`
856
+ function recurse(n) {
857
+ if (n > 0) return recurse(n - 1);
858
+ return n;
859
+ }
860
+ recurse(10000);
861
+ `);
862
+ }).toThrow();
863
+
864
+ // Remove the limit
865
+ arena.setMaxStackSize(0);
866
+
867
+ // Now it should work (or at least get further)
868
+ const result = arena.evalCode(`
869
+ function recurse(n) {
870
+ if (n > 0) return recurse(n - 1);
871
+ return n;
872
+ }
873
+ recurse(1000);
874
+ `);
875
+
876
+ expect(result).toBe(0);
877
+
878
+ arena.dispose();
879
+ ctx.dispose();
880
+ });
881
+
882
+ test("memory limits work with marshaling", async () => {
883
+ const ctx = (await getQuickJS()).newContext();
884
+ const arena = new Arena(ctx, { isMarshalable: true });
885
+
886
+ // Set limit
887
+ arena.setMemoryLimit(200 * 1024);
888
+
889
+ const data = arena.sync({ items: [] as any[] });
890
+ arena.expose({ data });
891
+
892
+ // Should be able to add reasonable data
893
+ arena.evalCode(`
894
+ for (let i = 0; i < 10; i++) {
895
+ data.items.push({ id: i });
896
+ }
897
+ `);
898
+
899
+ expect(data.items.length).toBe(10);
900
+
901
+ // But not unlimited data
902
+ expect(() => {
903
+ arena.evalCode(`
904
+ for (let i = 0; i < 100000; i++) {
905
+ data.items.push({ id: i, data: "x".repeat(1000) });
906
+ }
907
+ `);
908
+ }).toThrow();
909
+
910
+ arena.dispose();
911
+ ctx.dispose();
616
912
  });
617
913
  });
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  VmCallResult,
7
7
  } from "quickjs-emscripten";
8
8
 
9
+ import { wrapContext, QuickJSContextEx } from "./contextex";
9
10
  import { defaultRegisteredObjects } from "./default";
10
11
  import marshal from "./marshal";
11
12
  import unmarshal from "./unmarshal";
@@ -48,13 +49,15 @@ export type Options = {
48
49
  isHandleWrappable?: (handle: QuickJSHandle, ctx: QuickJSContext) => boolean;
49
50
  /** Compatibility with quickjs-emscripten prior to v0.15. Inject code for compatibility into context at Arena class initialization time. */
50
51
  compat?: boolean;
52
+ /** Experimental: use QuickJSContextEx, which wraps existing QuickJSContext. */
53
+ experimentalContextEx?: boolean;
51
54
  };
52
55
 
53
56
  /**
54
57
  * The Arena class manages all generated handles at once by quickjs-emscripten and automatically converts objects between the host and the QuickJS VM.
55
58
  */
56
59
  export class Arena {
57
- context: QuickJSContext;
60
+ context: QuickJSContextEx;
58
61
  _map: VMMap;
59
62
  _registeredMap: VMMap;
60
63
  _registeredMapDispose: Set<any> = new Set();
@@ -74,7 +77,7 @@ export class Arena {
74
77
  };
75
78
  }
76
79
 
77
- this.context = ctx;
80
+ this.context = options?.experimentalContextEx ? wrapContext(ctx) : ctx;
78
81
  this._options = options;
79
82
  this._symbolHandle = ctx.unwrapResult(ctx.evalCode(`Symbol()`));
80
83
  this._map = new VMMap(ctx);
@@ -89,6 +92,7 @@ export class Arena {
89
92
  this._map.dispose();
90
93
  this._registeredMap.dispose();
91
94
  this._symbolHandle.dispose();
95
+ this.context.disposeEx?.();
92
96
  }
93
97
 
94
98
  /**
@@ -99,6 +103,27 @@ export class Arena {
99
103
  return this._unwrapResultAndUnmarshal(handle);
100
104
  }
101
105
 
106
+ /**
107
+ * Evaluate ES module code in the VM. The module can import/export, but the return value
108
+ * depends on whether the module exports are accessible (implementation varies by quickjs-emscripten version).
109
+ * Use this for side effects or when you don't need access to exports.
110
+ *
111
+ * @param code - The ES module code to evaluate
112
+ * @param filename - Optional filename for debugging purposes (default: "module.js")
113
+ * @returns Undefined in most cases (module side effects are applied)
114
+ *
115
+ * @example
116
+ * ```js
117
+ * // Execute module code with side effects
118
+ * arena.expose({ data: { count: 0 } });
119
+ * arena.evalModule('import { data } from "globals"; data.count = 42;'); // undefined, but data.count is now 42
120
+ * ```
121
+ */
122
+ evalModule(code: string, filename = "module.js"): void {
123
+ const handle = this.context.evalCode(code, filename, { type: "module" });
124
+ this._unwrapResultAndUnmarshal(handle);
125
+ }
126
+
102
127
  /**
103
128
  * Almost same as `vm.executePendingJobs()`, but it converts and re-throws error objects when an error is thrown during evaluation.
104
129
  */
@@ -110,6 +135,121 @@ export class Arena {
110
135
  throw this._unwrapIfNotSynced(result.error.consume(this._unmarshal));
111
136
  }
112
137
 
138
+ /**
139
+ * Set the max memory this runtime can allocate.
140
+ * To remove the limit, set to `-1`.
141
+ *
142
+ * This is useful for preventing runaway memory usage in untrusted code.
143
+ *
144
+ * @param limitBytes - Maximum memory in bytes, or -1 to remove limit
145
+ *
146
+ * @example
147
+ * ```js
148
+ * // Limit sandbox to 10MB
149
+ * arena.setMemoryLimit(10 * 1024 * 1024);
150
+ *
151
+ * try {
152
+ * arena.evalCode(`const huge = new Array(1000000000);`);
153
+ * } catch (e) {
154
+ * console.log("Memory limit exceeded");
155
+ * }
156
+ * ```
157
+ */
158
+ setMemoryLimit(limitBytes: number): void {
159
+ this.context.runtime.setMemoryLimit(limitBytes);
160
+ }
161
+
162
+ /**
163
+ * Set the max stack size for this runtime, in bytes.
164
+ * To remove the limit, set to `0`.
165
+ *
166
+ * This is useful for preventing stack overflow from deeply nested calls or recursion.
167
+ *
168
+ * @param stackSize - Maximum stack size in bytes, or 0 to remove limit
169
+ *
170
+ * @example
171
+ * ```js
172
+ * // Limit stack to 512KB
173
+ * arena.setMaxStackSize(512 * 1024);
174
+ *
175
+ * try {
176
+ * arena.evalCode(`function recurse() { recurse(); } recurse();`);
177
+ * } catch (e) {
178
+ * console.log("Stack overflow prevented");
179
+ * }
180
+ * ```
181
+ */
182
+ setMaxStackSize(stackSize: number): void {
183
+ this.context.runtime.setMaxStackSize(stackSize);
184
+ }
185
+
186
+ /**
187
+ * Get detailed memory usage statistics for this runtime.
188
+ *
189
+ * @returns An object containing detailed memory allocation information
190
+ *
191
+ * @example
192
+ * ```js
193
+ * const stats = arena.getMemoryUsage();
194
+ * console.log(`Memory used: ${stats.memory_used_size} bytes`);
195
+ * console.log(`Object count: ${stats.obj_count}`);
196
+ * console.log(`Memory limit: ${stats.malloc_limit}`);
197
+ * ```
198
+ */
199
+ getMemoryUsage(): {
200
+ malloc_limit: number;
201
+ memory_used_size: number;
202
+ malloc_count: number;
203
+ memory_used_count: number;
204
+ atom_count: number;
205
+ atom_size: number;
206
+ str_count: number;
207
+ str_size: number;
208
+ obj_count: number;
209
+ obj_size: number;
210
+ prop_count: number;
211
+ prop_size: number;
212
+ shape_count: number;
213
+ shape_size: number;
214
+ js_func_count: number;
215
+ js_func_size: number;
216
+ js_func_code_size: number;
217
+ js_func_pc2line_count: number;
218
+ js_func_pc2line_size: number;
219
+ c_func_count: number;
220
+ array_count: number;
221
+ fast_array_count: number;
222
+ fast_array_elements: number;
223
+ binary_object_count: number;
224
+ binary_object_size: number;
225
+ } {
226
+ const handle = this.context.runtime.computeMemoryUsage();
227
+ try {
228
+ return this.context.dump(handle);
229
+ } finally {
230
+ handle.dispose();
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Get a human-readable description of memory usage in this runtime.
236
+ *
237
+ * @returns A formatted string showing memory statistics
238
+ *
239
+ * @example
240
+ * ```js
241
+ * console.log(arena.dumpMemoryUsage());
242
+ * // Output:
243
+ * // QuickJS memory usage:
244
+ * // malloc_limit: 4294967295
245
+ * // memory_used_size: 67078
246
+ * // ...
247
+ * ```
248
+ */
249
+ dumpMemoryUsage(): string {
250
+ return this.context.runtime.dumpMemoryUsage();
251
+ }
252
+
113
253
  /**
114
254
  * Expose objects as global objects in the VM.
115
255
  *
@@ -153,7 +153,7 @@ test("class", async () => {
153
153
  }
154
154
  }
155
155
 
156
- expect(A.name).toBe("_A"); // class A's name is _A in vitest
156
+ expect(A.name).toBe("A");
157
157
  const handle = marshal(A);
158
158
  if (!map) throw new Error("map is undefined");
159
159
 
@@ -167,7 +167,7 @@ test("class", async () => {
167
167
 
168
168
  expect(ctx.typeof(handle)).toBe("function");
169
169
  expect(ctx.dump(ctx.getProp(handle, "length"))).toBe(1);
170
- expect(ctx.dump(ctx.getProp(handle, "name"))).toBe("_A");
170
+ expect(ctx.dump(ctx.getProp(handle, "name"))).toBe("A");
171
171
  const staticA = ctx.getProp(handle, "a");
172
172
  expect(instanceOf(ctx, staticA, handle)).toBe(true);
173
173
  expect(ctx.dump(ctx.getProp(staticA, "a"))).toBe(100);
package/src/vmmap.ts CHANGED
@@ -12,7 +12,7 @@ export default class VMMap {
12
12
  _mapSet: QuickJSHandle;
13
13
  _mapDelete: QuickJSHandle;
14
14
  _mapClear: QuickJSHandle;
15
- _counter = 0;
15
+ _counter = Number.MIN_SAFE_INTEGER;
16
16
 
17
17
  constructor(ctx: QuickJSContext) {
18
18
  this.ctx = ctx;
package/src/wrapper.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { QuickJSHandle, QuickJSContext } from "quickjs-emscripten";
2
2
 
3
3
  import { isObject } from "./util";
4
- import { call, isHandleObject, consumeAll, mayConsumeAll } from "./vmutil";
4
+ import { call, isHandleObject, mayConsumeAll } from "./vmutil";
5
5
 
6
6
  export type SyncMode = "both" | "vm" | "host";
7
7
 
@@ -86,32 +86,45 @@ export function wrapHandle(
86
86
 
87
87
  if (isHandleWrapped(ctx, handle, proxyKeySymbolHandle)) return [handle, false];
88
88
 
89
- return consumeAll(
90
- [
91
- ctx.newFunction("getSyncMode", h => {
92
- const res = syncMode?.(unmarshal(h));
93
- if (typeof res === "string") return ctx.newString(res);
94
- return ctx.undefined;
95
- }),
96
- ctx.newFunction("setter", (h, keyHandle, valueHandle) => {
97
- const target = unmarshal(h);
98
- if (!target) return;
99
- const key = unmarshal(keyHandle);
100
- if (key === "__proto__") return; // for security
101
- const value = unmarshal(valueHandle);
102
- unwrap(target, proxyKeySymbol)[key] = value;
103
- }),
104
- ctx.newFunction("deleter", (h, keyHandle) => {
105
- const target = unmarshal(h);
106
- if (!target) return;
107
- const key = unmarshal(keyHandle);
108
- delete unwrap(target, proxyKeySymbol)[key];
109
- }),
110
- ],
111
- args => [
89
+ const getSyncMode = (h: QuickJSHandle) => {
90
+ const res = syncMode?.(unmarshal(h));
91
+ if (typeof res === "string") return ctx.newString(res);
92
+ return ctx.undefined;
93
+ };
94
+
95
+ const setter = (h: QuickJSHandle, keyHandle: QuickJSHandle, valueHandle: QuickJSHandle) => {
96
+ const target = unmarshal(h);
97
+ if (!target) return;
98
+ const key = unmarshal(keyHandle);
99
+ if (key === "__proto__") return; // for security
100
+ const value = unmarshal(valueHandle);
101
+ unwrap(target, proxyKeySymbol)[key] = value;
102
+ };
103
+
104
+ const deleter = (h: QuickJSHandle, keyHandle: QuickJSHandle) => {
105
+ const target = unmarshal(h);
106
+ if (!target) return;
107
+ const key = unmarshal(keyHandle);
108
+ delete unwrap(target, proxyKeySymbol)[key];
109
+ };
110
+
111
+ return ctx
112
+ .newFunction("proxyFuncs", (t, ...args) => {
113
+ const name = ctx.getNumber(t);
114
+ switch (name) {
115
+ case 1:
116
+ return getSyncMode(args[0]);
117
+ case 2:
118
+ return setter(args[0], args[1], args[2]);
119
+ case 3:
120
+ return deleter(args[0], args[1]);
121
+ }
122
+ return ctx.undefined;
123
+ })
124
+ .consume(proxyFuncs => [
112
125
  call(
113
126
  ctx,
114
- `(target, sym, getSyncMode, setter, deleter) => {
127
+ `(target, sym, proxyFuncs) => {
115
128
  const rec = new Proxy(target, {
116
129
  get(obj, key, receiver) {
117
130
  return key === sym ? obj : Reflect.get(obj, key, receiver)
@@ -120,19 +133,19 @@ export function wrapHandle(
120
133
  const v = typeof value === "object" && value !== null || typeof value === "function"
121
134
  ? value[sym] ?? value
122
135
  : value;
123
- const sync = getSyncMode(receiver) ?? "vm";
136
+ const sync = proxyFuncs(1, receiver) ?? "vm";
124
137
  if (sync === "host" || Reflect.set(obj, key, v, receiver)) {
125
138
  if (sync !== "vm") {
126
- setter(receiver, key, v);
139
+ proxyFuncs(2, receiver, key, v);
127
140
  }
128
141
  }
129
142
  return true;
130
143
  },
131
144
  deleteProperty(obj, key) {
132
- const sync = getSyncMode(rec) ?? "vm";
145
+ const sync = proxyFuncs(1, rec) ?? "vm";
133
146
  if (sync === "host" || Reflect.deleteProperty(obj, key)) {
134
147
  if (sync !== "vm") {
135
- deleter(rec, key);
148
+ proxyFuncs(3, rec, key);
136
149
  }
137
150
  }
138
151
  return true;
@@ -143,11 +156,10 @@ export function wrapHandle(
143
156
  undefined,
144
157
  handle,
145
158
  proxyKeySymbolHandle,
146
- ...args,
159
+ proxyFuncs,
147
160
  ) as Wrapped<QuickJSHandle>,
148
161
  true,
149
- ],
150
- );
162
+ ]);
151
163
  }
152
164
 
153
165
  export function unwrap<T>(obj: T, key: string | symbol): T {