Skip to content

Commit f35a3b2

Browse files
✨ feat(Watch): New watch mode added, no more db connection waits :)
1 parent 1148dc4 commit f35a3b2

15 files changed

Lines changed: 516 additions & 282 deletions

File tree

deno.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
{
22
"name": "@murat/yelix",
33
"exports": "./mod.ts",
4-
"version": "0.1.20",
4+
"version": "0.1.21",
55
"license": "MIT",
66
"nodeModulesDir": "auto",
77
"tasks": {
8-
"dev": "deno run --watch --allow-net --allow-read --allow-env ./testing/main.ts",
8+
"dev": "deno run --allow-net --allow-read --allow-env ./testing/main.ts",
9+
"dev:watch": "deno run --watch --allow-net --allow-read --allow-env ./testing/main.ts",
910
"bump:patch": "deno run --allow-read --allow-write bump_version.ts patch",
1011
"bump:minor": "deno run --allow-read --allow-write bump_version.ts minor",
1112
"bump:major": "deno run --allow-read --allow-write bump_version.ts major",

deploy.bat

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
echo.
44
echo ------------------------------
55
echo TESTING, I know no one likes to test, but it's important
6-
echo $ deno test
7-
deno test
6+
echo $ deno task test
7+
deno task test
88
:testing_question
99
set /p test="TESTING - Can I continue? (y/n/kill): "
1010
if "%test%"=="y" goto testing_continue

jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@murat/yelix",
3-
"version": "0.1.20",
3+
"version": "0.1.21",
44
"license": "MIT",
55
"exports": "./mod.ts",
66
"imports": {

src/api/endpoints/loadEndpoints.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@ async function loadEndpointsFromFolder(yelix: Yelix, _path: string) {
1717
for (const file of files) {
1818
if (file.isFile) {
1919
yelix.log(`📄 Processing file: ${file.name}`);
20-
const globePath = "file:" + path.join(_path, file.name);
21-
const endpoint = await import(globePath);
22-
yelix.log(`✅ Successfully imported endpoint from ${file.name}`);
23-
endpoints.push(endpoint);
20+
// Add cache busting query parameter to force reload
21+
const cacheBuster = `?cacheBust=${Date.now()}`;
22+
const globePath = "file:" + path.join(_path, file.name) + cacheBuster;
23+
24+
try {
25+
// Force reload by bypassing cache
26+
const endpoint = await import(globePath);
27+
yelix.log(`✅ Successfully imported endpoint from ${file.name}`);
28+
endpoints.push(endpoint);
29+
} catch (err) {
30+
yelix.warn(`Failed to import ${file.name}: ${err}`);
31+
}
2432
}
2533
}
2634

src/api/endpoints/serveEndpoints.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
} from "@/src/OpenAPI/index.ts";
99
import type { Yelix } from "@/src/core/Yelix.ts";
1010
import type { NewEndpointParams } from "@/src/OpenAPI/index.ts";
11+
import type { DocsManager } from "@/src/core/DocsManager.ts";
1112

1213
interface MethodMap {
1314
[key: string]: (path: string, ...handlers: H[]) => void;
@@ -28,7 +29,11 @@ function createMethodMap(app: Hono): MethodMap {
2829
};
2930
}
3031

31-
function serveEndpoints(yelix: Yelix, endpointList: ParsedEndpoint[]) {
32+
function serveEndpoints(
33+
yelix: Yelix,
34+
docsManager: DocsManager,
35+
endpointList: ParsedEndpoint[],
36+
) {
3237
const methodMap = createMethodMap(yelix.app);
3338

3439
for (const endpoint of endpointList) {
@@ -54,7 +59,7 @@ function serveEndpoints(yelix: Yelix, endpointList: ParsedEndpoint[]) {
5459
query: endpoint.openAPI?.query ?? {},
5560
};
5661

57-
if (!isHide) yelix.YelixOpenAPI?.addNewEndpoint(newAPIDoc);
62+
if (!isHide) docsManager.YelixOpenAPI?.addNewEndpoint(newAPIDoc);
5863

5964
methodMap[method.method](endpoint.path, ...middlewares, async (c) => {
6065
const res = await method.handler(c);

src/api/indexPage/getHtml.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import type { Ctx, Yelix } from "@/mod.ts";
2+
import type { DocsManager } from "@/src/core/DocsManager.ts";
23

34
type IndexPageParams = {
45
yelix: Yelix;
6+
docsManager: DocsManager;
57
docsPath?: string;
68
};
79

810
export function serveIndexPage(params: IndexPageParams) {
911
params.yelix.app.notFound((ctx: Ctx) => {
1012
if (ctx.req.path === "/") {
11-
return ctx.html(getHtml({ docsPath: params.yelix.docsPath }), 200);
13+
return ctx.html(getHtml({ docsPath: params.docsManager.docsPath }), 200);
1214
}
1315

1416
return new Response("Not Found", { status: 404 });
1517
});
1618
}
1719

18-
function getHtml({ docsPath }: {
19-
docsPath?: string;
20-
}): string {
20+
function getHtml({ docsPath }: { docsPath?: string }): string {
2121
return /*html*/ `
2222
<!DOCTYPE html>
2323
<html lang="en">

src/core/DocsManager.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { Hono } from "hono";
2+
import { apiReference } from "npm:@scalar/hono-api-reference@0.5.172";
3+
import { YelixOpenAPI } from "@/src/OpenAPI/index.ts";
4+
import type { InitOpenAPIParams } from "@/src/types/types.d.ts";
5+
6+
export class DocsManager {
7+
private app: Hono;
8+
YelixOpenAPI?: YelixOpenAPI;
9+
docsPath?: string;
10+
11+
constructor(app: Hono) {
12+
this.app = app;
13+
}
14+
15+
initOpenAPI(
16+
config: InitOpenAPIParams,
17+
): { title: string; description: string } {
18+
const path = config.path || "/docs";
19+
this.docsPath = path;
20+
21+
this.YelixOpenAPI = new YelixOpenAPI({
22+
title: config.title || "Yelix API",
23+
version: config.version || "1.0.0",
24+
description: config.description || "Yelix API Documentation",
25+
});
26+
27+
this.app.get("/yelix-openapi-raw", (c) => {
28+
return c.json(this.YelixOpenAPI!.getJSON(), 200);
29+
});
30+
31+
const defaultConfig = {
32+
theme: "saturn",
33+
favicon: "/public/favicon.ico",
34+
pageTitle: "Yelix API Docs",
35+
};
36+
const apiReferenceConfig = Object.assign(
37+
defaultConfig,
38+
config.apiReferenceConfig,
39+
);
40+
41+
apiReferenceConfig.spec = { url: "/yelix-openapi-raw" };
42+
43+
this.app.get(path, apiReference(apiReferenceConfig));
44+
45+
return {
46+
title: "OpenAPI Docs",
47+
description: path,
48+
};
49+
}
50+
51+
getOpenAPI() {
52+
return this.YelixOpenAPI;
53+
}
54+
}

src/core/Logger.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
import { yelix_log, yelixClientLog } from "@/src/utils/logging.ts";
3+
import type { Yelix } from "@/src/core/Yelix.ts";
4+
5+
export class Logger {
6+
private debug: boolean;
7+
private yelix: Yelix;
8+
9+
constructor(yelix: Yelix, debug: boolean) {
10+
this.debug = debug;
11+
this.yelix = yelix;
12+
}
13+
14+
clientLog(...params: any): void {
15+
yelixClientLog(...params);
16+
}
17+
18+
log(...params: any): void {
19+
const props = [
20+
"%c INFO %c",
21+
"background-color: white; color: black;",
22+
"background-color: inherit",
23+
...params,
24+
];
25+
yelix_log(this.yelix, ...props);
26+
}
27+
28+
warn(...params: any): void {
29+
const props = [
30+
"%c WARN %c",
31+
"background-color: orange;",
32+
"background-color: inherit",
33+
...params,
34+
];
35+
yelix_log(this.yelix, ...props);
36+
}
37+
38+
throw(...params: any): void {
39+
const props = [
40+
"%c WARN %c",
41+
"background-color: red;",
42+
"background-color: inherit",
43+
...params,
44+
];
45+
yelix_log(this.yelix, ...props);
46+
console.error("❌", ...params);
47+
throw new Error(...params);
48+
}
49+
}

src/core/ServerManager.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
import { yelixClientLog } from "@/src/utils/logging.ts";
3+
import type { Logger } from "./Logger.ts";
4+
import version from "@/version.ts";
5+
import type { Yelix } from "@/src/core/Yelix.ts";
6+
7+
export class ServerManager {
8+
private yelix: Yelix;
9+
private server: any;
10+
private sigintListener: any;
11+
private servedInformations: { title: string; description: string }[] = [];
12+
private logger: Logger;
13+
14+
constructor(yelix: Yelix, logger: Logger) {
15+
this.yelix = yelix;
16+
this.logger = logger;
17+
}
18+
19+
private addLocalInformationToInitiate(addr: any) {
20+
const hostname = addr.hostname;
21+
const isLocalhost = hostname === "0.0.0.0";
22+
const port = addr.port;
23+
const addrStr = isLocalhost
24+
? `http://localhost:${port}`
25+
: `http://${hostname}:${port}`;
26+
27+
this.servedInformations.unshift({
28+
title: "Local",
29+
description: addrStr,
30+
});
31+
}
32+
33+
onListen(addr: any) {
34+
this.addLocalInformationToInitiate(addr);
35+
36+
const packageVersion = version;
37+
38+
if (this.yelix.isFirstServe) {
39+
this.logger.clientLog();
40+
this.logger.clientLog(
41+
" %c 𝕐 Yelix %c" + packageVersion,
42+
"color: orange;",
43+
"color: inherit",
44+
);
45+
const maxLength = Math.max(
46+
...this.servedInformations.map((i) => i.title.length),
47+
);
48+
this.servedInformations.forEach((info) => {
49+
this.logger.clientLog(
50+
` - ${info.title.padEnd(maxLength)}: ${info.description}`,
51+
);
52+
});
53+
this.logger.clientLog();
54+
}
55+
}
56+
57+
addServedInformation(info: { title: string; description: string }) {
58+
this.servedInformations.push(info);
59+
}
60+
61+
startServer(port: number, appFetch: any) {
62+
this.sigintListener = () => {
63+
yelixClientLog("interrupted!");
64+
this.kill();
65+
Deno.exit();
66+
};
67+
68+
Deno.addSignalListener("SIGINT", this.sigintListener);
69+
70+
return new Promise<void>((resolve) => {
71+
this.server = Deno.serve(
72+
{ port, onListen: (addr: any) => this.onListen(addr) },
73+
appFetch,
74+
);
75+
resolve();
76+
});
77+
}
78+
79+
async kill(forceAfterMs = 3000) {
80+
if (this.server) {
81+
try {
82+
let timeoutId = 0;
83+
const timeoutPromise = new Promise<void>((resolve) => {
84+
timeoutId = setTimeout(() => {
85+
this.logger.warn("Server shutdown timed out, forcing close");
86+
resolve();
87+
}, forceAfterMs);
88+
});
89+
90+
const shutdownPromise = this.server.shutdown();
91+
92+
// Race between normal shutdown and timeout
93+
await Promise.race([shutdownPromise, timeoutPromise]);
94+
clearTimeout(timeoutId);
95+
96+
Deno.removeSignalListener("SIGINT", this.sigintListener);
97+
98+
// Clear the reference to prevent any lingering issues
99+
this.server = null;
100+
} catch (error) {
101+
this.logger.warn("Error during server shutdown:", error);
102+
Deno.removeSignalListener("SIGINT", this.sigintListener);
103+
this.server = null;
104+
}
105+
} else {
106+
yelixClientLog(
107+
"You tried to kill the server but it was not running. This is fine.",
108+
);
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)