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.
- package/LICENSE +2 -2
- package/README.md +71 -44
- package/dist/index.d.ts +116 -23
- package/dist/quickjs-emscripten-sync.mjs +842 -0
- package/dist/quickjs-emscripten-sync.umd.js +64 -2
- package/package.json +22 -53
- package/src/default.ts +2 -2
- package/src/index.test.ts +223 -59
- package/src/index.ts +72 -66
- package/src/marshal/custom.test.ts +50 -0
- package/src/marshal/custom.ts +36 -0
- package/src/marshal/function.test.ts +64 -75
- package/src/marshal/function.ts +15 -18
- package/src/marshal/index.test.ts +122 -77
- package/src/marshal/index.ts +19 -15
- package/src/marshal/json.test.ts +37 -41
- package/src/marshal/json.ts +5 -7
- package/src/marshal/object.test.ts +59 -84
- package/src/marshal/object.ts +8 -15
- package/src/marshal/primitive.test.ts +18 -21
- package/src/marshal/primitive.ts +11 -10
- package/src/marshal/promise.test.ts +85 -0
- package/src/marshal/promise.ts +19 -0
- package/src/marshal/properties.test.ts +24 -27
- package/src/marshal/properties.ts +15 -17
- package/src/unmarshal/custom.test.ts +50 -0
- package/src/unmarshal/custom.ts +31 -0
- package/src/unmarshal/function.test.ts +63 -86
- package/src/unmarshal/function.ts +13 -22
- package/src/unmarshal/index.test.ts +114 -109
- package/src/unmarshal/index.ts +15 -14
- package/src/unmarshal/object.test.ts +42 -46
- package/src/unmarshal/object.ts +16 -20
- package/src/unmarshal/primitive.test.ts +17 -15
- package/src/unmarshal/primitive.ts +11 -18
- package/src/unmarshal/promise.test.ts +44 -0
- package/src/unmarshal/promise.ts +31 -0
- package/src/unmarshal/properties.test.ts +11 -9
- package/src/unmarshal/properties.ts +44 -45
- package/src/util.test.ts +14 -2
- package/src/util.ts +18 -8
- package/src/vmmap.test.ts +88 -82
- package/src/vmmap.ts +30 -44
- package/src/vmutil.test.ts +100 -75
- package/src/vmutil.ts +51 -34
- package/src/wrapper.test.ts +129 -167
- package/src/wrapper.ts +59 -62
- package/dist/default.d.ts +0 -4
- package/dist/marshal/function.d.ts +0 -2
- package/dist/marshal/index.d.ts +0 -11
- package/dist/marshal/json.d.ts +0 -2
- 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 +0 -2
- package/dist/quickjs-emscripten-sync.cjs.map +0 -1
- package/dist/quickjs-emscripten-sync.modern.js +0 -2
- package/dist/quickjs-emscripten-sync.modern.js.map +0 -1
- package/dist/quickjs-emscripten-sync.module.js +0 -2
- package/dist/quickjs-emscripten-sync.module.js.map +0 -1
- package/dist/quickjs-emscripten-sync.umd.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 -11
- package/dist/wrapper.d.ts +0 -11
- package/src/marshal/symbol.test.ts +0 -22
- package/src/marshal/symbol.ts +0 -20
- package/src/unmarshal/symbol.test.ts +0 -23
- package/src/unmarshal/symbol.ts +0 -12
package/src/index.ts
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
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
|
-
import
|
|
9
|
+
import { defaultRegisteredObjects } from "./default";
|
|
8
10
|
import marshal from "./marshal";
|
|
9
11
|
import unmarshal from "./unmarshal";
|
|
10
|
-
import { wrap, wrapHandle, unwrap, unwrapHandle, Wrapped } from "./wrapper";
|
|
11
12
|
import { complexity, isES2015Class, isObject, walkObject } from "./util";
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
isHandleObject,
|
|
16
|
-
json,
|
|
17
|
-
consumeAll,
|
|
18
|
-
mayConsume,
|
|
19
|
-
} from "./vmutil";
|
|
20
|
-
import { defaultRegisteredObjects } from "./default";
|
|
13
|
+
import VMMap from "./vmmap";
|
|
14
|
+
import { call, eq, isHandleObject, json, consumeAll, mayConsume, handleFrom } from "./vmutil";
|
|
15
|
+
import { wrap, wrapHandle, unwrap, unwrapHandle, Wrapped } from "./wrapper";
|
|
21
16
|
|
|
22
17
|
export {
|
|
23
18
|
VMMap,
|
|
@@ -38,18 +33,28 @@ export {
|
|
|
38
33
|
export type Options = {
|
|
39
34
|
/** 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. */
|
|
40
35
|
isMarshalable?: boolean | "json" | ((target: any) => boolean | "json");
|
|
41
|
-
/**
|
|
36
|
+
/** Pre-registered pairs 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`.
|
|
42
37
|
*
|
|
43
38
|
* 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.
|
|
44
39
|
*/
|
|
45
40
|
registeredObjects?: Iterable<[any, QuickJSHandle | string]>;
|
|
41
|
+
/** Register functions to convert an object to a QuickJS handle. */
|
|
42
|
+
customMarshaller?: Iterable<(target: unknown, ctx: QuickJSContext) => QuickJSHandle | undefined>;
|
|
43
|
+
/** Register functions to convert a QuickJS handle to an object. */
|
|
44
|
+
customUnmarshaller?: Iterable<(target: QuickJSHandle, ctx: QuickJSContext) => any>;
|
|
45
|
+
/** A callback that returns a boolean value that determines whether an object is wrappable by proxies. If returns false, note that the object cannot be synchronized between the host and the QuickJS even if arena.sync is used. */
|
|
46
|
+
isWrappable?: (target: any) => boolean;
|
|
47
|
+
/** A callback that returns a boolean value that determines whether an QuickJS handle is wrappable by proxies. If returns false, note that the handle cannot be synchronized between the host and the QuickJS even if arena.sync is used. */
|
|
48
|
+
isHandleWrappable?: (handle: QuickJSHandle, ctx: QuickJSContext) => boolean;
|
|
49
|
+
/** Compatibility with quickjs-emscripten prior to v0.15. Inject code for compatibility into context at Arena class initialization time. */
|
|
50
|
+
compat?: boolean;
|
|
46
51
|
};
|
|
47
52
|
|
|
48
53
|
/**
|
|
49
54
|
* The Arena class manages all generated handles at once by quickjs-emscripten and automatically converts objects between the host and the QuickJS VM.
|
|
50
55
|
*/
|
|
51
56
|
export class Arena {
|
|
52
|
-
|
|
57
|
+
context: QuickJSContext;
|
|
53
58
|
_map: VMMap;
|
|
54
59
|
_registeredMap: VMMap;
|
|
55
60
|
_registeredMapDispose: Set<any> = new Set();
|
|
@@ -59,13 +64,21 @@ export class Arena {
|
|
|
59
64
|
_symbolHandle: QuickJSHandle;
|
|
60
65
|
_options?: Options;
|
|
61
66
|
|
|
62
|
-
/** Constructs a new Arena instance. It requires a quickjs-emscripten
|
|
63
|
-
constructor(
|
|
64
|
-
|
|
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;
|
|
65
78
|
this._options = options;
|
|
66
|
-
this._symbolHandle =
|
|
67
|
-
this._map = new VMMap(
|
|
68
|
-
this._registeredMap = new VMMap(
|
|
79
|
+
this._symbolHandle = ctx.unwrapResult(ctx.evalCode(`Symbol()`));
|
|
80
|
+
this._map = new VMMap(ctx);
|
|
81
|
+
this._registeredMap = new VMMap(ctx);
|
|
69
82
|
this.registerAll(options?.registeredObjects ?? defaultRegisteredObjects);
|
|
70
83
|
}
|
|
71
84
|
|
|
@@ -82,7 +95,7 @@ export class Arena {
|
|
|
82
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.
|
|
83
96
|
*/
|
|
84
97
|
evalCode<T = any>(code: string): T {
|
|
85
|
-
const handle = this.
|
|
98
|
+
const handle = this.context.evalCode(code);
|
|
86
99
|
return this._unwrapResultAndUnmarshal(handle);
|
|
87
100
|
}
|
|
88
101
|
|
|
@@ -90,7 +103,7 @@ export class Arena {
|
|
|
90
103
|
* Almost same as `vm.executePendingJobs()`, but it converts and re-throws error objects when an error is thrown during evaluation.
|
|
91
104
|
*/
|
|
92
105
|
executePendingJobs(maxJobsToExecute?: number): number {
|
|
93
|
-
const result = this.
|
|
106
|
+
const result = this.context.runtime.executePendingJobs(maxJobsToExecute);
|
|
94
107
|
if ("value" in result) {
|
|
95
108
|
return result.value;
|
|
96
109
|
}
|
|
@@ -105,8 +118,8 @@ export class Arena {
|
|
|
105
118
|
*/
|
|
106
119
|
expose(obj: { [k: string]: any }) {
|
|
107
120
|
for (const [key, value] of Object.entries(obj)) {
|
|
108
|
-
mayConsume(this._marshal(value),
|
|
109
|
-
this.
|
|
121
|
+
mayConsume(this._marshal(value), handle => {
|
|
122
|
+
this.context.setProp(this.context.global, key, handle);
|
|
110
123
|
});
|
|
111
124
|
}
|
|
112
125
|
}
|
|
@@ -119,8 +132,9 @@ export class Arena {
|
|
|
119
132
|
sync<T>(target: T): T {
|
|
120
133
|
const wrapped = this._wrap(target);
|
|
121
134
|
if (typeof wrapped === "undefined") return target;
|
|
122
|
-
walkObject(wrapped,
|
|
123
|
-
this.
|
|
135
|
+
walkObject(wrapped, v => {
|
|
136
|
+
const u = this._unwrap(v);
|
|
137
|
+
this._sync.add(u);
|
|
124
138
|
});
|
|
125
139
|
return wrapped;
|
|
126
140
|
}
|
|
@@ -134,9 +148,9 @@ export class Arena {
|
|
|
134
148
|
if (this._registeredMap.has(target)) return;
|
|
135
149
|
const handle =
|
|
136
150
|
typeof handleOrCode === "string"
|
|
137
|
-
? this._unwrapResult(this.
|
|
151
|
+
? this._unwrapResult(this.context.evalCode(handleOrCode))
|
|
138
152
|
: handleOrCode;
|
|
139
|
-
if (eq(this.
|
|
153
|
+
if (eq(this.context, handle, this.context.undefined)) return;
|
|
140
154
|
if (typeof handleOrCode === "string") {
|
|
141
155
|
this._registeredMapDispose.add(target);
|
|
142
156
|
}
|
|
@@ -156,10 +170,7 @@ export class Arena {
|
|
|
156
170
|
* Unregister a pair of objects that were registered with `registeredObjects` option and `register` method.
|
|
157
171
|
*/
|
|
158
172
|
unregister(target: any, dispose?: boolean) {
|
|
159
|
-
this._registeredMap.delete(
|
|
160
|
-
target,
|
|
161
|
-
this._registeredMapDispose.has(target) || dispose
|
|
162
|
-
);
|
|
173
|
+
this._registeredMap.delete(target, this._registeredMapDispose.has(target) || dispose);
|
|
163
174
|
this._registeredMapDispose.delete(target);
|
|
164
175
|
}
|
|
165
176
|
|
|
@@ -174,7 +185,8 @@ export class Arena {
|
|
|
174
185
|
|
|
175
186
|
startSync(target: any) {
|
|
176
187
|
if (!isObject(target)) return;
|
|
177
|
-
this.
|
|
188
|
+
const u = this._unwrap(target);
|
|
189
|
+
this._sync.add(u);
|
|
178
190
|
}
|
|
179
191
|
|
|
180
192
|
endSync(target: any) {
|
|
@@ -188,13 +200,9 @@ export class Arena {
|
|
|
188
200
|
throw this._unwrapIfNotSynced(result.error.consume(this._unmarshal));
|
|
189
201
|
}
|
|
190
202
|
|
|
191
|
-
_unwrapResultAndUnmarshal(
|
|
192
|
-
result: VmCallResult<QuickJSHandle> | undefined
|
|
193
|
-
): any {
|
|
203
|
+
_unwrapResultAndUnmarshal(result: VmCallResult<QuickJSHandle> | undefined): any {
|
|
194
204
|
if (!result) return;
|
|
195
|
-
return this._unwrapIfNotSynced(
|
|
196
|
-
this._unwrapResult(result).consume(this._unmarshal)
|
|
197
|
-
);
|
|
205
|
+
return this._unwrapIfNotSynced(this._unwrapResult(result).consume(this._unmarshal));
|
|
198
206
|
}
|
|
199
207
|
|
|
200
208
|
_isMarshalable = (t: unknown): boolean | "json" => {
|
|
@@ -214,18 +222,14 @@ export class Arena {
|
|
|
214
222
|
|
|
215
223
|
_marshalPre = (
|
|
216
224
|
t: unknown,
|
|
217
|
-
h: QuickJSHandle,
|
|
218
|
-
mode: true | "json" | undefined
|
|
225
|
+
h: QuickJSHandle | QuickJSDeferredPromise,
|
|
226
|
+
mode: true | "json" | undefined,
|
|
219
227
|
): Wrapped<QuickJSHandle> | undefined => {
|
|
220
228
|
if (mode === "json") return;
|
|
221
|
-
return this._register(t, h, this._map)?.[1];
|
|
229
|
+
return this._register(t, handleFrom(h), this._map)?.[1];
|
|
222
230
|
};
|
|
223
231
|
|
|
224
|
-
_marshalPreApply = (
|
|
225
|
-
target: Function,
|
|
226
|
-
that: unknown,
|
|
227
|
-
args: unknown[]
|
|
228
|
-
): void => {
|
|
232
|
+
_marshalPreApply = (target: Function, that: unknown, args: unknown[]): void => {
|
|
229
233
|
const unwrapped = isObject(that) ? this._unwrap(that) : undefined;
|
|
230
234
|
// override sync mode of this object while calling the function
|
|
231
235
|
if (unwrapped) this._temporalSync.add(unwrapped);
|
|
@@ -244,12 +248,13 @@ export class Arena {
|
|
|
244
248
|
}
|
|
245
249
|
|
|
246
250
|
const handle = marshal(this._wrap(target) ?? target, {
|
|
247
|
-
|
|
251
|
+
ctx: this.context,
|
|
248
252
|
unmarshal: this._unmarshal,
|
|
249
253
|
isMarshalable: this._isMarshalable,
|
|
250
254
|
find: this._marshalFind,
|
|
251
255
|
pre: this._marshalPre,
|
|
252
256
|
preApply: this._marshalPreApply,
|
|
257
|
+
custom: this._options?.customMarshaller,
|
|
253
258
|
});
|
|
254
259
|
|
|
255
260
|
return [handle, !this._map.hasHandle(handle)];
|
|
@@ -271,10 +276,11 @@ export class Arena {
|
|
|
271
276
|
|
|
272
277
|
const [wrappedHandle] = this._wrapHandle(handle);
|
|
273
278
|
return unmarshal(wrappedHandle ?? handle, {
|
|
274
|
-
|
|
279
|
+
ctx: this.context,
|
|
275
280
|
marshal: this._marshal,
|
|
276
281
|
find: this._unmarshalFind,
|
|
277
282
|
pre: this._preUnmarshal,
|
|
283
|
+
custom: this._options?.customUnmarshaller,
|
|
278
284
|
});
|
|
279
285
|
};
|
|
280
286
|
|
|
@@ -282,15 +288,17 @@ export class Arena {
|
|
|
282
288
|
t: any,
|
|
283
289
|
h: QuickJSHandle,
|
|
284
290
|
map: VMMap = this._map,
|
|
285
|
-
sync?: boolean
|
|
291
|
+
sync?: boolean,
|
|
286
292
|
): [Wrapped<any>, Wrapped<QuickJSHandle>] | undefined {
|
|
287
293
|
if (this._registeredMap.has(t) || this._registeredMap.hasHandle(h)) {
|
|
288
294
|
return;
|
|
289
295
|
}
|
|
290
296
|
|
|
291
|
-
|
|
297
|
+
let wrappedT = this._wrap(t);
|
|
292
298
|
const [wrappedH] = this._wrapHandle(h);
|
|
293
|
-
|
|
299
|
+
const isPromise = t instanceof Promise;
|
|
300
|
+
if (!wrappedH || (!wrappedT && !isPromise)) return; // t or h is not an object
|
|
301
|
+
if (isPromise) wrappedT = t;
|
|
294
302
|
|
|
295
303
|
const unwrappedT = this._unwrap(t);
|
|
296
304
|
const [unwrappedH, unwrapped] = this._unwrapHandle(h);
|
|
@@ -309,19 +317,18 @@ export class Arena {
|
|
|
309
317
|
|
|
310
318
|
_syncMode = (obj: any): "both" | undefined => {
|
|
311
319
|
const obj2 = this._unwrap(obj);
|
|
312
|
-
return this._sync.has(obj2) || this._temporalSync.has(obj2)
|
|
313
|
-
? "both"
|
|
314
|
-
: undefined;
|
|
320
|
+
return this._sync.has(obj2) || this._temporalSync.has(obj2) ? "both" : undefined;
|
|
315
321
|
};
|
|
316
322
|
|
|
317
323
|
_wrap<T>(target: T): Wrapped<T> | undefined {
|
|
318
324
|
return wrap(
|
|
319
|
-
this.
|
|
325
|
+
this.context,
|
|
320
326
|
target,
|
|
321
327
|
this._symbol,
|
|
322
328
|
this._symbolHandle,
|
|
323
329
|
this._marshal,
|
|
324
|
-
this._syncMode
|
|
330
|
+
this._syncMode,
|
|
331
|
+
this._options?.isWrappable,
|
|
325
332
|
);
|
|
326
333
|
}
|
|
327
334
|
|
|
@@ -331,23 +338,22 @@ export class Arena {
|
|
|
331
338
|
|
|
332
339
|
_unwrapIfNotSynced = <T>(target: T): T => {
|
|
333
340
|
const unwrapped = this._unwrap(target);
|
|
334
|
-
return this._sync.has(unwrapped) ?
|
|
341
|
+
return unwrapped instanceof Promise || !this._sync.has(unwrapped) ? unwrapped : target;
|
|
335
342
|
};
|
|
336
343
|
|
|
337
|
-
_wrapHandle(
|
|
338
|
-
handle: QuickJSHandle
|
|
339
|
-
): [Wrapped<QuickJSHandle> | undefined, boolean] {
|
|
344
|
+
_wrapHandle(handle: QuickJSHandle): [Wrapped<QuickJSHandle> | undefined, boolean] {
|
|
340
345
|
return wrapHandle(
|
|
341
|
-
this.
|
|
346
|
+
this.context,
|
|
342
347
|
handle,
|
|
343
348
|
this._symbol,
|
|
344
349
|
this._symbolHandle,
|
|
345
350
|
this._unmarshal,
|
|
346
|
-
this._syncMode
|
|
351
|
+
this._syncMode,
|
|
352
|
+
this._options?.isHandleWrappable,
|
|
347
353
|
);
|
|
348
354
|
}
|
|
349
355
|
|
|
350
356
|
_unwrapHandle(target: QuickJSHandle): [QuickJSHandle, boolean] {
|
|
351
|
-
return unwrapHandle(this.
|
|
357
|
+
return unwrapHandle(this.context, target, this._symbolHandle);
|
|
352
358
|
}
|
|
353
359
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getQuickJS } from "quickjs-emscripten";
|
|
2
|
+
import { expect, test, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { call } from "../vmutil";
|
|
5
|
+
|
|
6
|
+
import marshalCustom, { defaultCustom } from "./custom";
|
|
7
|
+
|
|
8
|
+
test("symbol", async () => {
|
|
9
|
+
const ctx = (await getQuickJS()).newContext();
|
|
10
|
+
const pre = vi.fn();
|
|
11
|
+
const sym = Symbol("foobar");
|
|
12
|
+
|
|
13
|
+
const marshal = (t: unknown) => marshalCustom(ctx, t, pre, defaultCustom);
|
|
14
|
+
|
|
15
|
+
expect(marshal({})).toBe(undefined);
|
|
16
|
+
expect(pre).toBeCalledTimes(0);
|
|
17
|
+
|
|
18
|
+
const handle = marshal(sym);
|
|
19
|
+
if (!handle) throw new Error("handle is undefined");
|
|
20
|
+
expect(ctx.typeof(handle)).toBe("symbol");
|
|
21
|
+
expect(ctx.getString(ctx.getProp(handle, "description"))).toBe("foobar");
|
|
22
|
+
expect(pre).toReturnTimes(1);
|
|
23
|
+
expect(pre.mock.calls[0][0]).toBe(sym);
|
|
24
|
+
expect(pre.mock.calls[0][1] === handle).toBe(true);
|
|
25
|
+
|
|
26
|
+
handle.dispose();
|
|
27
|
+
ctx.dispose();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("date", async () => {
|
|
31
|
+
const ctx = (await getQuickJS()).newContext();
|
|
32
|
+
const pre = vi.fn();
|
|
33
|
+
const date = new Date(2022, 7, 26);
|
|
34
|
+
|
|
35
|
+
const marshal = (t: unknown) => marshalCustom(ctx, t, pre, defaultCustom);
|
|
36
|
+
|
|
37
|
+
expect(marshal({})).toBe(undefined);
|
|
38
|
+
expect(pre).toBeCalledTimes(0);
|
|
39
|
+
|
|
40
|
+
const handle = marshal(date);
|
|
41
|
+
if (!handle) throw new Error("handle is undefined");
|
|
42
|
+
expect(ctx.dump(call(ctx, "d => d instanceof Date", undefined, handle))).toBe(true);
|
|
43
|
+
expect(ctx.dump(call(ctx, "d => d.getTime()", undefined, handle))).toBe(date.getTime());
|
|
44
|
+
expect(pre).toReturnTimes(1);
|
|
45
|
+
expect(pre.mock.calls[0][0]).toBe(date);
|
|
46
|
+
expect(pre.mock.calls[0][1] === handle).toBe(true);
|
|
47
|
+
|
|
48
|
+
handle.dispose();
|
|
49
|
+
ctx.dispose();
|
|
50
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
+
|
|
3
|
+
import { call } from "../vmutil";
|
|
4
|
+
|
|
5
|
+
export default function marshalCustom(
|
|
6
|
+
ctx: QuickJSContext,
|
|
7
|
+
target: unknown,
|
|
8
|
+
preMarshal: (target: unknown, handle: QuickJSHandle) => QuickJSHandle | undefined,
|
|
9
|
+
custom: Iterable<(target: unknown, ctx: QuickJSContext) => QuickJSHandle | undefined>,
|
|
10
|
+
): QuickJSHandle | undefined {
|
|
11
|
+
let handle: QuickJSHandle | undefined;
|
|
12
|
+
for (const c of custom) {
|
|
13
|
+
handle = c(target, ctx);
|
|
14
|
+
if (handle) break;
|
|
15
|
+
}
|
|
16
|
+
return handle ? preMarshal(target, handle) ?? handle : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function symbol(target: unknown, ctx: QuickJSContext): QuickJSHandle | undefined {
|
|
20
|
+
if (typeof target !== "symbol") return;
|
|
21
|
+
const handle = call(
|
|
22
|
+
ctx,
|
|
23
|
+
"d => Symbol(d)",
|
|
24
|
+
undefined,
|
|
25
|
+
target.description ? ctx.newString(target.description) : ctx.undefined,
|
|
26
|
+
);
|
|
27
|
+
return handle;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function date(target: unknown, ctx: QuickJSContext): QuickJSHandle | undefined {
|
|
31
|
+
if (!(target instanceof Date)) return;
|
|
32
|
+
const handle = call(ctx, "d => new Date(d)", undefined, ctx.newNumber(target.getTime()));
|
|
33
|
+
return handle;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const defaultCustom = [symbol, date];
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
import { getQuickJS, QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
+
import { expect, test, vi } from "vitest";
|
|
3
|
+
|
|
2
4
|
import { json, eq, call } from "../vmutil";
|
|
5
|
+
|
|
3
6
|
import marshalFunction from "./function";
|
|
4
7
|
|
|
5
8
|
test("normal func", async () => {
|
|
6
|
-
const
|
|
9
|
+
const ctx = (await getQuickJS()).newContext();
|
|
7
10
|
|
|
8
|
-
const marshal =
|
|
9
|
-
const unmarshal =
|
|
10
|
-
|
|
11
|
-
);
|
|
12
|
-
const preMarshal = jest.fn((_, a) => a);
|
|
13
|
-
const innerfn = jest.fn((..._args: any[]) => "hoge");
|
|
11
|
+
const marshal = vi.fn(v => json(ctx, v));
|
|
12
|
+
const unmarshal = vi.fn(v => (eq(ctx, v, ctx.global) ? undefined : ctx.dump(v)));
|
|
13
|
+
const preMarshal = vi.fn((_, a) => a);
|
|
14
|
+
const innerfn = vi.fn((..._args: any[]) => "hoge");
|
|
14
15
|
const fn = (...args: any[]) => innerfn(...args);
|
|
15
16
|
|
|
16
|
-
const handle = marshalFunction(
|
|
17
|
+
const handle = marshalFunction(ctx, fn, marshal, unmarshal, preMarshal);
|
|
17
18
|
if (!handle) throw new Error("handle is undefined");
|
|
18
19
|
|
|
19
20
|
expect(marshal.mock.calls).toEqual([["length"], [0], ["name"], ["fn"]]); // fn.length, fn.name
|
|
20
21
|
expect(preMarshal.mock.calls).toEqual([[fn, handle]]); // fn.length, fn.name
|
|
21
|
-
expect(
|
|
22
|
-
expect(
|
|
23
|
-
expect(
|
|
22
|
+
expect(ctx.typeof(handle)).toBe("function");
|
|
23
|
+
expect(ctx.dump(ctx.getProp(handle, "length"))).toBe(0);
|
|
24
|
+
expect(ctx.dump(ctx.getProp(handle, "name"))).toBe("fn");
|
|
24
25
|
|
|
25
|
-
const result =
|
|
26
|
-
|
|
26
|
+
const result = ctx.unwrapResult(
|
|
27
|
+
ctx.callFunction(handle, ctx.undefined, ctx.newNumber(1), ctx.true),
|
|
27
28
|
);
|
|
28
29
|
|
|
29
|
-
expect(
|
|
30
|
+
expect(ctx.dump(result)).toBe("hoge");
|
|
30
31
|
expect(innerfn).toBeCalledWith(1, true);
|
|
31
32
|
expect(marshal).toHaveBeenLastCalledWith("hoge");
|
|
32
33
|
expect(unmarshal).toBeCalledTimes(3);
|
|
@@ -35,48 +36,48 @@ test("normal func", async () => {
|
|
|
35
36
|
expect(unmarshal.mock.results[2].value).toBe(true);
|
|
36
37
|
|
|
37
38
|
handle.dispose();
|
|
38
|
-
|
|
39
|
+
ctx.dispose();
|
|
39
40
|
});
|
|
40
41
|
|
|
41
42
|
test("func which has properties", async () => {
|
|
42
|
-
const
|
|
43
|
-
const marshal =
|
|
43
|
+
const ctx = (await getQuickJS()).newContext();
|
|
44
|
+
const marshal = vi.fn(v => json(ctx, v));
|
|
44
45
|
|
|
45
46
|
const fn = () => {};
|
|
46
47
|
fn.hoge = "foo";
|
|
47
48
|
|
|
48
49
|
const handle = marshalFunction(
|
|
49
|
-
|
|
50
|
+
ctx,
|
|
50
51
|
fn,
|
|
51
52
|
marshal,
|
|
52
|
-
|
|
53
|
-
(_, a) => a
|
|
53
|
+
v => ctx.dump(v),
|
|
54
|
+
(_, a) => a,
|
|
54
55
|
);
|
|
55
56
|
if (!handle) throw new Error("handle is undefined");
|
|
56
57
|
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
58
|
+
expect(ctx.typeof(handle)).toBe("function");
|
|
59
|
+
expect(ctx.dump(ctx.getProp(handle, "hoge"))).toBe("foo");
|
|
59
60
|
expect(marshal).toBeCalledWith("foo");
|
|
60
61
|
|
|
61
62
|
handle.dispose();
|
|
62
|
-
|
|
63
|
+
ctx.dispose();
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
test("class", async () => {
|
|
66
|
-
const
|
|
67
|
+
const ctx = (await getQuickJS()).newContext();
|
|
67
68
|
|
|
68
69
|
const disposables: QuickJSHandle[] = [];
|
|
69
70
|
const marshal = (v: any) => {
|
|
70
|
-
if (typeof v === "string") return
|
|
71
|
-
if (typeof v === "number") return
|
|
71
|
+
if (typeof v === "string") return ctx.newString(v);
|
|
72
|
+
if (typeof v === "number") return ctx.newNumber(v);
|
|
72
73
|
if (typeof v === "object") {
|
|
73
|
-
const obj =
|
|
74
|
+
const obj = ctx.newObject();
|
|
74
75
|
disposables.push(obj);
|
|
75
76
|
return obj;
|
|
76
77
|
}
|
|
77
|
-
return
|
|
78
|
+
return ctx.null;
|
|
78
79
|
};
|
|
79
|
-
const unmarshal = (v: QuickJSHandle) =>
|
|
80
|
+
const unmarshal = (v: QuickJSHandle) => ctx.dump(v);
|
|
80
81
|
|
|
81
82
|
class A {
|
|
82
83
|
a: number;
|
|
@@ -86,82 +87,70 @@ test("class", async () => {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
const handle = marshalFunction(
|
|
90
|
+
const handle = marshalFunction(ctx, A, marshal, unmarshal, (_, a) => a);
|
|
90
91
|
if (!handle) throw new Error("handle is undefined");
|
|
91
92
|
|
|
92
|
-
const newA =
|
|
93
|
-
const instance =
|
|
93
|
+
const newA = ctx.unwrapResult(ctx.evalCode(`A => new A(100)`));
|
|
94
|
+
const instance = ctx.unwrapResult(ctx.callFunction(newA, ctx.undefined, handle));
|
|
94
95
|
|
|
95
|
-
expect(
|
|
96
|
-
expect(
|
|
97
|
-
expect(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
).toBe(true);
|
|
102
|
-
expect(vm.dump(vm.getProp(instance, "a"))).toBe(100);
|
|
96
|
+
expect(ctx.dump(ctx.getProp(handle, "name"))).toBe("A");
|
|
97
|
+
expect(ctx.dump(ctx.getProp(handle, "length"))).toBe(1);
|
|
98
|
+
expect(ctx.dump(call(ctx, "(cls, i) => i instanceof cls", undefined, handle, instance))).toBe(
|
|
99
|
+
true,
|
|
100
|
+
);
|
|
101
|
+
expect(ctx.dump(ctx.getProp(instance, "a"))).toBe(100);
|
|
103
102
|
|
|
104
|
-
disposables.forEach(
|
|
103
|
+
disposables.forEach(d => d.dispose());
|
|
105
104
|
instance.dispose();
|
|
106
105
|
newA.dispose();
|
|
107
106
|
handle.dispose();
|
|
108
|
-
|
|
107
|
+
ctx.dispose();
|
|
109
108
|
});
|
|
110
109
|
|
|
111
110
|
test("preApply", async () => {
|
|
112
|
-
const
|
|
111
|
+
const ctx = (await getQuickJS()).newContext();
|
|
113
112
|
|
|
114
113
|
const marshal = (v: any) => {
|
|
115
|
-
if (typeof v === "string") return
|
|
116
|
-
if (typeof v === "number") return
|
|
117
|
-
return
|
|
114
|
+
if (typeof v === "string") return ctx.newString(v);
|
|
115
|
+
if (typeof v === "number") return ctx.newNumber(v);
|
|
116
|
+
return ctx.null;
|
|
118
117
|
};
|
|
119
|
-
const unmarshal = (v: QuickJSHandle) =>
|
|
120
|
-
|
|
121
|
-
const preApply = jest.fn(
|
|
122
|
-
(a: Function, b: any, c: any[]) => a.apply(b, c) + "!"
|
|
123
|
-
);
|
|
118
|
+
const unmarshal = (v: QuickJSHandle) => (ctx.typeof(v) === "object" ? that : ctx.dump(v));
|
|
119
|
+
const preApply = vi.fn((a: Function, b: any, c: any[]) => a.apply(b, c) + "!");
|
|
124
120
|
const that = {};
|
|
125
|
-
const thatHandle =
|
|
121
|
+
const thatHandle = ctx.newObject();
|
|
126
122
|
|
|
127
123
|
const fn = () => "foo";
|
|
128
|
-
const handle = marshalFunction(
|
|
129
|
-
vm,
|
|
130
|
-
fn,
|
|
131
|
-
marshal,
|
|
132
|
-
unmarshal,
|
|
133
|
-
(_, a) => a,
|
|
134
|
-
preApply
|
|
135
|
-
);
|
|
124
|
+
const handle = marshalFunction(ctx, fn, marshal, unmarshal, (_, a) => a, preApply);
|
|
136
125
|
if (!handle) throw new Error("handle is undefined");
|
|
137
126
|
|
|
138
127
|
expect(preApply).toBeCalledTimes(0);
|
|
139
128
|
|
|
140
|
-
const res =
|
|
141
|
-
|
|
129
|
+
const res = ctx.unwrapResult(
|
|
130
|
+
ctx.callFunction(handle, thatHandle, ctx.newNumber(100), ctx.newString("hoge")),
|
|
142
131
|
);
|
|
143
132
|
|
|
144
133
|
expect(preApply).toBeCalledTimes(1);
|
|
145
134
|
expect(preApply).toBeCalledWith(fn, that, [100, "hoge"]);
|
|
146
|
-
expect(
|
|
135
|
+
expect(ctx.dump(res)).toBe("foo!");
|
|
147
136
|
|
|
148
137
|
thatHandle.dispose();
|
|
149
138
|
handle.dispose();
|
|
150
|
-
|
|
139
|
+
ctx.dispose();
|
|
151
140
|
});
|
|
152
141
|
|
|
153
142
|
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(
|
|
143
|
+
const ctx = (await getQuickJS()).newContext();
|
|
144
|
+
const f = vi.fn();
|
|
145
|
+
|
|
146
|
+
expect(marshalFunction(ctx, undefined, f, f, f)).toBe(undefined);
|
|
147
|
+
expect(marshalFunction(ctx, null, f, f, f)).toBe(undefined);
|
|
148
|
+
expect(marshalFunction(ctx, false, f, f, f)).toBe(undefined);
|
|
149
|
+
expect(marshalFunction(ctx, true, f, f, f)).toBe(undefined);
|
|
150
|
+
expect(marshalFunction(ctx, 1, f, f, f)).toBe(undefined);
|
|
151
|
+
expect(marshalFunction(ctx, [1], f, f, f)).toBe(undefined);
|
|
152
|
+
expect(marshalFunction(ctx, { a: 1 }, f, f, f)).toBe(undefined);
|
|
164
153
|
expect(f).toBeCalledTimes(0);
|
|
165
154
|
|
|
166
|
-
|
|
155
|
+
ctx.dispose();
|
|
167
156
|
});
|