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.
- package/LICENSE +2 -2
- package/README.md +59 -49
- package/dist/index.d.ts +121 -26
- package/dist/quickjs-emscripten-sync.es.js +962 -0
- package/dist/quickjs-emscripten-sync.umd.js +64 -0
- package/package.json +28 -40
- package/src/default.ts +2 -2
- package/src/index.test.ts +371 -69
- package/src/index.ts +135 -72
- package/src/marshal/function.test.ts +70 -61
- package/src/marshal/function.ts +10 -9
- package/src/marshal/index.test.ts +135 -62
- package/src/marshal/index.ts +33 -16
- package/src/marshal/json.test.ts +94 -0
- package/src/marshal/json.ts +16 -0
- package/src/marshal/object.test.ts +65 -58
- package/src/marshal/object.ts +6 -5
- package/src/marshal/primitive.test.ts +23 -17
- package/src/marshal/primitive.ts +10 -9
- package/src/marshal/promise.test.ts +86 -0
- package/src/marshal/promise.ts +26 -0
- package/src/marshal/properties.test.ts +23 -19
- package/src/marshal/properties.ts +11 -10
- package/src/marshal/symbol.test.ts +9 -7
- package/src/marshal/symbol.ts +5 -4
- package/src/unmarshal/function.test.ts +70 -61
- package/src/unmarshal/function.ts +39 -30
- package/src/unmarshal/index.test.ts +101 -100
- package/src/unmarshal/index.ts +13 -9
- package/src/unmarshal/object.test.ts +45 -41
- package/src/unmarshal/object.ts +14 -13
- package/src/unmarshal/primitive.test.ts +20 -15
- package/src/unmarshal/primitive.ts +10 -10
- package/src/unmarshal/promise.test.ts +44 -0
- package/src/unmarshal/promise.ts +38 -0
- package/src/unmarshal/properties.test.ts +10 -8
- package/src/unmarshal/properties.ts +47 -42
- package/src/unmarshal/symbol.test.ts +9 -7
- package/src/unmarshal/symbol.ts +4 -4
- package/src/util.test.ts +23 -5
- package/src/util.ts +15 -0
- package/src/vmmap.test.ts +88 -82
- package/src/vmmap.ts +21 -17
- package/src/vmutil.test.ts +159 -53
- package/src/vmutil.ts +91 -19
- package/src/wrapper.test.ts +151 -115
- package/src/wrapper.ts +69 -48
- package/dist/default.d.ts +0 -4
- package/dist/index.js +0 -8
- package/dist/marshal/function.d.ts +0 -2
- package/dist/marshal/index.d.ts +0 -11
- package/dist/marshal/object.d.ts +0 -2
- package/dist/marshal/primitive.d.ts +0 -2
- package/dist/marshal/properties.d.ts +0 -2
- package/dist/marshal/symbol.d.ts +0 -2
- package/dist/quickjs-emscripten-sync.cjs.development.js +0 -1289
- package/dist/quickjs-emscripten-sync.cjs.development.js.map +0 -1
- package/dist/quickjs-emscripten-sync.cjs.production.min.js +0 -2
- package/dist/quickjs-emscripten-sync.cjs.production.min.js.map +0 -1
- package/dist/quickjs-emscripten-sync.esm.js +0 -1272
- package/dist/quickjs-emscripten-sync.esm.js.map +0 -1
- package/dist/unmarshal/function.d.ts +0 -2
- package/dist/unmarshal/index.d.ts +0 -9
- package/dist/unmarshal/object.d.ts +0 -2
- package/dist/unmarshal/primitive.d.ts +0 -2
- package/dist/unmarshal/properties.d.ts +0 -2
- package/dist/unmarshal/symbol.d.ts +0 -2
- package/dist/util.d.ts +0 -7
- package/dist/vmmap.d.ts +0 -35
- package/dist/vmutil.d.ts +0 -7
- package/dist/wrapper.d.ts +0 -11
package/src/index.ts
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
QuickJSDeferredPromise,
|
|
3
|
+
QuickJSHandle,
|
|
4
|
+
QuickJSContext,
|
|
3
5
|
SuccessOrFail,
|
|
4
6
|
VmCallResult,
|
|
5
|
-
} from "quickjs-emscripten
|
|
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 {
|
|
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
|
-
|
|
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
|
|
46
|
-
|
|
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
|
|
57
|
-
constructor(
|
|
58
|
-
|
|
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 =
|
|
61
|
-
this._map = new VMMap(
|
|
62
|
-
this._registeredMap = new VMMap(
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
103
|
-
|
|
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.
|
|
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.
|
|
151
|
+
? this._unwrapResult(this.context.evalCode(handleOrCode))
|
|
131
152
|
: handleOrCode;
|
|
132
|
-
if (eq(this.
|
|
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.
|
|
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(
|
|
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.
|
|
210
|
+
return this._unwrapIfNotSynced(
|
|
211
|
+
this._unwrapResult(result).consume(this._unmarshal)
|
|
212
|
+
);
|
|
189
213
|
}
|
|
190
214
|
|
|
191
|
-
|
|
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
|
-
|
|
202
|
-
unmarshal:
|
|
203
|
-
isMarshalable:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
235
|
-
marshal:
|
|
236
|
-
find:
|
|
237
|
-
pre:
|
|
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
|
-
|
|
306
|
+
let wrappedT = this._wrap(t);
|
|
253
307
|
const [wrappedH] = this._wrapHandle(h);
|
|
254
|
-
|
|
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.
|
|
336
|
+
this.context,
|
|
281
337
|
target,
|
|
282
338
|
this._symbol,
|
|
283
339
|
this._symbolHandle,
|
|
284
|
-
|
|
285
|
-
|
|
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.
|
|
360
|
+
this.context,
|
|
298
361
|
handle,
|
|
299
362
|
this._symbol,
|
|
300
363
|
this._symbolHandle,
|
|
301
|
-
|
|
302
|
-
|
|
364
|
+
this._unmarshal,
|
|
365
|
+
this._syncMode
|
|
303
366
|
);
|
|
304
367
|
}
|
|
305
368
|
|
|
306
369
|
_unwrapHandle(target: QuickJSHandle): [QuickJSHandle, boolean] {
|
|
307
|
-
return unwrapHandle(this.
|
|
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
|
-
|
|
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
|
|
8
|
+
const ctx = (await getQuickJS()).newContext();
|
|
7
9
|
|
|
8
|
-
const marshal =
|
|
9
|
-
const unmarshal =
|
|
10
|
-
eq(
|
|
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 =
|
|
13
|
-
const innerfn =
|
|
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(
|
|
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(
|
|
22
|
-
expect(
|
|
23
|
-
expect(
|
|
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 =
|
|
26
|
-
|
|
27
|
+
const result = ctx.unwrapResult(
|
|
28
|
+
ctx.callFunction(handle, ctx.undefined, ctx.newNumber(1), ctx.true)
|
|
27
29
|
);
|
|
28
30
|
|
|
29
|
-
expect(
|
|
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
|
-
|
|
40
|
+
ctx.dispose();
|
|
39
41
|
});
|
|
40
42
|
|
|
41
43
|
test("func which has properties", async () => {
|
|
42
|
-
const
|
|
43
|
-
const marshal =
|
|
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
|
-
|
|
51
|
+
ctx,
|
|
50
52
|
fn,
|
|
51
53
|
marshal,
|
|
52
|
-
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(
|
|
58
|
-
expect(
|
|
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
|
-
|
|
64
|
+
ctx.dispose();
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
test("class", async () => {
|
|
66
|
-
const
|
|
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
|
|
71
|
-
if (typeof v === "number") return
|
|
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 =
|
|
75
|
+
const obj = ctx.newObject();
|
|
74
76
|
disposables.push(obj);
|
|
75
77
|
return obj;
|
|
76
78
|
}
|
|
77
|
-
return
|
|
79
|
+
return ctx.null;
|
|
78
80
|
};
|
|
79
|
-
const unmarshal = (v: QuickJSHandle) =>
|
|
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(
|
|
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 =
|
|
93
|
-
const instance =
|
|
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(
|
|
96
|
-
expect(
|
|
99
|
+
expect(ctx.dump(ctx.getProp(handle, "name"))).toBe("A");
|
|
100
|
+
expect(ctx.dump(ctx.getProp(handle, "length"))).toBe(1);
|
|
97
101
|
expect(
|
|
98
|
-
|
|
99
|
-
call(
|
|
102
|
+
ctx.dump(
|
|
103
|
+
call(ctx, "(cls, i) => i instanceof cls", undefined, handle, instance)
|
|
100
104
|
)
|
|
101
105
|
).toBe(true);
|
|
102
|
-
expect(
|
|
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
|
-
|
|
112
|
+
ctx.dispose();
|
|
109
113
|
});
|
|
110
114
|
|
|
111
115
|
test("preApply", async () => {
|
|
112
|
-
const
|
|
116
|
+
const ctx = (await getQuickJS()).newContext();
|
|
113
117
|
|
|
114
118
|
const marshal = (v: any) => {
|
|
115
|
-
if (typeof v === "string") return
|
|
116
|
-
if (typeof v === "number") return
|
|
117
|
-
return
|
|
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
|
-
|
|
121
|
-
const preApply =
|
|
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 =
|
|
129
|
+
const thatHandle = ctx.newObject();
|
|
126
130
|
|
|
127
131
|
const fn = () => "foo";
|
|
128
132
|
const handle = marshalFunction(
|
|
129
|
-
|
|
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 =
|
|
141
|
-
|
|
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(
|
|
155
|
+
expect(ctx.dump(res)).toBe("foo!");
|
|
147
156
|
|
|
148
157
|
thatHandle.dispose();
|
|
149
158
|
handle.dispose();
|
|
150
|
-
|
|
159
|
+
ctx.dispose();
|
|
151
160
|
});
|
|
152
161
|
|
|
153
162
|
test("undefined", async () => {
|
|
154
|
-
const
|
|
155
|
-
const f =
|
|
156
|
-
|
|
157
|
-
expect(marshalFunction(
|
|
158
|
-
expect(marshalFunction(
|
|
159
|
-
expect(marshalFunction(
|
|
160
|
-
expect(marshalFunction(
|
|
161
|
-
expect(marshalFunction(
|
|
162
|
-
expect(marshalFunction(
|
|
163
|
-
expect(marshalFunction(
|
|
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
|
-
|
|
175
|
+
ctx.dispose();
|
|
167
176
|
});
|
package/src/marshal/function.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
54
|
+
marshalProperties(ctx, target, raw, marshal);
|
|
54
55
|
|
|
55
56
|
return handle;
|
|
56
57
|
}
|