Skip to content

Commit f68d9cd

Browse files
authored
Move OpenAPI file watching into server/openapi-watcher.ts and load-openapi-document.ts
Agent-Logs-Url: https://github.com/counterfact/api-simulator/sessions/ec10975a-392f-4945-ab3c-cb4e22edf971
1 parent f967106 commit f68d9cd

3 files changed

Lines changed: 82 additions & 51 deletions

File tree

src/app.ts

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
11
import fs, { rm } from "node:fs/promises";
22
import nodePath from "node:path";
33

4-
import { dereference } from "@apidevtools/json-schema-ref-parser";
5-
import { type FSWatcher, watch as watchFile } from "chokidar";
6-
import createDebug from "debug";
74
import { createHttpTerminator, type HttpTerminator } from "http-terminator";
85

96
import { startRepl as startReplServer } from "./repl/repl.js";
107
import type { Config } from "./server/config.js";
11-
import { CHOKIDAR_OPTIONS } from "./server/constants.js";
128
import { ContextRegistry } from "./server/context-registry.js";
139
import { createKoaApp } from "./server/create-koa-app.js";
14-
import {
15-
Dispatcher,
16-
type DispatcherRequest,
17-
type OpenApiDocument,
18-
} from "./server/dispatcher.js";
10+
import { Dispatcher, type DispatcherRequest } from "./server/dispatcher.js";
1911
import { koaMiddleware } from "./server/koa-middleware.js";
12+
import { loadOpenApiDocument } from "./server/load-openapi-document.js";
2013
import { ModuleLoader } from "./server/module-loader.js";
14+
import { OpenApiWatcher } from "./server/openapi-watcher.js";
2115
import { Registry } from "./server/registry.js";
2216
import { Transpiler } from "./server/transpiler.js";
2317
import { CodeGenerator } from "./typescript-generator/code-generator.js";
2418
import { runtimeCanExecuteErasableTs } from "./util/runtime-can-execute-erasable-ts.js";
2519

26-
const debug = createDebug("counterfact:app");
20+
export { loadOpenApiDocument } from "./server/load-openapi-document.js";
2721

2822
type MswHandlerMap = {
2923
[key: string]: (request: MockRequest) => Promise<unknown>;
@@ -41,19 +35,6 @@ const allowedMethods = [
4135

4236
export type MockRequest = DispatcherRequest & { rawPath: string };
4337

44-
export async function loadOpenApiDocument(source: string) {
45-
try {
46-
return (await dereference(source)) as OpenApiDocument;
47-
} catch (error) {
48-
debug("could not load OpenAPI document from %s: %o", source, error);
49-
const details = error instanceof Error ? error.message : String(error);
50-
throw new Error(
51-
`Could not load the OpenAPI spec from "${source}".\n${details}`,
52-
{ cause: error },
53-
);
54-
}
55-
}
56-
5738
const mswHandlers: MswHandlerMap = {};
5839

5940
export async function handleMswRequest(request: MockRequest) {
@@ -167,6 +148,8 @@ export async function counterfact(config: Config) {
167148

168149
const koaApp = createKoaApp(registry, middleware, config, contextRegistry);
169150

151+
const openApiWatcher = new OpenApiWatcher(config.openApiPath, dispatcher);
152+
170153
async function start(options: Config) {
171154
const { generate, startServer, watch, buildCache } = options;
172155

@@ -179,35 +162,10 @@ export async function counterfact(config: Config) {
179162
}
180163

181164
let httpTerminator: HttpTerminator | undefined;
182-
let openApiWatcher: FSWatcher | undefined;
183-
184-
if (
185-
startServer &&
186-
config.openApiPath !== "_" &&
187-
!config.openApiPath.startsWith("http")
188-
) {
189-
openApiWatcher = watchFile(config.openApiPath, CHOKIDAR_OPTIONS).on(
190-
"change",
191-
() => {
192-
void (async () => {
193-
try {
194-
dispatcher.openApiDocument = await loadOpenApiDocument(
195-
config.openApiPath,
196-
);
197-
debug("reloaded OpenAPI document from %s", config.openApiPath);
198-
} catch (error: unknown) {
199-
debug(
200-
"failed to reload OpenAPI document from %s: %o",
201-
config.openApiPath,
202-
error,
203-
);
204-
}
205-
})();
206-
},
207-
);
208-
}
209165

210166
if (startServer) {
167+
await openApiWatcher.watch();
168+
211169
if (!nativeTs) {
212170
await transpiler.watch();
213171
}
@@ -232,7 +190,7 @@ export async function counterfact(config: Config) {
232190
await codeGenerator.stopWatching();
233191
await transpiler.stopWatching();
234192
await moduleLoader.stopWatching();
235-
await openApiWatcher?.close();
193+
await openApiWatcher.stopWatching();
236194
await httpTerminator?.terminate();
237195
},
238196
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { dereference } from "@apidevtools/json-schema-ref-parser";
2+
import createDebug from "debug";
3+
4+
import type { OpenApiDocument } from "./dispatcher.js";
5+
6+
const debug = createDebug("counterfact:server:load-openapi-document");
7+
8+
export async function loadOpenApiDocument(source: string) {
9+
try {
10+
return (await dereference(source)) as OpenApiDocument;
11+
} catch (error) {
12+
debug("could not load OpenAPI document from %s: %o", source, error);
13+
const details = error instanceof Error ? error.message : String(error);
14+
throw new Error(
15+
`Could not load the OpenAPI spec from "${source}".\n${details}`,
16+
{ cause: error },
17+
);
18+
}
19+
}

src/server/openapi-watcher.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { type FSWatcher, watch } from "chokidar";
2+
import createDebug from "debug";
3+
4+
import { waitForEvent } from "../util/wait-for-event.js";
5+
import { CHOKIDAR_OPTIONS } from "./constants.js";
6+
import type { Dispatcher } from "./dispatcher.js";
7+
import { loadOpenApiDocument } from "./load-openapi-document.js";
8+
9+
const debug = createDebug("counterfact:server:openapi-watcher");
10+
11+
export class OpenApiWatcher {
12+
private readonly openApiPath: string;
13+
14+
private readonly dispatcher: Dispatcher;
15+
16+
private watcher: FSWatcher | undefined;
17+
18+
public constructor(openApiPath: string, dispatcher: Dispatcher) {
19+
this.openApiPath = openApiPath;
20+
this.dispatcher = dispatcher;
21+
}
22+
23+
public async watch(): Promise<void> {
24+
if (this.openApiPath === "_" || this.openApiPath.startsWith("http")) {
25+
return;
26+
}
27+
28+
this.watcher = watch(this.openApiPath, CHOKIDAR_OPTIONS).on(
29+
"change",
30+
() => {
31+
void (async () => {
32+
try {
33+
this.dispatcher.openApiDocument = await loadOpenApiDocument(
34+
this.openApiPath,
35+
);
36+
debug("reloaded OpenAPI document from %s", this.openApiPath);
37+
} catch (error: unknown) {
38+
debug(
39+
"failed to reload OpenAPI document from %s: %o",
40+
this.openApiPath,
41+
error,
42+
);
43+
}
44+
})();
45+
},
46+
);
47+
48+
await waitForEvent(this.watcher, "ready");
49+
}
50+
51+
public async stopWatching(): Promise<void> {
52+
await this.watcher?.close();
53+
}
54+
}

0 commit comments

Comments
 (0)