@jxsuite/server 2.0.0 → 2.2.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/package.json +19 -16
- package/src/ai-api.ts +30 -13
- package/src/code-api.ts +2 -1
- package/src/collab.ts +35 -4
- package/src/data-api.ts +8 -1
- package/src/import-api.ts +5 -4
- package/src/jx-mounts.ts +11 -0
- package/src/net-guard.ts +188 -28
- package/src/oauth-loopback.ts +373 -0
- package/src/packages.ts +13 -2
- package/src/problem.ts +89 -0
- package/src/project-server.ts +124 -11
- package/src/refactor/apply.ts +3 -57
- package/src/refactor/find-refs.ts +0 -0
- package/src/refactor/index.ts +7 -0
- package/src/refactor/refs.ts +103 -63
- package/src/refactor/scan.ts +70 -0
- package/src/refactor/watcher.ts +4 -0
- package/src/resolve.ts +9 -10
- package/src/server.ts +36 -6
- package/src/site-output.ts +111 -0
- package/src/site-preview.ts +106 -0
- package/src/studio-api.ts +198 -89
- package/src/watch.ts +54 -3
package/src/watch.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { watch as chokidarWatch } from "chokidar";
|
|
|
4
4
|
import { relative } from "node:path";
|
|
5
5
|
import { rebuild } from "./build.ts";
|
|
6
6
|
import { coalesceFsEvents, toFsEvent } from "./refactor/fs-events.ts";
|
|
7
|
+
import { invalidateReferenceCache } from "./refactor/find-refs.ts";
|
|
7
8
|
import type { BuildEntry } from "./types.ts";
|
|
8
9
|
import type { FsEventPayload } from "./refactor/fs-events.ts";
|
|
9
10
|
|
|
@@ -49,6 +50,15 @@ function shouldIgnore(pathname: string, ignore: string[]) {
|
|
|
49
50
|
|
|
50
51
|
export { shouldIgnore };
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Reconnection time advertised on the stream, in milliseconds (HTML Standard, `retry:`).
|
|
55
|
+
*
|
|
56
|
+
* The default is user-agent defined and measured in seconds, which on a dev server restart is long
|
|
57
|
+
* enough for a save to look like it did nothing. Both endpoints are on loopback, so a reconnect
|
|
58
|
+
* costs nothing and half a second lands inside a typical restart.
|
|
59
|
+
*/
|
|
60
|
+
export const RECONNECT_MS = 500;
|
|
61
|
+
|
|
52
62
|
export const SSE_SCRIPT = `\n<script>new EventSource('/__reload').onmessage=()=>location.reload()</script>`;
|
|
53
63
|
|
|
54
64
|
/** @param {string} html */
|
|
@@ -71,7 +81,7 @@ export function injectSSE(html: string) {
|
|
|
71
81
|
* }} [opts]
|
|
72
82
|
* @returns {{
|
|
73
83
|
* broadcast: () => void;
|
|
74
|
-
* handleSSE: () => Response;
|
|
84
|
+
* handleSSE: (request?: Request) => Response;
|
|
75
85
|
* watcher: import("chokidar").FSWatcher;
|
|
76
86
|
* }}
|
|
77
87
|
*/
|
|
@@ -94,9 +104,23 @@ export function createWatcher(
|
|
|
94
104
|
const clients = new Set<(msg: string) => void>();
|
|
95
105
|
const encoder = new TextEncoder();
|
|
96
106
|
|
|
107
|
+
/*
|
|
108
|
+
* Monotonic reload counter, sent as the SSE `id:` of every reload frame.
|
|
109
|
+
*
|
|
110
|
+
* Its only job is to arm the browser: a stream that has never sent an `id:` makes the client omit
|
|
111
|
+
* `Last-Event-ID` on reconnect, and then the server cannot tell a first connection from a
|
|
112
|
+
* reconnection. **Nothing is buffered against it and nothing is replayed.** A reconnecting client
|
|
113
|
+
* gets exactly one reload, because the page it is holding was built before the disconnect and a
|
|
114
|
+
* full reload subsumes every event it missed — one is as correct as a hundred and finishes
|
|
115
|
+
* sooner. Do not "complete" this into a replay buffer; there is no per-event state to replay.
|
|
116
|
+
*/
|
|
117
|
+
let lastEventId = 0;
|
|
118
|
+
|
|
97
119
|
function broadcast() {
|
|
120
|
+
lastEventId += 1;
|
|
121
|
+
const frame = `id: ${lastEventId}\ndata: reload\n\n`;
|
|
98
122
|
for (const send of clients) {
|
|
99
|
-
send(
|
|
123
|
+
send(frame);
|
|
100
124
|
}
|
|
101
125
|
}
|
|
102
126
|
|
|
@@ -111,7 +135,24 @@ export function createWatcher(
|
|
|
111
135
|
}
|
|
112
136
|
}
|
|
113
137
|
|
|
114
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Open the live-reload stream.
|
|
140
|
+
*
|
|
141
|
+
* Two halves of the HTML Standard's EventSource contract that were missing, and are the whole of
|
|
142
|
+
* `gap:sse-reconnect`:
|
|
143
|
+
*
|
|
144
|
+
* - **`retry:`** sets the reconnection time. The default is user-agent defined and is measured in
|
|
145
|
+
* seconds; during a dev-server restart that is long enough to watch a save do nothing. 500ms is
|
|
146
|
+
* loopback, so the reconnect costs nothing and lands inside the restart.
|
|
147
|
+
* - **`Last-Event-ID`** is how the browser says "I was here before". It gets **one** reload and no
|
|
148
|
+
* replay — see the note on `lastEventId`. A dev server has no event log and needs none: the
|
|
149
|
+
* reload is idempotent and total.
|
|
150
|
+
*
|
|
151
|
+
* @param {Request} [request] - The stream request; read only for its `Last-Event-ID`
|
|
152
|
+
* @returns {Response}
|
|
153
|
+
*/
|
|
154
|
+
function handleSSE(request?: Request) {
|
|
155
|
+
const resuming = (request?.headers.get("Last-Event-ID") ?? "") !== "";
|
|
115
156
|
/** @type {((msg: string) => void) | undefined} */
|
|
116
157
|
let send: ((msg: string) => void) | undefined;
|
|
117
158
|
const stream = new ReadableStream({
|
|
@@ -127,6 +168,12 @@ export function createWatcher(
|
|
|
127
168
|
} catch {}
|
|
128
169
|
};
|
|
129
170
|
clients.add(send);
|
|
171
|
+
// The retry interval is a stream-level field, so it is set once, before any event.
|
|
172
|
+
send(`retry: ${RECONNECT_MS}\n\n`);
|
|
173
|
+
if (resuming) {
|
|
174
|
+
lastEventId += 1;
|
|
175
|
+
send(`id: ${lastEventId}\ndata: reload\n\n`);
|
|
176
|
+
}
|
|
130
177
|
const hb = setInterval(() => {
|
|
131
178
|
try {
|
|
132
179
|
c.enqueue(encoder.encode(": heartbeat\n\n"));
|
|
@@ -165,6 +212,10 @@ export function createWatcher(
|
|
|
165
212
|
return;
|
|
166
213
|
}
|
|
167
214
|
|
|
215
|
+
// The usage query's cache is invalidated by the filesystem, never by a timer — a delete
|
|
216
|
+
// Confirmation that says "used on 7 pages" must be answering about the tree as it is now.
|
|
217
|
+
invalidateReferenceCache(root);
|
|
218
|
+
|
|
168
219
|
// Structured FS events for the studio sidebar (coalesced + batched), emitted as a named "fs"
|
|
169
220
|
// SSE event so the preview iframe ignores them while the studio shell subscribes to them.
|
|
170
221
|
const fsEvent = toFsEvent(eventType, root, changedPath);
|