@hypen-space/core 0.4.26 → 0.4.27
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/index.browser.js +21 -1
- package/dist/index.browser.js.map +3 -3
- package/dist/index.js +21 -1
- package/dist/index.js.map +3 -3
- package/dist/remote/client.d.ts +10 -0
- package/dist/remote/client.js +21 -1
- package/dist/remote/client.js.map +3 -3
- package/dist/remote/index.js +63 -5
- package/dist/remote/index.js.map +4 -4
- package/dist/remote/server.js +43 -5
- package/dist/remote/server.js.map +3 -3
- package/dist/remote/types.d.ts +18 -1
- package/package.json +2 -1
- package/src/remote/client.ts +35 -0
- package/src/remote/server.ts +50 -3
- package/src/remote/types.ts +21 -0
- package/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-browser/package.json +1 -1
- package/wasm-node/hypen_engine_bg.wasm +0 -0
- package/wasm-node/package.json +1 -1
package/dist/remote/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Remote UI Protocol Types
|
|
3
3
|
*/
|
|
4
4
|
import type { Patch } from "../types.js";
|
|
5
|
-
export type RemoteMessage = InitialTreeMessage | PatchMessage | StateUpdateMessage | DispatchActionMessage | HelloMessage | SessionAckMessage | SessionExpiredMessage;
|
|
5
|
+
export type RemoteMessage = InitialTreeMessage | PatchMessage | StateUpdateMessage | UpdateStateMessage | SubscribeStateMessage | DispatchActionMessage | HelloMessage | SessionAckMessage | SessionExpiredMessage;
|
|
6
6
|
export interface InitialTreeMessage {
|
|
7
7
|
type: "initialTree";
|
|
8
8
|
module: string;
|
|
@@ -28,6 +28,23 @@ export interface DispatchActionMessage {
|
|
|
28
28
|
action: string;
|
|
29
29
|
payload?: any;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Client → Server: Override the module's state (e.g. for time-travel)
|
|
33
|
+
* Server will update the module instance and re-render, sending back
|
|
34
|
+
* patch + stateUpdate messages.
|
|
35
|
+
*/
|
|
36
|
+
export interface UpdateStateMessage {
|
|
37
|
+
type: "updateState";
|
|
38
|
+
module: string;
|
|
39
|
+
state: any;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Client → Server: Opt-in to receiving stateUpdate messages after each render.
|
|
43
|
+
* Only subscribed clients receive state snapshots (e.g. Studio for time-travel).
|
|
44
|
+
*/
|
|
45
|
+
export interface SubscribeStateMessage {
|
|
46
|
+
type: "subscribeState";
|
|
47
|
+
}
|
|
31
48
|
/**
|
|
32
49
|
* Client → Server: First message after WebSocket opens
|
|
33
50
|
* Used to establish or resume a session
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypen-space/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.27",
|
|
4
4
|
"description": "Hypen core engine - Platform-agnostic reactive UI runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"./remote/client": {
|
|
86
86
|
"types": "./dist/remote/client.d.ts",
|
|
87
87
|
"bun": "./src/remote/client.ts",
|
|
88
|
+
"browser": "./src/remote/client.ts",
|
|
88
89
|
"import": "./dist/remote/client.js",
|
|
89
90
|
"default": "./dist/remote/client.js"
|
|
90
91
|
},
|
package/src/remote/client.ts
CHANGED
|
@@ -32,6 +32,8 @@ import type {
|
|
|
32
32
|
InitialTreeMessage,
|
|
33
33
|
PatchMessage,
|
|
34
34
|
DispatchActionMessage,
|
|
35
|
+
UpdateStateMessage,
|
|
36
|
+
SubscribeStateMessage,
|
|
35
37
|
HelloMessage,
|
|
36
38
|
SessionAckMessage,
|
|
37
39
|
SessionExpiredMessage,
|
|
@@ -278,6 +280,39 @@ export class RemoteEngine implements Disposable {
|
|
|
278
280
|
this.ws.send(JSON.stringify(message));
|
|
279
281
|
}
|
|
280
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Subscribe to state updates after each render.
|
|
285
|
+
* Only subscribed clients receive stateUpdate messages (avoids overhead for native clients).
|
|
286
|
+
*/
|
|
287
|
+
subscribeState(): void {
|
|
288
|
+
if (this.state !== "connected" || !this.ws) {
|
|
289
|
+
log.warn("Cannot subscribe to state: not connected");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const message: SubscribeStateMessage = { type: "subscribeState" };
|
|
294
|
+
this.ws.send(JSON.stringify(message));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Override the remote module's state (e.g. for time-travel).
|
|
299
|
+
* The server will update state, re-render, and send back patches.
|
|
300
|
+
*/
|
|
301
|
+
updateState(state: unknown): void {
|
|
302
|
+
if (this.state !== "connected" || !this.ws) {
|
|
303
|
+
log.warn("Cannot update state: not connected");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const message: UpdateStateMessage = {
|
|
308
|
+
type: "updateState",
|
|
309
|
+
module: this.moduleName,
|
|
310
|
+
state,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
this.ws.send(JSON.stringify(message));
|
|
314
|
+
}
|
|
315
|
+
|
|
281
316
|
/**
|
|
282
317
|
* Register callback for patches
|
|
283
318
|
*/
|
package/src/remote/server.ts
CHANGED
|
@@ -37,6 +37,8 @@ import type {
|
|
|
37
37
|
RemoteServerConfig,
|
|
38
38
|
InitialTreeMessage,
|
|
39
39
|
PatchMessage,
|
|
40
|
+
StateUpdateMessage,
|
|
41
|
+
UpdateStateMessage,
|
|
40
42
|
DispatchActionMessage,
|
|
41
43
|
HelloMessage,
|
|
42
44
|
SessionAckMessage,
|
|
@@ -68,6 +70,8 @@ interface ClientData {
|
|
|
68
70
|
helloReceived: boolean;
|
|
69
71
|
/** Timeout for legacy clients that don't send hello */
|
|
70
72
|
helloTimeout?: ReturnType<typeof setTimeout>;
|
|
73
|
+
/** Whether this client subscribes to state updates after each render */
|
|
74
|
+
stateSubscribed: boolean;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
/**
|
|
@@ -341,6 +345,7 @@ export class RemoteServer {
|
|
|
341
345
|
connectedAt,
|
|
342
346
|
sessionId: "",
|
|
343
347
|
helloReceived: false,
|
|
348
|
+
stateSubscribed: false,
|
|
344
349
|
};
|
|
345
350
|
|
|
346
351
|
this.clients.set(ws, clientData);
|
|
@@ -497,14 +502,25 @@ export class RemoteServer {
|
|
|
497
502
|
|
|
498
503
|
data.revision++;
|
|
499
504
|
|
|
500
|
-
const
|
|
505
|
+
const patchMessage: PatchMessage = {
|
|
501
506
|
type: "patch",
|
|
502
507
|
module: this._moduleName,
|
|
503
508
|
patches,
|
|
504
509
|
revision: data.revision,
|
|
505
510
|
};
|
|
506
511
|
|
|
507
|
-
ws.send(JSON.stringify(
|
|
512
|
+
ws.send(JSON.stringify(patchMessage));
|
|
513
|
+
|
|
514
|
+
// Only send state snapshot to clients that opted in (e.g. Studio)
|
|
515
|
+
if (data.stateSubscribed) {
|
|
516
|
+
const stateMessage: StateUpdateMessage = {
|
|
517
|
+
type: "stateUpdate",
|
|
518
|
+
module: this._moduleName,
|
|
519
|
+
state: data.moduleInstance.getState(),
|
|
520
|
+
revision: data.revision,
|
|
521
|
+
};
|
|
522
|
+
ws.send(JSON.stringify(stateMessage));
|
|
523
|
+
}
|
|
508
524
|
|
|
509
525
|
// If allow-multiple, broadcast to other connections on same session
|
|
510
526
|
if (
|
|
@@ -515,7 +531,18 @@ export class RemoteServer {
|
|
|
515
531
|
if (connections) {
|
|
516
532
|
for (const conn of connections) {
|
|
517
533
|
if (conn !== ws) {
|
|
518
|
-
|
|
534
|
+
const otherWs = conn as ServerWebSocket<unknown>;
|
|
535
|
+
const otherData = this.clients.get(otherWs);
|
|
536
|
+
otherWs.send(JSON.stringify(patchMessage));
|
|
537
|
+
if (otherData?.stateSubscribed) {
|
|
538
|
+
const stateMessage: StateUpdateMessage = {
|
|
539
|
+
type: "stateUpdate",
|
|
540
|
+
module: this._moduleName,
|
|
541
|
+
state: data.moduleInstance.getState(),
|
|
542
|
+
revision: data.revision,
|
|
543
|
+
};
|
|
544
|
+
otherWs.send(JSON.stringify(stateMessage));
|
|
545
|
+
}
|
|
519
546
|
}
|
|
520
547
|
}
|
|
521
548
|
}
|
|
@@ -637,6 +664,26 @@ export class RemoteServer {
|
|
|
637
664
|
break;
|
|
638
665
|
}
|
|
639
666
|
|
|
667
|
+
case "updateState": {
|
|
668
|
+
const stateMsg = msg as UpdateStateMessage;
|
|
669
|
+
clientData.moduleInstance.updateState(stateMsg.state);
|
|
670
|
+
|
|
671
|
+
// Sync mode: update state on all other clients' engines
|
|
672
|
+
if (this._syncActions) {
|
|
673
|
+
for (const [otherWs, otherClient] of this.clients) {
|
|
674
|
+
if (otherWs === ws || !otherClient.helloReceived) continue;
|
|
675
|
+
otherClient.moduleInstance.updateState(stateMsg.state);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
case "subscribeState": {
|
|
682
|
+
clientData.stateSubscribed = true;
|
|
683
|
+
log.info(`Client ${clientData.id} subscribed to state updates`);
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
|
|
640
687
|
default:
|
|
641
688
|
// Unknown message type
|
|
642
689
|
break;
|
package/src/remote/types.ts
CHANGED
|
@@ -8,6 +8,8 @@ export type RemoteMessage =
|
|
|
8
8
|
| InitialTreeMessage
|
|
9
9
|
| PatchMessage
|
|
10
10
|
| StateUpdateMessage
|
|
11
|
+
| UpdateStateMessage
|
|
12
|
+
| SubscribeStateMessage
|
|
11
13
|
| DispatchActionMessage
|
|
12
14
|
| HelloMessage
|
|
13
15
|
| SessionAckMessage
|
|
@@ -42,6 +44,25 @@ export interface DispatchActionMessage {
|
|
|
42
44
|
payload?: any;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Client → Server: Override the module's state (e.g. for time-travel)
|
|
49
|
+
* Server will update the module instance and re-render, sending back
|
|
50
|
+
* patch + stateUpdate messages.
|
|
51
|
+
*/
|
|
52
|
+
export interface UpdateStateMessage {
|
|
53
|
+
type: "updateState";
|
|
54
|
+
module: string;
|
|
55
|
+
state: any;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Client → Server: Opt-in to receiving stateUpdate messages after each render.
|
|
60
|
+
* Only subscribed clients receive state snapshots (e.g. Studio for time-travel).
|
|
61
|
+
*/
|
|
62
|
+
export interface SubscribeStateMessage {
|
|
63
|
+
type: "subscribeState";
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
/**
|
|
46
67
|
* Client → Server: First message after WebSocket opens
|
|
47
68
|
* Used to establish or resume a session
|
|
Binary file
|
|
Binary file
|