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.
Files changed (3) hide show
  1. package/client.js +42 -18
  2. package/handler.js +81 -5
  3. 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
- await ready;
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
- await ready;
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
- await ready;
220
- const id = nextId++;
221
- return new Promise((resolve, reject) => {
222
- pending.set(id, { resolve, reject });
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 === "call") {
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
- const fn = getByPath(exports, path);
44
- if (typeof fn !== "function") {
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
- const result = await fn.apply(undefined, args);
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 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "export-runtime",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Cloudflare Workers ESM Export Framework Runtime",
5
5
  "keywords": [
6
6
  "cloudflare",