@marimo-team/islands 0.19.5-dev12 → 0.19.5-dev13
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/dist/main.js +17 -2
- package/package.json +1 -1
- package/src/core/lsp/__tests__/transport.test.ts +149 -0
- package/src/core/lsp/transport.ts +48 -0
package/dist/main.js
CHANGED
|
@@ -32662,6 +32662,7 @@ ${c.sqlString}
|
|
|
32662
32662
|
super();
|
|
32663
32663
|
__publicField(this, "isClosed", false);
|
|
32664
32664
|
__publicField(this, "hasConnectedBefore", false);
|
|
32665
|
+
__publicField(this, "pendingSubscriptions", []);
|
|
32665
32666
|
this.options = e, this.delegate = void 0;
|
|
32666
32667
|
}
|
|
32667
32668
|
createDelegate() {
|
|
@@ -32670,7 +32671,9 @@ ${c.sqlString}
|
|
|
32670
32671
|
} catch (e) {
|
|
32671
32672
|
Logger.warn("Error closing old WebSocket delegate", e);
|
|
32672
32673
|
}
|
|
32673
|
-
|
|
32674
|
+
this.delegate = new import_build.WebSocketTransport(this.options.getWsUrl());
|
|
32675
|
+
for (let { event: e, handler: r } of this.pendingSubscriptions) this.delegate.subscribe(e, r);
|
|
32676
|
+
return this.delegate;
|
|
32674
32677
|
}
|
|
32675
32678
|
isDelegateClosedOrClosing() {
|
|
32676
32679
|
if (!this.delegate) return true;
|
|
@@ -32697,6 +32700,18 @@ ${c.sqlString}
|
|
|
32697
32700
|
var _a2;
|
|
32698
32701
|
this.isClosed = true, (_a2 = this.delegate) == null ? void 0 : _a2.close(), this.delegate = void 0, this.connectionPromise = void 0;
|
|
32699
32702
|
}
|
|
32703
|
+
subscribe(...e) {
|
|
32704
|
+
super.subscribe(...e);
|
|
32705
|
+
let [r, c] = e;
|
|
32706
|
+
this.pendingSubscriptions.push({
|
|
32707
|
+
event: r,
|
|
32708
|
+
handler: c
|
|
32709
|
+
}), this.delegate && this.delegate.subscribe(r, c);
|
|
32710
|
+
}
|
|
32711
|
+
unsubscribe(...e) {
|
|
32712
|
+
let r = super.unsubscribe(...e), [c, d] = e;
|
|
32713
|
+
return this.pendingSubscriptions = this.pendingSubscriptions.filter((e2) => !(e2.event === c && e2.handler === d)), this.delegate && this.delegate.unsubscribe(c, d), r;
|
|
32714
|
+
}
|
|
32700
32715
|
async sendData(e, r) {
|
|
32701
32716
|
var _a2;
|
|
32702
32717
|
if (this.isDelegateClosedOrClosing()) {
|
|
@@ -101063,7 +101078,7 @@ Defaulting to \`null\`.`;
|
|
|
101063
101078
|
return Logger.warn("Failed to get version from mount config"), null;
|
|
101064
101079
|
}
|
|
101065
101080
|
}
|
|
101066
|
-
const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.5-
|
|
101081
|
+
const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.5-dev13"), showCodeInRunModeAtom = atom(true);
|
|
101067
101082
|
atom(null);
|
|
101068
101083
|
var VIRTUAL_FILE_REGEX = /\/@file\/([^\s"&'/]+)\.([\dA-Za-z]+)/g, VirtualFileTracker = class e {
|
|
101069
101084
|
constructor() {
|
package/package.json
CHANGED
|
@@ -41,6 +41,8 @@ describe("ReconnectingWebSocketTransport", () => {
|
|
|
41
41
|
this.connect = vi.fn().mockResolvedValue(undefined);
|
|
42
42
|
this.close = vi.fn();
|
|
43
43
|
this.sendData = vi.fn().mockResolvedValue({ result: "success" });
|
|
44
|
+
this.subscribe = vi.fn();
|
|
45
|
+
this.unsubscribe = vi.fn();
|
|
44
46
|
});
|
|
45
47
|
});
|
|
46
48
|
|
|
@@ -287,4 +289,151 @@ describe("ReconnectingWebSocketTransport", () => {
|
|
|
287
289
|
"Reconnect callback failed",
|
|
288
290
|
);
|
|
289
291
|
});
|
|
292
|
+
|
|
293
|
+
describe("subscribe", () => {
|
|
294
|
+
it("should track subscriptions", () => {
|
|
295
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
296
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
297
|
+
|
|
298
|
+
const handler = vi.fn();
|
|
299
|
+
transport.subscribe("notification", handler);
|
|
300
|
+
|
|
301
|
+
expect((transport as any).pendingSubscriptions).toHaveLength(1);
|
|
302
|
+
expect((transport as any).pendingSubscriptions[0]).toEqual({
|
|
303
|
+
event: "notification",
|
|
304
|
+
handler,
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("should register handler on delegate if it exists", async () => {
|
|
309
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
310
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
311
|
+
|
|
312
|
+
await transport.connect();
|
|
313
|
+
|
|
314
|
+
const handler = vi.fn();
|
|
315
|
+
transport.subscribe("notification", handler);
|
|
316
|
+
|
|
317
|
+
const delegate = (transport as any).delegate;
|
|
318
|
+
expect(delegate.subscribe).toHaveBeenCalledWith("notification", handler);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should register pending subscriptions when delegate is created", async () => {
|
|
322
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
323
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
324
|
+
|
|
325
|
+
const handler1 = vi.fn();
|
|
326
|
+
const handler2 = vi.fn();
|
|
327
|
+
transport.subscribe("notification", handler1);
|
|
328
|
+
transport.subscribe("response", handler2);
|
|
329
|
+
|
|
330
|
+
await transport.connect();
|
|
331
|
+
|
|
332
|
+
const delegate = (transport as any).delegate;
|
|
333
|
+
expect(delegate.subscribe).toHaveBeenCalledWith("notification", handler1);
|
|
334
|
+
expect(delegate.subscribe).toHaveBeenCalledWith("response", handler2);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should re-register subscriptions on reconnection", async () => {
|
|
338
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
339
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
340
|
+
|
|
341
|
+
// Add subscription before connection
|
|
342
|
+
const handler = vi.fn();
|
|
343
|
+
transport.subscribe("notification", handler);
|
|
344
|
+
|
|
345
|
+
// First connection
|
|
346
|
+
await transport.connect();
|
|
347
|
+
const firstDelegate = (transport as any).delegate;
|
|
348
|
+
expect(firstDelegate.subscribe).toHaveBeenCalledWith(
|
|
349
|
+
"notification",
|
|
350
|
+
handler,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Clear mock calls
|
|
354
|
+
firstDelegate.subscribe.mockClear();
|
|
355
|
+
|
|
356
|
+
// Simulate connection loss
|
|
357
|
+
mockConnection.readyState = WebSocket.CLOSED;
|
|
358
|
+
|
|
359
|
+
// Reconnect by sending data
|
|
360
|
+
const data: any = { method: "test", params: [] };
|
|
361
|
+
await transport.sendData(data, 5000);
|
|
362
|
+
|
|
363
|
+
// New delegate should have been created
|
|
364
|
+
const secondDelegate = (transport as any).delegate;
|
|
365
|
+
expect(secondDelegate).not.toBe(firstDelegate);
|
|
366
|
+
|
|
367
|
+
// Subscription should be re-registered on new delegate
|
|
368
|
+
expect(secondDelegate.subscribe).toHaveBeenCalledWith(
|
|
369
|
+
"notification",
|
|
370
|
+
handler,
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe("unsubscribe", () => {
|
|
376
|
+
it("should remove subscription from tracking", () => {
|
|
377
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
378
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
379
|
+
|
|
380
|
+
const handler = vi.fn();
|
|
381
|
+
transport.subscribe("notification", handler);
|
|
382
|
+
expect((transport as any).pendingSubscriptions).toHaveLength(1);
|
|
383
|
+
|
|
384
|
+
transport.unsubscribe("notification", handler);
|
|
385
|
+
expect((transport as any).pendingSubscriptions).toHaveLength(0);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("should unregister from delegate if it exists", async () => {
|
|
389
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
390
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
391
|
+
|
|
392
|
+
await transport.connect();
|
|
393
|
+
|
|
394
|
+
const handler = vi.fn();
|
|
395
|
+
transport.subscribe("notification", handler);
|
|
396
|
+
|
|
397
|
+
const delegate = (transport as any).delegate;
|
|
398
|
+
delegate.unsubscribe.mockClear();
|
|
399
|
+
|
|
400
|
+
transport.unsubscribe("notification", handler);
|
|
401
|
+
|
|
402
|
+
expect(delegate.unsubscribe).toHaveBeenCalledWith(
|
|
403
|
+
"notification",
|
|
404
|
+
handler,
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("should not re-register unsubscribed handlers on reconnection", async () => {
|
|
409
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
410
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
411
|
+
|
|
412
|
+
const handler1 = vi.fn();
|
|
413
|
+
const handler2 = vi.fn();
|
|
414
|
+
transport.subscribe("notification", handler1);
|
|
415
|
+
transport.subscribe("response", handler2);
|
|
416
|
+
|
|
417
|
+
await transport.connect();
|
|
418
|
+
|
|
419
|
+
// Unsubscribe handler1
|
|
420
|
+
transport.unsubscribe("notification", handler1);
|
|
421
|
+
|
|
422
|
+
// Simulate connection loss
|
|
423
|
+
mockConnection.readyState = WebSocket.CLOSED;
|
|
424
|
+
|
|
425
|
+
// Reconnect by sending data
|
|
426
|
+
const data: any = { method: "test", params: [] };
|
|
427
|
+
await transport.sendData(data, 5000);
|
|
428
|
+
|
|
429
|
+
const newDelegate = (transport as any).delegate;
|
|
430
|
+
|
|
431
|
+
// Only handler2 should be registered on the new delegate
|
|
432
|
+
expect(newDelegate.subscribe).not.toHaveBeenCalledWith(
|
|
433
|
+
"notification",
|
|
434
|
+
handler1,
|
|
435
|
+
);
|
|
436
|
+
expect(newDelegate.subscribe).toHaveBeenCalledWith("response", handler2);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
290
439
|
});
|
|
@@ -23,6 +23,11 @@ export interface ReconnectingWebSocketTransportOptions {
|
|
|
23
23
|
onReconnect?: () => Promise<void>;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
interface Subscription {
|
|
27
|
+
event: "pending" | "notification" | "response" | "error";
|
|
28
|
+
handler: Parameters<Transport["subscribe"]>[1];
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
/**
|
|
27
32
|
* A WebSocket transport that automatically reconnects when the connection is lost.
|
|
28
33
|
* This handles cases like computer sleep/wake or network interruptions.
|
|
@@ -33,6 +38,7 @@ export class ReconnectingWebSocketTransport extends Transport {
|
|
|
33
38
|
private connectionPromise: Promise<void> | undefined;
|
|
34
39
|
private isClosed = false;
|
|
35
40
|
private hasConnectedBefore = false;
|
|
41
|
+
private pendingSubscriptions: Subscription[] = [];
|
|
36
42
|
|
|
37
43
|
constructor(options: ReconnectingWebSocketTransportOptions) {
|
|
38
44
|
super();
|
|
@@ -55,6 +61,12 @@ export class ReconnectingWebSocketTransport extends Transport {
|
|
|
55
61
|
|
|
56
62
|
// Create a new delegate
|
|
57
63
|
this.delegate = new WebSocketTransport(this.options.getWsUrl());
|
|
64
|
+
|
|
65
|
+
// Re-register all pending subscriptions on the new delegate
|
|
66
|
+
for (const { event, handler } of this.pendingSubscriptions) {
|
|
67
|
+
this.delegate.subscribe(event, handler);
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
return this.delegate;
|
|
59
71
|
}
|
|
60
72
|
|
|
@@ -134,6 +146,42 @@ export class ReconnectingWebSocketTransport extends Transport {
|
|
|
134
146
|
this.connectionPromise = undefined;
|
|
135
147
|
}
|
|
136
148
|
|
|
149
|
+
override subscribe(...args: Parameters<Transport["subscribe"]>): void {
|
|
150
|
+
// Register handler on parent Transport
|
|
151
|
+
super.subscribe(...args);
|
|
152
|
+
|
|
153
|
+
const [event, handler] = args;
|
|
154
|
+
|
|
155
|
+
// Track the subscription
|
|
156
|
+
this.pendingSubscriptions.push({ event, handler });
|
|
157
|
+
|
|
158
|
+
// Also register on delegate if it exists
|
|
159
|
+
if (this.delegate) {
|
|
160
|
+
this.delegate.subscribe(event, handler);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override unsubscribe(
|
|
165
|
+
...args: Parameters<Transport["unsubscribe"]>
|
|
166
|
+
): import("events").EventEmitter | undefined {
|
|
167
|
+
// Unregister from parent
|
|
168
|
+
const result = super.unsubscribe(...args);
|
|
169
|
+
|
|
170
|
+
const [event, handler] = args;
|
|
171
|
+
|
|
172
|
+
// Remove from pending subscriptions
|
|
173
|
+
this.pendingSubscriptions = this.pendingSubscriptions.filter(
|
|
174
|
+
(sub) => !(sub.event === event && sub.handler === handler),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Also unregister from delegate if it exists
|
|
178
|
+
if (this.delegate) {
|
|
179
|
+
this.delegate.unsubscribe(event, handler);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
137
185
|
override async sendData(
|
|
138
186
|
data: JSONRPCRequestData,
|
|
139
187
|
timeout: number | null | undefined,
|