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