@jxsuite/server 4.5.0 → 4.5.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.
@@ -29,6 +29,8 @@
29
29
 
30
30
  import { existsSync, readFileSync, statSync } from "node:fs";
31
31
  import { join, resolve } from "node:path";
32
+ import { isJsonObject, isString } from "@jxsuite/schema/guards";
33
+ import type { JsonValue } from "@jxsuite/schema/types";
32
34
 
33
35
  import { serveContained } from "./net-guard.ts";
34
36
 
@@ -60,11 +62,15 @@ export function siteOutDir(projectRoot: string): string | null {
60
62
  }
61
63
  let outDir: string | null = null;
62
64
  try {
63
- const config = JSON.parse(readFileSync(configPath, "utf8")) as {
64
- build?: { outDir?: unknown };
65
- };
66
- const declared = config.build?.outDir;
67
- outDir = resolve(projectRoot, typeof declared === "string" && declared ? declared : "dist");
65
+ // SAFETY: JSON.parse without a reviver only ever yields a JSON value; its `any` says no more.
66
+ const config = JSON.parse(readFileSync(configPath, "utf8")) as JsonValue;
67
+ const build = isJsonObject(config) ? config.build : undefined;
68
+ const declared = isJsonObject(build) ? build.outDir : undefined;
69
+ // A `null` document has no settings to read at all, so it declines like an unparseable one.
70
+ outDir =
71
+ config === null
72
+ ? null
73
+ : resolve(projectRoot, isString(declared) && declared ? declared : "dist");
68
74
  } catch {
69
75
  // An unparseable project.json is the editor's problem to report, not this server's to guess
70
76
  // At. No output directory means this fallback simply declines, and the request 404s as before.
package/src/sse.ts CHANGED
@@ -21,6 +21,9 @@
21
21
  * open, dropped on cancel — so a broadcast after a tab closes reaches nobody rather than throwing.
22
22
  */
23
23
 
24
+ import type { JsonValue } from "@jxsuite/schema/types";
25
+ import type { FsEventPayload } from "./refactor/fs-events.ts";
26
+
24
27
  /**
25
28
  * Reconnection time advertised on the stream, in milliseconds (HTML Standard, `retry:`).
26
29
  *
@@ -32,6 +35,17 @@ export const RECONNECT_MS = 500;
32
35
  /** How often a comment frame is sent to keep an idle connection from being reaped. */
33
36
  const HEARTBEAT_MS = 15_000;
34
37
 
38
+ /** The `fs` event's data: one debounced batch of filesystem changes for the studio sidebar. */
39
+ export interface FsEventBatch {
40
+ events: FsEventPayload[];
41
+ }
42
+
43
+ /**
44
+ * What a named event carries on its `data:` line, serialized as JSON. The `fs` batch is named
45
+ * because its changes are typed as an interface, which TypeScript never treats as a `JsonValue`.
46
+ */
47
+ export type SseEventData = JsonValue | FsEventBatch;
48
+
35
49
  /** A broadcast hub for one stream. */
36
50
  export interface SseHub {
37
51
  /** Send the default (unnamed) `reload` message, stamped with the next `id:`. */
@@ -43,7 +57,7 @@ export interface SseHub {
43
57
  * listener that asked for it without triggering everyone else's reload. That is what lets the
44
58
  * studio shell subscribe to `fs` on the same connection the preview reloads on.
45
59
  */
46
- broadcastEvent: (event: string, payload: unknown) => void;
60
+ broadcastEvent: (event: string, payload: SseEventData) => void;
47
61
  /** Open the stream. Pass the request so `Last-Event-ID` can be read. */
48
62
  handleSSE: (request?: Request) => Response;
49
63
  /** How many clients are connected right now. */
@@ -75,7 +89,7 @@ export function createSseHub(): SseHub {
75
89
  }
76
90
  }
77
91
 
78
- function broadcastEvent(event: string, payload: unknown) {
92
+ function broadcastEvent(event: string, payload: SseEventData) {
79
93
  const frame = `event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`;
80
94
  for (const send of clients) {
81
95
  send(frame);