quickjs-emscripten-sync 1.0.0 → 1.3.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 +303 -0
- package/dist/index.d.ts +129 -29
- package/dist/quickjs-emscripten-sync.es.js +951 -0
- package/dist/quickjs-emscripten-sync.umd.js +64 -0
- package/package.json +28 -40
- package/src/default.ts +6 -2
- package/src/index.test.ts +266 -51
- package/src/index.ts +123 -66
- package/src/marshal/function.test.ts +11 -10
- package/src/marshal/function.ts +3 -3
- package/src/marshal/index.test.ts +75 -10
- package/src/marshal/index.ts +28 -14
- package/src/marshal/json.test.ts +94 -0
- package/src/marshal/json.ts +15 -0
- package/src/marshal/object.test.ts +49 -6
- package/src/marshal/object.ts +2 -2
- package/src/marshal/primitive.test.ts +2 -0
- package/src/marshal/primitive.ts +8 -2
- package/src/marshal/promise.test.ts +86 -0
- package/src/marshal/promise.ts +26 -0
- package/src/marshal/properties.test.ts +7 -5
- package/src/marshal/properties.ts +2 -2
- package/src/marshal/symbol.test.ts +3 -1
- package/src/unmarshal/function.test.ts +28 -21
- package/src/unmarshal/function.ts +31 -23
- package/src/unmarshal/index.test.ts +86 -86
- package/src/unmarshal/index.ts +5 -4
- package/src/unmarshal/object.test.ts +36 -8
- package/src/unmarshal/object.ts +8 -4
- package/src/unmarshal/primitive.test.ts +2 -0
- package/src/unmarshal/primitive.ts +1 -2
- package/src/unmarshal/promise.test.ts +44 -0
- package/src/unmarshal/promise.ts +40 -0
- package/src/unmarshal/properties.test.ts +4 -2
- package/src/unmarshal/properties.ts +11 -9
- package/src/unmarshal/symbol.test.ts +3 -1
- package/src/util.test.ts +22 -5
- package/src/util.ts +15 -0
- package/src/vmmap.test.ts +22 -0
- package/src/vmmap.ts +6 -2
- package/src/vmutil.test.ts +116 -8
- package/src/vmutil.ts +76 -8
- package/src/wrapper.test.ts +44 -31
- package/src/wrapper.ts +47 -30
- package/dist/default.d.ts +0 -4
- package/dist/index.js +0 -8
- package/dist/marshal/array.d.ts +0 -2
- 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 -1302
- 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 -1285
- package/dist/quickjs-emscripten-sync.esm.js.map +0 -1
- package/dist/unmarshal/array.d.ts +0 -2
- 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/marshal/array.test.ts +0 -40
- package/src/marshal/array.ts +0 -24
- package/src/unmarshal/array.test.ts +0 -45
- package/src/unmarshal/array.ts +0 -32
package/src/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
QuickJSDeferredPromise,
|
|
3
|
+
QuickJSHandle,
|
|
4
|
+
QuickJSVm,
|
|
5
|
+
} from "quickjs-emscripten";
|
|
6
|
+
import type {
|
|
3
7
|
SuccessOrFail,
|
|
4
8
|
VmCallResult,
|
|
5
9
|
} from "quickjs-emscripten/dist/vm-interface";
|
|
@@ -9,11 +13,18 @@ import marshal from "./marshal";
|
|
|
9
13
|
import unmarshal from "./unmarshal";
|
|
10
14
|
import { wrap, wrapHandle, unwrap, unwrapHandle, Wrapped } from "./wrapper";
|
|
11
15
|
import { complexity, isES2015Class, isObject, walkObject } from "./util";
|
|
12
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
call,
|
|
18
|
+
eq,
|
|
19
|
+
isHandleObject,
|
|
20
|
+
json,
|
|
21
|
+
consumeAll,
|
|
22
|
+
mayConsume,
|
|
23
|
+
handleFrom,
|
|
24
|
+
} from "./vmutil";
|
|
13
25
|
import { defaultRegisteredObjects } from "./default";
|
|
14
26
|
|
|
15
27
|
export {
|
|
16
|
-
Arena,
|
|
17
28
|
VMMap,
|
|
18
29
|
defaultRegisteredObjects,
|
|
19
30
|
marshal,
|
|
@@ -25,14 +36,14 @@ export {
|
|
|
25
36
|
call,
|
|
26
37
|
eq,
|
|
27
38
|
isHandleObject,
|
|
28
|
-
|
|
39
|
+
json,
|
|
29
40
|
consumeAll,
|
|
30
41
|
};
|
|
31
42
|
|
|
32
43
|
export type Options = {
|
|
33
44
|
/** 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;
|
|
35
|
-
/** You can pre-register a pair of objects that will be considered the same between the
|
|
45
|
+
isMarshalable?: boolean | "json" | ((target: any) => boolean | "json");
|
|
46
|
+
/** 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
47
|
*
|
|
37
48
|
* 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
49
|
*/
|
|
@@ -40,9 +51,9 @@ export type Options = {
|
|
|
40
51
|
};
|
|
41
52
|
|
|
42
53
|
/**
|
|
43
|
-
* The Arena class manages all generated handles at once by quickjs-emscripten and automatically converts objects between
|
|
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
|
|
56
|
+
export class Arena {
|
|
46
57
|
vm: QuickJSVm;
|
|
47
58
|
_map: VMMap;
|
|
48
59
|
_registeredMap: VMMap;
|
|
@@ -73,10 +84,10 @@ export default class Arena {
|
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
/**
|
|
76
|
-
* Evaluate JS code in the VM and get the result as an object on the
|
|
87
|
+
* 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
88
|
*/
|
|
78
89
|
evalCode<T = any>(code: string): T {
|
|
79
|
-
|
|
90
|
+
const handle = this.vm.evalCode(code);
|
|
80
91
|
return this._unwrapResultAndUnmarshal(handle);
|
|
81
92
|
}
|
|
82
93
|
|
|
@@ -88,31 +99,39 @@ export default class Arena {
|
|
|
88
99
|
if ("value" in result) {
|
|
89
100
|
return result.value;
|
|
90
101
|
}
|
|
91
|
-
throw result.error.consume(
|
|
102
|
+
throw this._unwrapIfNotSynced(result.error.consume(this._unmarshal));
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
/**
|
|
95
106
|
* Expose objects as global objects in the VM.
|
|
96
107
|
*
|
|
97
|
-
*
|
|
108
|
+
* By default, exposed objects are not synchronized between the host and the VM.
|
|
109
|
+
* If you want to sync an objects, first wrap the object with sync method, and then expose the wrapped object.
|
|
110
|
+
*/
|
|
111
|
+
expose(obj: { [k: string]: any }) {
|
|
112
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
113
|
+
mayConsume(this._marshal(value), (handle) => {
|
|
114
|
+
this.vm.setProp(this.vm.global, key, handle);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Enables sync for the object between the host and the VM and returns objects wrapped with proxies.
|
|
121
|
+
*
|
|
122
|
+
* The return value is necessary in order to reflect changes to the object from the host to the VM. Please note that setting a value in the field or deleting a field in the original object will not synchronize it.
|
|
98
123
|
*/
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
walkObject(value2, v => {
|
|
105
|
-
this._sync.add(this._unwrap(v));
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
this.vm.setProp(this.vm.global, key, handle);
|
|
109
|
-
return [key, value2] as const;
|
|
124
|
+
sync<T>(target: T): T {
|
|
125
|
+
const wrapped = this._wrap(target);
|
|
126
|
+
if (typeof wrapped === "undefined") return target;
|
|
127
|
+
walkObject(wrapped, (v) => {
|
|
128
|
+
this._sync.add(this._unwrap(v));
|
|
110
129
|
});
|
|
111
|
-
return
|
|
130
|
+
return wrapped;
|
|
112
131
|
}
|
|
113
132
|
|
|
114
133
|
/**
|
|
115
|
-
* Register a pair of objects that will be considered the same between the
|
|
134
|
+
* Register a pair of objects that will be considered the same between the host and the QuickJS VM.
|
|
116
135
|
*
|
|
117
136
|
* Instead of a string, you can also pass a QuickJSHandle directly. In that case, however, when you have to dispose them manually when destroying the VM.
|
|
118
137
|
*/
|
|
@@ -122,6 +141,7 @@ export default class Arena {
|
|
|
122
141
|
typeof handleOrCode === "string"
|
|
123
142
|
? this._unwrapResult(this.vm.evalCode(handleOrCode))
|
|
124
143
|
: handleOrCode;
|
|
144
|
+
if (eq(this.vm, handle, this.vm.undefined)) return;
|
|
125
145
|
if (typeof handleOrCode === "string") {
|
|
126
146
|
this._registeredMapDispose.add(target);
|
|
127
147
|
}
|
|
@@ -170,52 +190,85 @@ export default class Arena {
|
|
|
170
190
|
if ("value" in result) {
|
|
171
191
|
return result.value;
|
|
172
192
|
}
|
|
173
|
-
throw result.error.consume(
|
|
193
|
+
throw this._unwrapIfNotSynced(result.error.consume(this._unmarshal));
|
|
174
194
|
}
|
|
175
195
|
|
|
176
196
|
_unwrapResultAndUnmarshal(
|
|
177
197
|
result: VmCallResult<QuickJSHandle> | undefined
|
|
178
198
|
): any {
|
|
179
199
|
if (!result) return;
|
|
180
|
-
return this.
|
|
200
|
+
return this._unwrapIfNotSynced(
|
|
201
|
+
this._unwrapResult(result).consume(this._unmarshal)
|
|
202
|
+
);
|
|
181
203
|
}
|
|
182
204
|
|
|
183
|
-
|
|
205
|
+
_isMarshalable = (t: unknown): boolean | "json" => {
|
|
206
|
+
const im = this._options?.isMarshalable;
|
|
207
|
+
return (typeof im === "function" ? im(this._unwrap(t)) : im) ?? "json";
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
_marshalFind = (t: unknown) => {
|
|
211
|
+
const unwrappedT = this._unwrap(t);
|
|
212
|
+
const handle =
|
|
213
|
+
this._registeredMap.get(t) ??
|
|
214
|
+
(unwrappedT !== t ? this._registeredMap.get(unwrappedT) : undefined) ??
|
|
215
|
+
this._map.get(t) ??
|
|
216
|
+
(unwrappedT !== t ? this._map.get(unwrappedT) : undefined);
|
|
217
|
+
return handle;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
_marshalPre = (
|
|
221
|
+
t: unknown,
|
|
222
|
+
h: QuickJSHandle | QuickJSDeferredPromise,
|
|
223
|
+
mode: true | "json" | undefined
|
|
224
|
+
): Wrapped<QuickJSHandle> | undefined => {
|
|
225
|
+
if (mode === "json") return;
|
|
226
|
+
return this._register(t, handleFrom(h), this._map)?.[1];
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
_marshalPreApply = (
|
|
230
|
+
target: Function,
|
|
231
|
+
that: unknown,
|
|
232
|
+
args: unknown[]
|
|
233
|
+
): void => {
|
|
234
|
+
const unwrapped = isObject(that) ? this._unwrap(that) : undefined;
|
|
235
|
+
// override sync mode of this object while calling the function
|
|
236
|
+
if (unwrapped) this._temporalSync.add(unwrapped);
|
|
237
|
+
try {
|
|
238
|
+
return target.apply(that, args);
|
|
239
|
+
} finally {
|
|
240
|
+
// restore sync mode
|
|
241
|
+
if (unwrapped) this._temporalSync.delete(unwrapped);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
_marshal = (target: any): [QuickJSHandle, boolean] => {
|
|
184
246
|
const registered = this._registeredMap.get(target);
|
|
185
247
|
if (registered) {
|
|
186
|
-
return registered;
|
|
248
|
+
return [registered, false];
|
|
187
249
|
}
|
|
188
250
|
|
|
189
|
-
const map = new VMMap(this.vm);
|
|
190
|
-
map.merge(this._map);
|
|
191
|
-
|
|
192
251
|
const handle = marshal(this._wrap(target) ?? target, {
|
|
193
252
|
vm: this.vm,
|
|
194
|
-
unmarshal:
|
|
195
|
-
isMarshalable:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
preApply: (target, that, args) => {
|
|
200
|
-
const unwrapped = isObject(that) ? this._unwrap(that) : undefined;
|
|
201
|
-
// override sync mode of this object while calling the function
|
|
202
|
-
if (unwrapped) this._temporalSync.add(unwrapped);
|
|
203
|
-
try {
|
|
204
|
-
return target.apply(that, args);
|
|
205
|
-
} finally {
|
|
206
|
-
// restore sync mode
|
|
207
|
-
if (unwrapped) this._temporalSync.delete(unwrapped);
|
|
208
|
-
}
|
|
209
|
-
},
|
|
253
|
+
unmarshal: this._unmarshal,
|
|
254
|
+
isMarshalable: this._isMarshalable,
|
|
255
|
+
find: this._marshalFind,
|
|
256
|
+
pre: this._marshalPre,
|
|
257
|
+
preApply: this._marshalPreApply,
|
|
210
258
|
});
|
|
211
259
|
|
|
212
|
-
this._map.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
260
|
+
return [handle, !this._map.hasHandle(handle)];
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
_preUnmarshal = (t: any, h: QuickJSHandle): Wrapped<any> => {
|
|
264
|
+
return this._register(t, h, undefined, true)?.[0];
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
_unmarshalFind = (h: QuickJSHandle): unknown => {
|
|
268
|
+
return this._registeredMap.getByHandle(h) ?? this._map.getByHandle(h);
|
|
269
|
+
};
|
|
217
270
|
|
|
218
|
-
_unmarshal(handle: QuickJSHandle): any {
|
|
271
|
+
_unmarshal = (handle: QuickJSHandle): any => {
|
|
219
272
|
const registered = this._registeredMap.getByHandle(handle);
|
|
220
273
|
if (typeof registered !== "undefined") {
|
|
221
274
|
return registered;
|
|
@@ -224,12 +277,11 @@ export default class Arena {
|
|
|
224
277
|
const [wrappedHandle] = this._wrapHandle(handle);
|
|
225
278
|
return unmarshal(wrappedHandle ?? handle, {
|
|
226
279
|
vm: this.vm,
|
|
227
|
-
marshal:
|
|
228
|
-
find:
|
|
229
|
-
pre:
|
|
230
|
-
this._register(t, h, undefined, true)?.[0],
|
|
280
|
+
marshal: this._marshal,
|
|
281
|
+
find: this._unmarshalFind,
|
|
282
|
+
pre: this._preUnmarshal,
|
|
231
283
|
});
|
|
232
|
-
}
|
|
284
|
+
};
|
|
233
285
|
|
|
234
286
|
_register(
|
|
235
287
|
t: any,
|
|
@@ -260,12 +312,12 @@ export default class Arena {
|
|
|
260
312
|
return [wrappedT, wrappedH];
|
|
261
313
|
}
|
|
262
314
|
|
|
263
|
-
_syncMode(obj: any) {
|
|
315
|
+
_syncMode = (obj: any): "both" | undefined => {
|
|
264
316
|
const obj2 = this._unwrap(obj);
|
|
265
317
|
return this._sync.has(obj2) || this._temporalSync.has(obj2)
|
|
266
318
|
? "both"
|
|
267
319
|
: undefined;
|
|
268
|
-
}
|
|
320
|
+
};
|
|
269
321
|
|
|
270
322
|
_wrap<T>(target: T): Wrapped<T> | undefined {
|
|
271
323
|
return wrap(
|
|
@@ -273,8 +325,8 @@ export default class Arena {
|
|
|
273
325
|
target,
|
|
274
326
|
this._symbol,
|
|
275
327
|
this._symbolHandle,
|
|
276
|
-
|
|
277
|
-
|
|
328
|
+
this._marshal,
|
|
329
|
+
this._syncMode
|
|
278
330
|
);
|
|
279
331
|
}
|
|
280
332
|
|
|
@@ -282,6 +334,11 @@ export default class Arena {
|
|
|
282
334
|
return unwrap(target, this._symbol);
|
|
283
335
|
}
|
|
284
336
|
|
|
337
|
+
_unwrapIfNotSynced = <T>(target: T): T => {
|
|
338
|
+
const unwrapped = this._unwrap(target);
|
|
339
|
+
return this._sync.has(unwrapped) ? target : unwrapped;
|
|
340
|
+
};
|
|
341
|
+
|
|
285
342
|
_wrapHandle(
|
|
286
343
|
handle: QuickJSHandle
|
|
287
344
|
): [Wrapped<QuickJSHandle> | undefined, boolean] {
|
|
@@ -290,8 +347,8 @@ export default class Arena {
|
|
|
290
347
|
handle,
|
|
291
348
|
this._symbol,
|
|
292
349
|
this._symbolHandle,
|
|
293
|
-
|
|
294
|
-
|
|
350
|
+
this._unmarshal,
|
|
351
|
+
this._syncMode
|
|
295
352
|
);
|
|
296
353
|
}
|
|
297
354
|
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { getQuickJS, QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
-
import {
|
|
2
|
+
import { json, eq, call } from "../vmutil";
|
|
3
3
|
import marshalFunction from "./function";
|
|
4
|
+
import { expect, test, vi } from "vitest";
|
|
4
5
|
|
|
5
6
|
test("normal func", async () => {
|
|
6
7
|
const vm = (await getQuickJS()).createVm();
|
|
7
8
|
|
|
8
|
-
const marshal =
|
|
9
|
-
const unmarshal =
|
|
9
|
+
const marshal = vi.fn((v) => json(vm, v));
|
|
10
|
+
const unmarshal = vi.fn((v) =>
|
|
10
11
|
eq(vm, v, vm.global) ? undefined : vm.dump(v)
|
|
11
12
|
);
|
|
12
|
-
const preMarshal =
|
|
13
|
-
const innerfn =
|
|
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
17
|
const handle = marshalFunction(vm, fn, marshal, unmarshal, preMarshal);
|
|
@@ -40,7 +41,7 @@ test("normal func", async () => {
|
|
|
40
41
|
|
|
41
42
|
test("func which has properties", async () => {
|
|
42
43
|
const vm = (await getQuickJS()).createVm();
|
|
43
|
-
const marshal =
|
|
44
|
+
const marshal = vi.fn((v) => json(vm, v));
|
|
44
45
|
|
|
45
46
|
const fn = () => {};
|
|
46
47
|
fn.hoge = "foo";
|
|
@@ -49,7 +50,7 @@ test("func which has properties", async () => {
|
|
|
49
50
|
vm,
|
|
50
51
|
fn,
|
|
51
52
|
marshal,
|
|
52
|
-
v => vm.dump(v),
|
|
53
|
+
(v) => vm.dump(v),
|
|
53
54
|
(_, a) => a
|
|
54
55
|
);
|
|
55
56
|
if (!handle) throw new Error("handle is undefined");
|
|
@@ -101,7 +102,7 @@ test("class", async () => {
|
|
|
101
102
|
).toBe(true);
|
|
102
103
|
expect(vm.dump(vm.getProp(instance, "a"))).toBe(100);
|
|
103
104
|
|
|
104
|
-
disposables.forEach(d => d.dispose());
|
|
105
|
+
disposables.forEach((d) => d.dispose());
|
|
105
106
|
instance.dispose();
|
|
106
107
|
newA.dispose();
|
|
107
108
|
handle.dispose();
|
|
@@ -118,7 +119,7 @@ test("preApply", async () => {
|
|
|
118
119
|
};
|
|
119
120
|
const unmarshal = (v: QuickJSHandle) =>
|
|
120
121
|
vm.typeof(v) === "object" ? that : vm.dump(v);
|
|
121
|
-
const preApply =
|
|
122
|
+
const preApply = vi.fn(
|
|
122
123
|
(a: Function, b: any, c: any[]) => a.apply(b, c) + "!"
|
|
123
124
|
);
|
|
124
125
|
const that = {};
|
|
@@ -152,7 +153,7 @@ test("preApply", async () => {
|
|
|
152
153
|
|
|
153
154
|
test("undefined", async () => {
|
|
154
155
|
const vm = (await getQuickJS()).createVm();
|
|
155
|
-
const f =
|
|
156
|
+
const f = vi.fn();
|
|
156
157
|
|
|
157
158
|
expect(marshalFunction(vm, undefined, f, f, f)).toBe(undefined);
|
|
158
159
|
expect(marshalFunction(vm, null, f, f, f)).toBe(undefined);
|
package/src/marshal/function.ts
CHANGED
|
@@ -17,9 +17,9 @@ export default function marshalFunction(
|
|
|
17
17
|
if (typeof target !== "function") return;
|
|
18
18
|
|
|
19
19
|
const raw = vm
|
|
20
|
-
.newFunction(target.name, function(...argHandles) {
|
|
20
|
+
.newFunction(target.name, function (...argHandles) {
|
|
21
21
|
const that = unmarshal(this);
|
|
22
|
-
const args = argHandles.map(a => unmarshal(a));
|
|
22
|
+
const args = argHandles.map((a) => unmarshal(a));
|
|
23
23
|
|
|
24
24
|
if (isES2015Class(target) && isObject(that)) {
|
|
25
25
|
// Class constructors cannot be invoked without new expression, and new.target is not changed
|
|
@@ -34,7 +34,7 @@ export default function marshalFunction(
|
|
|
34
34
|
preApply ? preApply(target, that, args) : target.apply(that, args)
|
|
35
35
|
);
|
|
36
36
|
})
|
|
37
|
-
.consume(handle2 =>
|
|
37
|
+
.consume((handle2) =>
|
|
38
38
|
// fucntions created by vm.newFunction are not callable as a class constrcutor
|
|
39
39
|
call(
|
|
40
40
|
vm,
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { getQuickJS } from "quickjs-emscripten";
|
|
2
|
+
import { expect, test, vi } from "vitest";
|
|
3
|
+
|
|
2
4
|
import VMMap from "../vmmap";
|
|
3
|
-
import { instanceOf, call } from "../vmutil";
|
|
5
|
+
import { instanceOf, call, handleFrom, fn } from "../vmutil";
|
|
4
6
|
import marshal from ".";
|
|
7
|
+
import { newDeferred } from "../util";
|
|
5
8
|
|
|
6
9
|
test("primitive, array, object", async () => {
|
|
7
10
|
const { vm, map, marshal, dispose } = await setup();
|
|
@@ -67,7 +70,7 @@ test("arrow function", async () => {
|
|
|
67
70
|
test("function", async () => {
|
|
68
71
|
const { vm, map, marshal, dispose } = await setup();
|
|
69
72
|
|
|
70
|
-
const bar = function(a: number, b: { hoge: number }) {
|
|
73
|
+
const bar = function (a: number, b: { hoge: number }) {
|
|
71
74
|
return a + b.hoge;
|
|
72
75
|
};
|
|
73
76
|
const handle = marshal(bar);
|
|
@@ -90,6 +93,42 @@ test("function", async () => {
|
|
|
90
93
|
dispose();
|
|
91
94
|
});
|
|
92
95
|
|
|
96
|
+
test("promise", async () => {
|
|
97
|
+
const { vm, marshal, dispose } = await setup();
|
|
98
|
+
const register = fn(
|
|
99
|
+
vm,
|
|
100
|
+
`promise => { promise.then(d => notify("resolve", d), d => notify("reject", d)); }`
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
let notified: any;
|
|
104
|
+
vm.newFunction("notify", (...handles) => {
|
|
105
|
+
notified = handles.map((h) => vm.dump(h));
|
|
106
|
+
}).consume((h) => {
|
|
107
|
+
vm.setProp(vm.global, "notify", h);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const deferred = newDeferred();
|
|
111
|
+
const handle = marshal(deferred.promise);
|
|
112
|
+
register(undefined, handle);
|
|
113
|
+
|
|
114
|
+
deferred.resolve("foo");
|
|
115
|
+
await deferred.promise;
|
|
116
|
+
expect(vm.unwrapResult(vm.executePendingJobs())).toBe(1);
|
|
117
|
+
expect(notified).toEqual(["resolve", "foo"]);
|
|
118
|
+
|
|
119
|
+
const deferred2 = newDeferred();
|
|
120
|
+
const handle2 = marshal(deferred2.promise);
|
|
121
|
+
register(undefined, handle2);
|
|
122
|
+
|
|
123
|
+
deferred2.reject("bar");
|
|
124
|
+
await expect(deferred2.promise).rejects.toBe("bar");
|
|
125
|
+
expect(vm.unwrapResult(vm.executePendingJobs())).toBe(1);
|
|
126
|
+
expect(notified).toEqual(["reject", "bar"]);
|
|
127
|
+
|
|
128
|
+
register.dispose();
|
|
129
|
+
dispose();
|
|
130
|
+
});
|
|
131
|
+
|
|
93
132
|
test("class", async () => {
|
|
94
133
|
const { vm, map, marshal, dispose } = await setup();
|
|
95
134
|
|
|
@@ -117,6 +156,7 @@ test("class", async () => {
|
|
|
117
156
|
}
|
|
118
157
|
}
|
|
119
158
|
|
|
159
|
+
expect(A.name).toBe("_A"); // class A's name is _A in vitest
|
|
120
160
|
const handle = marshal(A);
|
|
121
161
|
if (!map) throw new Error("map is undefined");
|
|
122
162
|
|
|
@@ -126,15 +166,15 @@ test("class", async () => {
|
|
|
126
166
|
expect(map.has(A.a)).toBe(true);
|
|
127
167
|
expect(map.has(A.prototype.hoge)).toBe(true);
|
|
128
168
|
expect(
|
|
129
|
-
map.has(Object.getOwnPropertyDescriptor(A.prototype, "foo")
|
|
169
|
+
map.has(Object.getOwnPropertyDescriptor(A.prototype, "foo")?.get)
|
|
130
170
|
).toBe(true);
|
|
131
171
|
expect(
|
|
132
|
-
map.has(Object.getOwnPropertyDescriptor(A.prototype, "foo")
|
|
172
|
+
map.has(Object.getOwnPropertyDescriptor(A.prototype, "foo")?.set)
|
|
133
173
|
).toBe(true);
|
|
134
174
|
|
|
135
175
|
expect(vm.typeof(handle)).toBe("function");
|
|
136
176
|
expect(vm.dump(vm.getProp(handle, "length"))).toBe(1);
|
|
137
|
-
expect(vm.dump(vm.getProp(handle, "name"))).toBe("
|
|
177
|
+
expect(vm.dump(vm.getProp(handle, "name"))).toBe("_A");
|
|
138
178
|
const staticA = vm.getProp(handle, "a");
|
|
139
179
|
expect(instanceOf(vm, staticA, handle)).toBe(true);
|
|
140
180
|
expect(vm.dump(vm.getProp(staticA, "a"))).toBe(100);
|
|
@@ -172,7 +212,7 @@ test("class", async () => {
|
|
|
172
212
|
});
|
|
173
213
|
|
|
174
214
|
test("marshalable", async () => {
|
|
175
|
-
const isMarshalable =
|
|
215
|
+
const isMarshalable = vi.fn((a: any) => a !== globalThis);
|
|
176
216
|
const { vm, marshal, dispose } = await setup({
|
|
177
217
|
isMarshalable,
|
|
178
218
|
});
|
|
@@ -186,10 +226,34 @@ test("marshalable", async () => {
|
|
|
186
226
|
dispose();
|
|
187
227
|
});
|
|
188
228
|
|
|
229
|
+
test("marshalable json", async () => {
|
|
230
|
+
const isMarshalable = vi.fn(() => "json" as const);
|
|
231
|
+
const { vm, marshal, dispose } = await setup({
|
|
232
|
+
isMarshalable,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
class Hoge {}
|
|
236
|
+
const target = {
|
|
237
|
+
a: { c: () => 1, d: new Date(), e: [() => 1, 1, new Hoge()] },
|
|
238
|
+
b: 1,
|
|
239
|
+
};
|
|
240
|
+
const handle = marshal(target);
|
|
241
|
+
|
|
242
|
+
expect(vm.dump(handle)).toEqual({
|
|
243
|
+
a: { d: target.a.d.toISOString(), e: [null, 1, {}] },
|
|
244
|
+
b: 1,
|
|
245
|
+
});
|
|
246
|
+
expect(isMarshalable).toBeCalledTimes(1);
|
|
247
|
+
expect(isMarshalable).toBeCalledWith(target);
|
|
248
|
+
expect(isMarshalable).toReturnWith("json");
|
|
249
|
+
|
|
250
|
+
dispose();
|
|
251
|
+
});
|
|
252
|
+
|
|
189
253
|
const setup = async ({
|
|
190
254
|
isMarshalable,
|
|
191
255
|
}: {
|
|
192
|
-
isMarshalable?: (target: any) => boolean;
|
|
256
|
+
isMarshalable?: (target: any) => boolean | "json";
|
|
193
257
|
} = {}) => {
|
|
194
258
|
const vm = (await getQuickJS()).createVm();
|
|
195
259
|
const map = new VMMap(vm);
|
|
@@ -199,13 +263,14 @@ const setup = async ({
|
|
|
199
263
|
marshal: (v: any) =>
|
|
200
264
|
marshal(v, {
|
|
201
265
|
vm,
|
|
202
|
-
unmarshal: h => map.getByHandle(h) ?? vm.dump(h),
|
|
266
|
+
unmarshal: (h) => map.getByHandle(h) ?? vm.dump(h),
|
|
203
267
|
isMarshalable,
|
|
204
|
-
pre: (t,
|
|
268
|
+
pre: (t, d) => {
|
|
269
|
+
const h = handleFrom(d);
|
|
205
270
|
map.set(t, h);
|
|
206
271
|
return h;
|
|
207
272
|
},
|
|
208
|
-
find: t => map.get(t),
|
|
273
|
+
find: (t) => map.get(t),
|
|
209
274
|
}),
|
|
210
275
|
dispose: () => {
|
|
211
276
|
map.dispose();
|
package/src/marshal/index.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
QuickJSDeferredPromise,
|
|
3
|
+
QuickJSHandle,
|
|
4
|
+
QuickJSVm,
|
|
5
|
+
} from "quickjs-emscripten";
|
|
3
6
|
import marshalFunction from "./function";
|
|
4
7
|
import marshalObject from "./object";
|
|
5
8
|
import marshalPrimitive from "./primitive";
|
|
6
9
|
import marshalSymbol from "./symbol";
|
|
10
|
+
import marshalJSON from "./json";
|
|
11
|
+
import marshalPromise from "./promise";
|
|
7
12
|
|
|
8
13
|
export type Options = {
|
|
9
14
|
vm: QuickJSVm;
|
|
10
15
|
unmarshal: (handle: QuickJSHandle) => unknown;
|
|
11
|
-
isMarshalable?: (target: unknown) => boolean;
|
|
16
|
+
isMarshalable?: (target: unknown) => boolean | "json";
|
|
12
17
|
find: (target: unknown) => QuickJSHandle | undefined;
|
|
13
|
-
pre: (
|
|
18
|
+
pre: (
|
|
19
|
+
target: unknown,
|
|
20
|
+
handle: QuickJSHandle | QuickJSDeferredPromise,
|
|
21
|
+
mode: true | "json" | undefined
|
|
22
|
+
) => QuickJSHandle | undefined;
|
|
14
23
|
preApply?: (target: Function, thisArg: unknown, args: unknown[]) => any;
|
|
15
24
|
};
|
|
16
25
|
|
|
@@ -29,20 +38,25 @@ export function marshal(target: unknown, options: Options): QuickJSHandle {
|
|
|
29
38
|
if (handle) return handle;
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
const marshalable = isMarshalable?.(target);
|
|
42
|
+
if (marshalable === false) {
|
|
33
43
|
return vm.undefined;
|
|
34
44
|
}
|
|
35
45
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
marshalFunction(vm, target, marshal2, unmarshal, pre, options.preApply) ??
|
|
42
|
-
marshalObject(vm, target, marshal2, pre) ??
|
|
43
|
-
vm.undefined;
|
|
46
|
+
const pre2 = (target: any, handle: QuickJSHandle | QuickJSDeferredPromise) =>
|
|
47
|
+
pre(target, handle, marshalable);
|
|
48
|
+
if (marshalable === "json") {
|
|
49
|
+
return marshalJSON(vm, target, pre2);
|
|
50
|
+
}
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
const marshal2 = (t: unknown) => marshal(t, options);
|
|
53
|
+
return (
|
|
54
|
+
marshalSymbol(vm, target, pre2) ??
|
|
55
|
+
marshalPromise(vm, target, marshal2, pre2) ??
|
|
56
|
+
marshalFunction(vm, target, marshal2, unmarshal, pre2, options.preApply) ??
|
|
57
|
+
marshalObject(vm, target, marshal2, pre2) ??
|
|
58
|
+
vm.undefined
|
|
59
|
+
);
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
export default marshal;
|