export-runtime 0.0.1 → 0.0.2
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/client.js +42 -18
- package/handler.js +81 -5
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -168,6 +168,15 @@ const ready = new Promise((resolve, reject) => {
|
|
|
168
168
|
ws.onerror = (e) => reject(e);
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
const sendRequest = async (msg) => {
|
|
172
|
+
await ready;
|
|
173
|
+
const id = nextId++;
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
pending.set(id, { resolve, reject });
|
|
176
|
+
ws.send(stringify({ ...msg, id }));
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
171
180
|
ws.onmessage = (event) => {
|
|
172
181
|
const msg = parse(event.data);
|
|
173
182
|
const resolver = pending.get(msg.id);
|
|
@@ -179,24 +188,16 @@ ws.onmessage = (event) => {
|
|
|
179
188
|
} else if (msg.type === "result") {
|
|
180
189
|
if (msg.valueType === "function") {
|
|
181
190
|
resolver.resolve(createProxy(msg.path));
|
|
191
|
+
} else if (msg.valueType === "instance") {
|
|
192
|
+
resolver.resolve(createInstanceProxy(msg.instanceId));
|
|
182
193
|
} else if (msg.valueType === "asynciterator") {
|
|
183
194
|
const iteratorProxy = {
|
|
184
195
|
[Symbol.asyncIterator]() { return this; },
|
|
185
196
|
async next() {
|
|
186
|
-
|
|
187
|
-
const id = nextId++;
|
|
188
|
-
return new Promise((resolve, reject) => {
|
|
189
|
-
pending.set(id, { resolve, reject });
|
|
190
|
-
ws.send(stringify({ type: "iterate-next", id, iteratorId: msg.iteratorId }));
|
|
191
|
-
});
|
|
197
|
+
return sendRequest({ type: "iterate-next", iteratorId: msg.iteratorId });
|
|
192
198
|
},
|
|
193
199
|
async return(value) {
|
|
194
|
-
|
|
195
|
-
const id = nextId++;
|
|
196
|
-
return new Promise((resolve, reject) => {
|
|
197
|
-
pending.set(id, { resolve, reject });
|
|
198
|
-
ws.send(stringify({ type: "iterate-return", id, iteratorId: msg.iteratorId, value }));
|
|
199
|
-
});
|
|
200
|
+
return sendRequest({ type: "iterate-return", iteratorId: msg.iteratorId, value });
|
|
200
201
|
}
|
|
201
202
|
};
|
|
202
203
|
resolver.resolve(iteratorProxy);
|
|
@@ -210,18 +211,41 @@ ws.onmessage = (event) => {
|
|
|
210
211
|
}
|
|
211
212
|
};
|
|
212
213
|
|
|
214
|
+
// Proxy for remote class instances
|
|
215
|
+
const createInstanceProxy = (instanceId, path = []) => {
|
|
216
|
+
const proxy = new Proxy(function(){}, {
|
|
217
|
+
get(_, prop) {
|
|
218
|
+
if (prop === "then" || prop === Symbol.toStringTag) return undefined;
|
|
219
|
+
if (prop === Symbol.dispose || prop === Symbol.asyncDispose) {
|
|
220
|
+
return () => sendRequest({ type: "release", instanceId });
|
|
221
|
+
}
|
|
222
|
+
if (prop === "[release]") {
|
|
223
|
+
return () => sendRequest({ type: "release", instanceId });
|
|
224
|
+
}
|
|
225
|
+
return createInstanceProxy(instanceId, [...path, prop]);
|
|
226
|
+
},
|
|
227
|
+
set(_, prop, value) {
|
|
228
|
+
sendRequest({ type: "set", instanceId, path: [...path, prop], args: [value] });
|
|
229
|
+
return true;
|
|
230
|
+
},
|
|
231
|
+
async apply(_, __, args) {
|
|
232
|
+
return sendRequest({ type: "call", instanceId, path, args });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return proxy;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Proxy for exports (functions, classes, objects)
|
|
213
239
|
const createProxy = (path = []) => new Proxy(function(){}, {
|
|
214
240
|
get(_, prop) {
|
|
215
241
|
if (prop === "then" || prop === Symbol.toStringTag) return undefined;
|
|
216
242
|
return createProxy([...path, prop]);
|
|
217
243
|
},
|
|
218
244
|
async apply(_, __, args) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
ws.send(stringify({ type: "call", id, path, args }));
|
|
224
|
-
});
|
|
245
|
+
return sendRequest({ type: "call", path, args });
|
|
246
|
+
},
|
|
247
|
+
construct(_, args) {
|
|
248
|
+
return sendRequest({ type: "construct", path, args });
|
|
225
249
|
}
|
|
226
250
|
});
|
|
227
251
|
|
package/handler.js
CHANGED
|
@@ -13,10 +13,15 @@ const getByPath = (obj, path) => {
|
|
|
13
13
|
const isAsyncIterable = (value) =>
|
|
14
14
|
value != null && typeof value[Symbol.asyncIterator] === "function";
|
|
15
15
|
|
|
16
|
+
const isClass = (fn) =>
|
|
17
|
+
typeof fn === "function" && /^class\s/.test(Function.prototype.toString.call(fn));
|
|
18
|
+
|
|
16
19
|
export const createHandler = (exports) => {
|
|
17
20
|
const exportKeys = Object.keys(exports);
|
|
18
21
|
const iteratorStore = new Map();
|
|
22
|
+
const instanceStore = new Map();
|
|
19
23
|
let nextIteratorId = 1;
|
|
24
|
+
let nextInstanceId = 1;
|
|
20
25
|
|
|
21
26
|
const send = (ws, data) => {
|
|
22
27
|
ws.send(stringify(data));
|
|
@@ -36,16 +41,50 @@ export const createHandler = (exports) => {
|
|
|
36
41
|
server.addEventListener("message", async (event) => {
|
|
37
42
|
try {
|
|
38
43
|
const msg = parse(event.data);
|
|
39
|
-
const { type, id, path = [], args = [], iteratorId } = msg;
|
|
44
|
+
const { type, id, path = [], args = [], iteratorId, instanceId } = msg;
|
|
40
45
|
|
|
41
|
-
if (type === "
|
|
46
|
+
if (type === "construct") {
|
|
47
|
+
// Class instantiation
|
|
48
|
+
try {
|
|
49
|
+
const Ctor = getByPath(exports, path);
|
|
50
|
+
if (!isClass(Ctor)) {
|
|
51
|
+
send(server, { type: "error", id, error: `${path.join(".")} is not a class` });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const instance = new Ctor(...args);
|
|
55
|
+
const instId = nextInstanceId++;
|
|
56
|
+
instanceStore.set(instId, instance);
|
|
57
|
+
send(server, { type: "result", id, instanceId: instId, valueType: "instance" });
|
|
58
|
+
} catch (err) {
|
|
59
|
+
send(server, { type: "error", id, error: String(err) });
|
|
60
|
+
}
|
|
61
|
+
} else if (type === "call") {
|
|
42
62
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
let target;
|
|
64
|
+
let thisArg;
|
|
65
|
+
|
|
66
|
+
if (instanceId !== undefined) {
|
|
67
|
+
// Method call on instance
|
|
68
|
+
const instance = instanceStore.get(instanceId);
|
|
69
|
+
if (!instance) {
|
|
70
|
+
send(server, { type: "error", id, error: "Instance not found" });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
target = getByPath(instance, path);
|
|
74
|
+
thisArg = path.length > 1 ? getByPath(instance, path.slice(0, -1)) : instance;
|
|
75
|
+
} else {
|
|
76
|
+
// Regular function call
|
|
77
|
+
target = getByPath(exports, path);
|
|
78
|
+
thisArg = path.length > 1 ? getByPath(exports, path.slice(0, -1)) : undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof target !== "function") {
|
|
45
82
|
send(server, { type: "error", id, error: `${path.join(".")} is not a function` });
|
|
46
83
|
return;
|
|
47
84
|
}
|
|
48
|
-
|
|
85
|
+
|
|
86
|
+
// Await result to support both sync and async functions
|
|
87
|
+
const result = await target.apply(thisArg, args);
|
|
49
88
|
|
|
50
89
|
if (isAsyncIterable(result)) {
|
|
51
90
|
const iterId = nextIteratorId++;
|
|
@@ -59,6 +98,42 @@ export const createHandler = (exports) => {
|
|
|
59
98
|
} catch (err) {
|
|
60
99
|
send(server, { type: "error", id, error: String(err) });
|
|
61
100
|
}
|
|
101
|
+
} else if (type === "get") {
|
|
102
|
+
// Property access on instance
|
|
103
|
+
try {
|
|
104
|
+
const instance = instanceStore.get(instanceId);
|
|
105
|
+
if (!instance) {
|
|
106
|
+
send(server, { type: "error", id, error: "Instance not found" });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const value = getByPath(instance, path);
|
|
110
|
+
if (typeof value === "function") {
|
|
111
|
+
send(server, { type: "result", id, valueType: "function" });
|
|
112
|
+
} else {
|
|
113
|
+
send(server, { type: "result", id, value });
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
send(server, { type: "error", id, error: String(err) });
|
|
117
|
+
}
|
|
118
|
+
} else if (type === "set") {
|
|
119
|
+
// Property assignment on instance
|
|
120
|
+
try {
|
|
121
|
+
const instance = instanceStore.get(instanceId);
|
|
122
|
+
if (!instance) {
|
|
123
|
+
send(server, { type: "error", id, error: "Instance not found" });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const parent = path.length > 1 ? getByPath(instance, path.slice(0, -1)) : instance;
|
|
127
|
+
const prop = path[path.length - 1];
|
|
128
|
+
parent[prop] = args[0];
|
|
129
|
+
send(server, { type: "result", id, value: true });
|
|
130
|
+
} catch (err) {
|
|
131
|
+
send(server, { type: "error", id, error: String(err) });
|
|
132
|
+
}
|
|
133
|
+
} else if (type === "release") {
|
|
134
|
+
// Release instance
|
|
135
|
+
instanceStore.delete(instanceId);
|
|
136
|
+
send(server, { type: "result", id, value: true });
|
|
62
137
|
} else if (type === "iterate-next") {
|
|
63
138
|
const iter = iteratorStore.get(iteratorId);
|
|
64
139
|
if (!iter) {
|
|
@@ -85,6 +160,7 @@ export const createHandler = (exports) => {
|
|
|
85
160
|
|
|
86
161
|
server.addEventListener("close", () => {
|
|
87
162
|
iteratorStore.clear();
|
|
163
|
+
instanceStore.clear();
|
|
88
164
|
});
|
|
89
165
|
|
|
90
166
|
return new Response(null, { status: 101, webSocket: client });
|