Skip to content

Commit 00e2cb8

Browse files
committed
create and increment dependency graph, error on circular deps
1 parent 5966a58 commit 00e2cb8

File tree

1 file changed

+21
-3
lines changed

1 file changed

+21
-3
lines changed

src/preview.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {getResolvers} from "./resolvers.js";
3333
import {bundleStyles, rollupClient} from "./rollup.js";
3434
import {searchIndex} from "./search.js";
3535
import {Telemetry} from "./telemetry.js";
36-
import {bold, faint, green, link} from "./tty.js";
36+
import {bold, faint, green, link, red} from "./tty.js";
3737

3838
export interface PreviewOptions {
3939
config?: string;
@@ -55,6 +55,7 @@ export class PreviewServer {
5555
private readonly _socketServer: WebSocketServer;
5656
private readonly _verbose: boolean;
5757
private readonly _effects: LoadEffects;
58+
private _dag: Map<string, Set<string>>;
5859

5960
private constructor({
6061
config,
@@ -77,6 +78,7 @@ export class PreviewServer {
7778
this._socketServer = new WebSocketServer({server: this._server});
7879
this._socketServer.on("connection", this._handleConnection);
7980
this._effects = effects;
81+
this._dag = new Map(); // TODO: read state
8082
}
8183

8284
static async start({verbose = true, hostname, port, open, ...options}: PreviewOptions) {
@@ -165,13 +167,14 @@ export class PreviewServer {
165167
} else if (pathname.startsWith("/_chain/")) {
166168
machine = true;
167169
const [caller, path] = pathname.slice("/_chain".length).split("::");
168-
// now any file that watches caller should also watch path
169-
console.warn("chained data loader", {caller, path, loaders});
170+
this.updateDag(caller, path);
171+
this._dag.delete(path); // reset path for this file
170172
const file = await this.getFile(path, root, loaders);
171173
if (file !== undefined) return void send(req, file, {root}).pipe(res);
172174
throw new HttpError(`Not found: ${pathname}`, 404);
173175
} else if (pathname.startsWith("/_file/")) {
174176
const path = pathname.slice("/_file".length);
177+
this._dag.delete(path); // reset path for this file
175178
const file = await this.getFile(path, root, loaders);
176179
if (file !== undefined) return void send(req, file, {root}).pipe(res);
177180
throw new HttpError(`Not found: ${pathname}`, 404);
@@ -263,6 +266,21 @@ export class PreviewServer {
263266
}
264267
}
265268
}
269+
270+
updateDag(caller: string, path: string) {
271+
if (!this._dag.has(caller)) this._dag.set(caller, new Set());
272+
this._dag.get(caller)!.add(path);
273+
const seen = new Set<string>();
274+
const q = [caller];
275+
while (true) {
276+
const node = q.shift();
277+
if (!node) return;
278+
if (seen.has(node))
279+
throw new Error(`${red("Circular dependency detected")}: ${[...seen, node].map(bold).join(" ← ")}`);
280+
q.push(...(this._dag.get(node) ?? []));
281+
seen.add(node);
282+
}
283+
}
266284
}
267285

268286
// Like send, but for in-memory dynamic content.

0 commit comments

Comments
 (0)