@jxsuite/server 0.28.5 → 0.30.1

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/src/watch.ts CHANGED
@@ -3,7 +3,9 @@
3
3
  import { watch as chokidarWatch } from "chokidar";
4
4
  import { relative } from "node:path";
5
5
  import { rebuild } from "./build.ts";
6
+ import { coalesceFsEvents, toFsEvent } from "./refactor/fs-events.ts";
6
7
  import type { BuildEntry } from "./types.ts";
8
+ import type { FsEventPayload } from "./refactor/fs-events.ts";
7
9
 
8
10
  const DEFAULT_IGNORE = [
9
11
  "**/node_modules/**",
@@ -86,6 +88,17 @@ export function createWatcher(
86
88
  }
87
89
  }
88
90
 
91
+ /**
92
+ * Send a _named_ SSE event. The preview iframe only listens to the default (unnamed) `onmessage`,
93
+ * so named events (e.g. "fs") reach the studio shell without triggering a preview reload.
94
+ */
95
+ function broadcastEvent(event: string, payload: unknown) {
96
+ const frame = `event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`;
97
+ for (const send of clients) {
98
+ send(frame);
99
+ }
100
+ }
101
+
89
102
  function handleSSE() {
90
103
  /** @type {((msg: string) => void) | undefined} */
91
104
  let send: ((msg: string) => void) | undefined;
@@ -121,6 +134,9 @@ export function createWatcher(
121
134
  }
122
135
 
123
136
  let timer: ReturnType<typeof setTimeout> | null = null;
137
+ let fsTimer: ReturnType<typeof setTimeout> | null = null;
138
+ let fsBuffer: FsEventPayload[] = [];
139
+ const fsDebounceMs = 40;
124
140
  const watcher = chokidarWatch(root, {
125
141
  awaitWriteFinish: {
126
142
  pollInterval: 10,
@@ -131,11 +147,26 @@ export function createWatcher(
131
147
  ignored: (watchedPath) => shouldIgnore(watchedPath, ignore),
132
148
  });
133
149
 
134
- watcher.on("all", (_, changedPath) => {
150
+ watcher.on("all", (eventType, changedPath) => {
135
151
  const filename = relative(root, changedPath);
136
152
  if (!filename || filename.startsWith("..")) {
137
153
  return;
138
154
  }
155
+
156
+ // Structured FS events for the studio sidebar (coalesced + batched), emitted as a named "fs"
157
+ // SSE event so the preview iframe ignores them while the studio shell subscribes to them.
158
+ const fsEvent = toFsEvent(eventType, root, changedPath);
159
+ if (fsEvent) {
160
+ fsBuffer = coalesceFsEvents(fsBuffer, fsEvent);
161
+ clearTimeout(fsTimer ?? undefined);
162
+ fsTimer = setTimeout(() => {
163
+ if (fsBuffer.length > 0) {
164
+ broadcastEvent("fs", { events: fsBuffer });
165
+ fsBuffer = [];
166
+ }
167
+ }, fsDebounceMs);
168
+ }
169
+
139
170
  clearTimeout(timer ?? undefined);
140
171
  timer = setTimeout(async () => {
141
172
  if (builds.length > 0) {
@@ -155,5 +186,5 @@ export function createWatcher(
155
186
  }, debounceMs);
156
187
  });
157
188
 
158
- return { broadcast, handleSSE, watcher };
189
+ return { broadcast, broadcastEvent, handleSSE, watcher };
159
190
  }