@hypen-space/core 0.4.25 → 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.
@@ -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
  /**
@@ -87,6 +91,7 @@ export class RemoteServer {
87
91
  private nextClientId = 1;
88
92
  private server: ReturnType<typeof Bun.serve> | null = null;
89
93
  private sessionManager: SessionManager | null = null;
94
+ private _syncActions: boolean = false;
90
95
 
91
96
  /**
92
97
  * Set the module for this app
@@ -141,6 +146,16 @@ export class RemoteServer {
141
146
  return this;
142
147
  }
143
148
 
149
+ /**
150
+ * Enable action synchronization across all connected clients.
151
+ * When enabled, an action dispatched by any client is also dispatched
152
+ * to every other client's engine, keeping all clients in sync.
153
+ */
154
+ syncActions(): this {
155
+ this._syncActions = true;
156
+ return this;
157
+ }
158
+
144
159
  /**
145
160
  * Register connection callback
146
161
  */
@@ -330,6 +345,7 @@ export class RemoteServer {
330
345
  connectedAt,
331
346
  sessionId: "",
332
347
  helloReceived: false,
348
+ stateSubscribed: false,
333
349
  };
334
350
 
335
351
  this.clients.set(ws, clientData);
@@ -486,14 +502,25 @@ export class RemoteServer {
486
502
 
487
503
  data.revision++;
488
504
 
489
- const message: PatchMessage = {
505
+ const patchMessage: PatchMessage = {
490
506
  type: "patch",
491
507
  module: this._moduleName,
492
508
  patches,
493
509
  revision: data.revision,
494
510
  };
495
511
 
496
- ws.send(JSON.stringify(message));
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
+ }
497
524
 
498
525
  // If allow-multiple, broadcast to other connections on same session
499
526
  if (
@@ -504,7 +531,18 @@ export class RemoteServer {
504
531
  if (connections) {
505
532
  for (const conn of connections) {
506
533
  if (conn !== ws) {
507
- (conn as ServerWebSocket<unknown>).send(JSON.stringify(message));
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
+ }
508
546
  }
509
547
  }
510
548
  }
@@ -615,6 +653,34 @@ export class RemoteServer {
615
653
  case "dispatchAction": {
616
654
  const actionMsg = msg as DispatchActionMessage;
617
655
  clientData.engine.dispatchAction(actionMsg.action, actionMsg.payload);
656
+
657
+ // Sync mode: dispatch to all other clients' engines
658
+ if (this._syncActions) {
659
+ for (const [otherWs, otherClient] of this.clients) {
660
+ if (otherWs === ws || !otherClient.helloReceived) continue;
661
+ otherClient.engine.dispatchAction(actionMsg.action, actionMsg.payload);
662
+ }
663
+ }
664
+ break;
665
+ }
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`);
618
684
  break;
619
685
  }
620
686
 
@@ -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
@@ -5,7 +5,7 @@
5
5
  "Hypen Contributors"
6
6
  ],
7
7
  "description": "A Rust implementation of the Hypen engine",
8
- "version": "0.4.25",
8
+ "version": "0.4.27",
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
Binary file
@@ -4,7 +4,7 @@
4
4
  "Hypen Contributors"
5
5
  ],
6
6
  "description": "A Rust implementation of the Hypen engine",
7
- "version": "0.4.25",
7
+ "version": "0.4.27",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",