@ricsam/isolate-client 0.1.4 → 0.1.6

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.
@@ -10,7 +10,7 @@ import {
10
10
  STREAM_DEFAULT_CREDIT,
11
11
  marshalValue
12
12
  } from "@ricsam/isolate-protocol";
13
- import { createPlaywrightHandler } from "@ricsam/isolate-playwright";
13
+ import { createPlaywrightHandler } from "@ricsam/isolate-playwright/client";
14
14
  var DEFAULT_TIMEOUT = 30000;
15
15
  var isolateWsCallbacks = new Map;
16
16
  async function connect(options = {}) {
@@ -24,7 +24,8 @@ async function connect(options = {}) {
24
24
  nextStreamId: 1,
25
25
  connected: true,
26
26
  streamResponses: new Map,
27
- uploadStreams: new Map
27
+ uploadStreams: new Map,
28
+ moduleSourceCache: new Map
28
29
  };
29
30
  const parser = createFrameParser();
30
31
  socket.on("data", (data) => {
@@ -39,15 +40,38 @@ async function connect(options = {}) {
39
40
  socket.on("close", () => {
40
41
  state.connected = false;
41
42
  for (const [, pending] of state.pendingRequests) {
43
+ if (pending.timeoutId) {
44
+ clearTimeout(pending.timeoutId);
45
+ }
42
46
  pending.reject(new Error("Connection closed"));
43
47
  }
44
48
  state.pendingRequests.clear();
49
+ for (const [, receiver] of state.streamResponses) {
50
+ receiver.state = "errored";
51
+ receiver.error = new Error("Connection closed");
52
+ const resolvers = receiver.pullResolvers.splice(0);
53
+ for (const resolver of resolvers) {
54
+ resolver();
55
+ }
56
+ }
57
+ state.streamResponses.clear();
58
+ for (const [, session] of state.uploadStreams) {
59
+ session.state = "closed";
60
+ if (session.creditResolver) {
61
+ session.creditResolver();
62
+ }
63
+ }
64
+ state.uploadStreams.clear();
45
65
  });
46
66
  socket.on("error", (err) => {
47
67
  console.error("Socket error:", err);
48
68
  });
49
69
  return {
50
70
  createRuntime: (runtimeOptions) => createRuntime(state, runtimeOptions),
71
+ createNamespace: (id) => ({
72
+ id,
73
+ createRuntime: (runtimeOptions) => createRuntime(state, runtimeOptions, id)
74
+ }),
51
75
  close: async () => {
52
76
  state.connected = false;
53
77
  socket.destroy();
@@ -148,10 +172,76 @@ function handleMessage(message, state) {
148
172
  streamId: msg.streamId,
149
173
  requestId: msg.requestId,
150
174
  metadata: msg.metadata,
151
- chunks: [],
152
- totalBytes: 0
175
+ controller: null,
176
+ state: "active",
177
+ pendingChunks: [],
178
+ pullResolvers: [],
179
+ controllerFinalized: false
153
180
  };
181
+ const readableStream = new ReadableStream({
182
+ start(controller) {
183
+ receiver.controller = controller;
184
+ },
185
+ pull(_controller) {
186
+ if (receiver.controllerFinalized) {
187
+ return;
188
+ }
189
+ while (receiver.pendingChunks.length > 0) {
190
+ const chunk = receiver.pendingChunks.shift();
191
+ receiver.controller.enqueue(chunk);
192
+ }
193
+ if (receiver.state === "closed") {
194
+ if (!receiver.controllerFinalized) {
195
+ receiver.controllerFinalized = true;
196
+ receiver.controller.close();
197
+ }
198
+ return Promise.resolve();
199
+ }
200
+ if (receiver.state === "errored") {
201
+ if (!receiver.controllerFinalized && receiver.error) {
202
+ receiver.controllerFinalized = true;
203
+ receiver.controller.error(receiver.error);
204
+ }
205
+ return Promise.resolve();
206
+ }
207
+ sendMessage(state.socket, {
208
+ type: MessageType.STREAM_PULL,
209
+ streamId: msg.streamId,
210
+ maxBytes: STREAM_DEFAULT_CREDIT
211
+ });
212
+ return new Promise((resolve) => {
213
+ receiver.pullResolvers.push(resolve);
214
+ });
215
+ },
216
+ cancel(_reason) {
217
+ receiver.state = "closed";
218
+ receiver.controllerFinalized = true;
219
+ const resolvers = receiver.pullResolvers.splice(0);
220
+ for (const resolver of resolvers) {
221
+ resolver();
222
+ }
223
+ sendMessage(state.socket, {
224
+ type: MessageType.STREAM_ERROR,
225
+ streamId: msg.streamId,
226
+ error: "Stream cancelled by consumer"
227
+ });
228
+ state.streamResponses.delete(msg.streamId);
229
+ return new Promise((resolve) => setTimeout(resolve, 0));
230
+ }
231
+ });
154
232
  state.streamResponses.set(msg.streamId, receiver);
233
+ const pending = state.pendingRequests.get(msg.requestId);
234
+ if (pending) {
235
+ state.pendingRequests.delete(msg.requestId);
236
+ if (pending.timeoutId)
237
+ clearTimeout(pending.timeoutId);
238
+ const response = new Response(readableStream, {
239
+ status: msg.metadata?.status ?? 200,
240
+ statusText: msg.metadata?.statusText ?? "OK",
241
+ headers: msg.metadata?.headers
242
+ });
243
+ pending.resolve({ response, __streaming: true });
244
+ }
155
245
  sendMessage(state.socket, {
156
246
  type: MessageType.STREAM_PULL,
157
247
  streamId: msg.streamId,
@@ -162,14 +252,14 @@ function handleMessage(message, state) {
162
252
  case MessageType.RESPONSE_STREAM_CHUNK: {
163
253
  const msg = message;
164
254
  const receiver = state.streamResponses.get(msg.streamId);
165
- if (receiver) {
166
- receiver.chunks.push(msg.chunk);
167
- receiver.totalBytes += msg.chunk.length;
168
- sendMessage(state.socket, {
169
- type: MessageType.STREAM_PULL,
170
- streamId: msg.streamId,
171
- maxBytes: STREAM_DEFAULT_CREDIT
172
- });
255
+ if (receiver && receiver.state === "active") {
256
+ if (receiver.pullResolvers.length > 0) {
257
+ receiver.controller.enqueue(msg.chunk);
258
+ const resolver = receiver.pullResolvers.shift();
259
+ resolver();
260
+ } else {
261
+ receiver.pendingChunks.push(msg.chunk);
262
+ }
173
263
  }
174
264
  break;
175
265
  }
@@ -177,20 +267,18 @@ function handleMessage(message, state) {
177
267
  const msg = message;
178
268
  const receiver = state.streamResponses.get(msg.streamId);
179
269
  if (receiver) {
180
- const body = concatUint8Arrays(receiver.chunks);
181
- const pending = state.pendingRequests.get(receiver.requestId);
182
- if (pending) {
183
- state.pendingRequests.delete(receiver.requestId);
184
- if (pending.timeoutId)
185
- clearTimeout(pending.timeoutId);
186
- pending.resolve({
187
- response: {
188
- status: receiver.metadata?.status ?? 200,
189
- statusText: receiver.metadata?.statusText ?? "OK",
190
- headers: receiver.metadata?.headers ?? [],
191
- body
192
- }
193
- });
270
+ receiver.state = "closed";
271
+ while (receiver.pendingChunks.length > 0) {
272
+ const chunk = receiver.pendingChunks.shift();
273
+ receiver.controller.enqueue(chunk);
274
+ }
275
+ if (!receiver.controllerFinalized) {
276
+ receiver.controllerFinalized = true;
277
+ receiver.controller.close();
278
+ }
279
+ const resolvers = receiver.pullResolvers.splice(0);
280
+ for (const resolver of resolvers) {
281
+ resolver();
194
282
  }
195
283
  state.streamResponses.delete(msg.streamId);
196
284
  }
@@ -217,12 +305,15 @@ function handleMessage(message, state) {
217
305
  }
218
306
  const receiver = state.streamResponses.get(msg.streamId);
219
307
  if (receiver) {
220
- const pending = state.pendingRequests.get(receiver.requestId);
221
- if (pending) {
222
- state.pendingRequests.delete(receiver.requestId);
223
- if (pending.timeoutId)
224
- clearTimeout(pending.timeoutId);
225
- pending.reject(new Error(msg.error));
308
+ receiver.state = "errored";
309
+ receiver.error = new Error(msg.error);
310
+ while (receiver.pendingChunks.length > 0) {
311
+ const chunk = receiver.pendingChunks.shift();
312
+ receiver.controller.enqueue(chunk);
313
+ }
314
+ const resolvers = receiver.pullResolvers.splice(0);
315
+ for (const resolver of resolvers) {
316
+ resolver();
226
317
  }
227
318
  state.streamResponses.delete(msg.streamId);
228
319
  }
@@ -232,16 +323,6 @@ function handleMessage(message, state) {
232
323
  console.warn(`Unexpected message type: ${message.type}`);
233
324
  }
234
325
  }
235
- function concatUint8Arrays(arrays) {
236
- const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
237
- const result = new Uint8Array(totalLength);
238
- let offset = 0;
239
- for (const arr of arrays) {
240
- result.set(arr, offset);
241
- offset += arr.length;
242
- }
243
- return result;
244
- }
245
326
  async function handleCallbackInvoke(invoke, state) {
246
327
  const callback = state.callbacks.get(invoke.callbackId);
247
328
  const response = {
@@ -291,7 +372,7 @@ function sendRequest(state, message, timeout = DEFAULT_TIMEOUT) {
291
372
  sendMessage(state.socket, message);
292
373
  });
293
374
  }
294
- async function createRuntime(state, options = {}) {
375
+ async function createRuntime(state, options = {}, namespaceId) {
295
376
  const callbacks = {};
296
377
  if (options.console) {
297
378
  callbacks.console = registerConsoleCallbacks(state, options.console);
@@ -414,11 +495,13 @@ async function createRuntime(state, options = {}) {
414
495
  memoryLimitMB: options.memoryLimitMB,
415
496
  cwd: options.cwd,
416
497
  callbacks,
417
- testEnvironment: testEnvironmentOption
498
+ testEnvironment: testEnvironmentOption,
499
+ namespaceId
418
500
  }
419
501
  };
420
502
  const result = await sendRequest(state, request);
421
503
  const isolateId = result.isolateId;
504
+ const reused = result.reused ?? false;
422
505
  const wsCommandCallbacks = new Set;
423
506
  isolateWsCallbacks.set(isolateId, wsCommandCallbacks);
424
507
  const fetchHandle = {
@@ -433,15 +516,21 @@ async function createRuntime(state, options = {}) {
433
516
  request: serializableRequest,
434
517
  options: opts
435
518
  };
519
+ const handleResponse = (res) => {
520
+ if (res.__streaming && res.response instanceof Response) {
521
+ return res.response;
522
+ }
523
+ return deserializeResponse(res.response);
524
+ };
436
525
  if (serialized.bodyStreamId !== undefined && bodyStream) {
437
526
  const streamId = serialized.bodyStreamId;
438
527
  const responsePromise = sendRequest(state, request2, opts?.timeout ?? DEFAULT_TIMEOUT);
439
528
  await sendBodyStream(state, streamId, bodyStream);
440
529
  const res = await responsePromise;
441
- return deserializeResponse(res.response);
530
+ return handleResponse(res);
442
531
  } else {
443
532
  const res = await sendRequest(state, request2, opts?.timeout ?? DEFAULT_TIMEOUT);
444
- return deserializeResponse(res.response);
533
+ return handleResponse(res);
445
534
  }
446
535
  },
447
536
  async getUpgradeRequest() {
@@ -656,6 +745,7 @@ async function createRuntime(state, options = {}) {
656
745
  return {
657
746
  id: isolateId,
658
747
  isolateId,
748
+ reused,
659
749
  fetch: fetchHandle,
660
750
  timers: timersHandle,
661
751
  console: consoleHandle,
@@ -789,7 +879,14 @@ function registerFsCallbacks(state, callbacks) {
789
879
  function registerModuleLoaderCallback(state, callback) {
790
880
  const callbackId = state.nextCallbackId++;
791
881
  state.callbacks.set(callbackId, async (moduleName) => {
792
- return callback(moduleName);
882
+ const specifier = moduleName;
883
+ const cached = state.moduleSourceCache.get(specifier);
884
+ if (cached !== undefined) {
885
+ return cached;
886
+ }
887
+ const source = await callback(specifier);
888
+ state.moduleSourceCache.set(specifier, source);
889
+ return source;
793
890
  });
794
891
  return { callbackId, name: "moduleLoader", type: "async" };
795
892
  }
@@ -1118,4 +1215,4 @@ export {
1118
1215
  connect
1119
1216
  };
1120
1217
 
1121
- //# debugId=8168995DA414336764756E2164756E21
1218
+ //# debugId=B4800C212010D2AD64756E2164756E21