tina4-nodejs 3.0.0-rc.2
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/BENCHMARK_REPORT.md +96 -0
- package/CARBONAH.md +140 -0
- package/CLAUDE.md +599 -0
- package/COMPARISON.md +194 -0
- package/README.md +595 -0
- package/package.json +59 -0
- package/packages/cli/src/bin.ts +110 -0
- package/packages/cli/src/commands/init.ts +194 -0
- package/packages/cli/src/commands/migrate.ts +96 -0
- package/packages/cli/src/commands/migrateCreate.ts +59 -0
- package/packages/cli/src/commands/routes.ts +61 -0
- package/packages/cli/src/commands/serve.ts +58 -0
- package/packages/cli/src/commands/test.ts +83 -0
- package/packages/core/gallery/auth/meta.json +1 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +22 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +16 -0
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +97 -0
- package/packages/core/gallery/database/meta.json +1 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +13 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +17 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +23 -0
- package/packages/core/gallery/error-overlay/meta.json +1 -0
- package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +17 -0
- package/packages/core/gallery/orm/meta.json +1 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +12 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +7 -0
- package/packages/core/gallery/queue/meta.json +1 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +16 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +10 -0
- package/packages/core/gallery/rest-api/meta.json +1 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +6 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +7 -0
- package/packages/core/gallery/templates/meta.json +1 -0
- package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +15 -0
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +257 -0
- package/packages/core/public/css/tina4.css +2463 -0
- package/packages/core/public/css/tina4.min.css +1 -0
- package/packages/core/public/favicon.ico +0 -0
- package/packages/core/public/images/logo.svg +5 -0
- package/packages/core/public/images/tina4-logo-icon.webp +0 -0
- package/packages/core/public/js/frond.min.js +420 -0
- package/packages/core/public/js/tina4-dev-admin.min.js +327 -0
- package/packages/core/public/js/tina4.min.js +93 -0
- package/packages/core/public/swagger/index.html +90 -0
- package/packages/core/public/swagger/oauth2-redirect.html +63 -0
- package/packages/core/src/ai.ts +359 -0
- package/packages/core/src/api.ts +248 -0
- package/packages/core/src/auth.ts +287 -0
- package/packages/core/src/cache.ts +121 -0
- package/packages/core/src/constants.ts +48 -0
- package/packages/core/src/container.ts +90 -0
- package/packages/core/src/devAdmin.ts +2024 -0
- package/packages/core/src/devMailbox.ts +316 -0
- package/packages/core/src/dotenv.ts +172 -0
- package/packages/core/src/errorOverlay.test.ts +122 -0
- package/packages/core/src/errorOverlay.ts +278 -0
- package/packages/core/src/events.ts +112 -0
- package/packages/core/src/fakeData.ts +309 -0
- package/packages/core/src/graphql.ts +812 -0
- package/packages/core/src/health.ts +31 -0
- package/packages/core/src/htmlElement.ts +172 -0
- package/packages/core/src/i18n.ts +136 -0
- package/packages/core/src/index.ts +88 -0
- package/packages/core/src/logger.ts +226 -0
- package/packages/core/src/messenger.ts +822 -0
- package/packages/core/src/middleware.ts +138 -0
- package/packages/core/src/queue.ts +481 -0
- package/packages/core/src/queueBackends/kafkaBackend.ts +348 -0
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +479 -0
- package/packages/core/src/rateLimiter.ts +107 -0
- package/packages/core/src/request.ts +189 -0
- package/packages/core/src/response.ts +146 -0
- package/packages/core/src/routeDiscovery.ts +87 -0
- package/packages/core/src/router.ts +398 -0
- package/packages/core/src/scss.ts +366 -0
- package/packages/core/src/server.ts +610 -0
- package/packages/core/src/service.ts +380 -0
- package/packages/core/src/session.ts +480 -0
- package/packages/core/src/sessionHandlers/mongoHandler.ts +286 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +184 -0
- package/packages/core/src/static.ts +58 -0
- package/packages/core/src/testing.ts +233 -0
- package/packages/core/src/types.ts +98 -0
- package/packages/core/src/watcher.ts +37 -0
- package/packages/core/src/websocket.ts +408 -0
- package/packages/core/src/wsdl.ts +546 -0
- package/packages/core/templates/errors/302.twig +14 -0
- package/packages/core/templates/errors/401.twig +9 -0
- package/packages/core/templates/errors/403.twig +29 -0
- package/packages/core/templates/errors/404.twig +29 -0
- package/packages/core/templates/errors/500.twig +38 -0
- package/packages/core/templates/errors/502.twig +9 -0
- package/packages/core/templates/errors/503.twig +12 -0
- package/packages/core/templates/errors/base.twig +37 -0
- package/packages/frond/src/engine.ts +1475 -0
- package/packages/frond/src/index.ts +2 -0
- package/packages/orm/src/adapters/firebird.ts +455 -0
- package/packages/orm/src/adapters/mssql.ts +440 -0
- package/packages/orm/src/adapters/mysql.ts +355 -0
- package/packages/orm/src/adapters/postgres.ts +362 -0
- package/packages/orm/src/adapters/sqlite.ts +270 -0
- package/packages/orm/src/autoCrud.ts +231 -0
- package/packages/orm/src/baseModel.ts +536 -0
- package/packages/orm/src/database.ts +321 -0
- package/packages/orm/src/fakeData.ts +118 -0
- package/packages/orm/src/index.ts +49 -0
- package/packages/orm/src/migration.ts +392 -0
- package/packages/orm/src/model.ts +56 -0
- package/packages/orm/src/query.ts +113 -0
- package/packages/orm/src/seeder.ts +120 -0
- package/packages/orm/src/sqlTranslation.ts +272 -0
- package/packages/orm/src/types.ts +110 -0
- package/packages/orm/src/validation.ts +93 -0
- package/packages/swagger/src/generator.ts +189 -0
- package/packages/swagger/src/index.ts +2 -0
- package/packages/swagger/src/ui.ts +48 -0
- package/skills/tina4-developer.skill +0 -0
- package/skills/tina4-js.skill +0 -0
- package/skills/tina4-maintainer.skill +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tina4 Node.js — Inline testing framework.
|
|
3
|
+
*
|
|
4
|
+
* Attach test assertions to functions and run them all at once.
|
|
5
|
+
*
|
|
6
|
+
* import { tests, assertEqual, assertThrows, runAllTests } from "./testing.js";
|
|
7
|
+
*
|
|
8
|
+
* const add = tests(
|
|
9
|
+
* assertEqual([5, 3], 8),
|
|
10
|
+
* assertThrows(Error, [null]),
|
|
11
|
+
* )(function add(a: number, b: number | null = null): number {
|
|
12
|
+
* if (b === null) throw new Error("b required");
|
|
13
|
+
* return a + b;
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* runAllTests();
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
interface Assertion {
|
|
22
|
+
type: "equal" | "raises" | "true" | "false";
|
|
23
|
+
args: unknown[];
|
|
24
|
+
expected?: unknown;
|
|
25
|
+
exception?: new (...a: unknown[]) => Error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface RegistryEntry {
|
|
29
|
+
name: string;
|
|
30
|
+
fn: (...args: unknown[]) => unknown;
|
|
31
|
+
assertions: Assertion[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface TestResults {
|
|
35
|
+
passed: number;
|
|
36
|
+
failed: number;
|
|
37
|
+
errors: number;
|
|
38
|
+
details: Array<{ name: string; status: string; message?: string }>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Registry ───────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const registry: RegistryEntry[] = [];
|
|
44
|
+
|
|
45
|
+
// ── Assertion builders ─────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/** Assert that calling the function with `args` returns `expected`. */
|
|
48
|
+
export function assertEqual(args: unknown[], expected: unknown): Assertion {
|
|
49
|
+
return { type: "equal", args, expected };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Assert that calling the function with `args` throws an instance of `errorClass`. */
|
|
53
|
+
export function assertThrows(
|
|
54
|
+
errorClass: new (...a: unknown[]) => Error,
|
|
55
|
+
args: unknown[],
|
|
56
|
+
): Assertion {
|
|
57
|
+
return { type: "raises", args, exception: errorClass };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Assert that calling the function with `args` returns a truthy value. */
|
|
61
|
+
export function assertTrue(args: unknown[]): Assertion {
|
|
62
|
+
return { type: "true", args };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Assert that calling the function with `args` returns a falsy value. */
|
|
66
|
+
export function assertFalse(args: unknown[]): Assertion {
|
|
67
|
+
return { type: "false", args };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Decorator ──────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Attach inline test assertions to a function.
|
|
74
|
+
*
|
|
75
|
+
* Returns a wrapper that accepts the function and registers it,
|
|
76
|
+
* then returns the original function unchanged.
|
|
77
|
+
*
|
|
78
|
+
* const myFn = tests(assertEqual([1,2], 3))(function myFn(a,b) { return a+b; });
|
|
79
|
+
*/
|
|
80
|
+
export function tests(
|
|
81
|
+
...assertions: Assertion[]
|
|
82
|
+
): <T extends (...args: unknown[]) => unknown>(fn: T) => T {
|
|
83
|
+
return <T extends (...args: unknown[]) => unknown>(fn: T): T => {
|
|
84
|
+
registry.push({
|
|
85
|
+
name: fn.name || "anonymous",
|
|
86
|
+
fn: fn as (...args: unknown[]) => unknown,
|
|
87
|
+
assertions,
|
|
88
|
+
});
|
|
89
|
+
return fn;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Runner ─────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/** Run all registered tests. Returns results summary. */
|
|
96
|
+
export function runAllTests(
|
|
97
|
+
options: { quiet?: boolean; failfast?: boolean } = {},
|
|
98
|
+
): TestResults {
|
|
99
|
+
const { quiet = false, failfast = false } = options;
|
|
100
|
+
const results: TestResults = { passed: 0, failed: 0, errors: 0, details: [] };
|
|
101
|
+
|
|
102
|
+
for (const entry of registry) {
|
|
103
|
+
if (!quiet) {
|
|
104
|
+
console.log(`\n ${entry.name}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const assertion of entry.assertions) {
|
|
108
|
+
const label = assertionLabel(assertion, entry.name);
|
|
109
|
+
try {
|
|
110
|
+
runAssertion(entry.fn, assertion);
|
|
111
|
+
results.passed++;
|
|
112
|
+
results.details.push({ name: label, status: "passed" });
|
|
113
|
+
if (!quiet) {
|
|
114
|
+
console.log(` \x1b[32m+\x1b[0m ${label}`);
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err instanceof TestAssertionError) {
|
|
118
|
+
results.failed++;
|
|
119
|
+
results.details.push({ name: label, status: "failed", message: err.message });
|
|
120
|
+
if (!quiet) {
|
|
121
|
+
console.log(` \x1b[31mx\x1b[0m ${label}: ${err.message}`);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
results.errors++;
|
|
125
|
+
const msg = err instanceof Error ? `${err.constructor.name}: ${err.message}` : String(err);
|
|
126
|
+
results.details.push({ name: label, status: "error", message: msg });
|
|
127
|
+
if (!quiet) {
|
|
128
|
+
console.log(` \x1b[33m!\x1b[0m ${label}: ${msg}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (failfast) {
|
|
132
|
+
printSummary(results, quiet);
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
printSummary(results, quiet);
|
|
140
|
+
return results;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Reset the test registry (useful between test runs). */
|
|
144
|
+
export function resetTests(): void {
|
|
145
|
+
registry.length = 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Internals ──────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
class TestAssertionError extends Error {
|
|
151
|
+
constructor(message: string) {
|
|
152
|
+
super(message);
|
|
153
|
+
this.name = "TestAssertionError";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function runAssertion(fn: (...args: unknown[]) => unknown, assertion: Assertion): void {
|
|
158
|
+
const { type, args } = assertion;
|
|
159
|
+
|
|
160
|
+
switch (type) {
|
|
161
|
+
case "equal": {
|
|
162
|
+
const result = fn(...args);
|
|
163
|
+
const expected = assertion.expected;
|
|
164
|
+
if (result !== expected) {
|
|
165
|
+
throw new TestAssertionError(
|
|
166
|
+
`expected ${JSON.stringify(expected)}, got ${JSON.stringify(result)}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case "raises": {
|
|
173
|
+
const ExcClass = assertion.exception!;
|
|
174
|
+
try {
|
|
175
|
+
fn(...args);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (err instanceof ExcClass) {
|
|
178
|
+
return; // success
|
|
179
|
+
}
|
|
180
|
+
throw new TestAssertionError(
|
|
181
|
+
`expected ${ExcClass.name}, got ${err instanceof Error ? err.constructor.name : typeof err}: ${err}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
throw new TestAssertionError(`expected ${ExcClass.name} to be raised`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
case "true": {
|
|
188
|
+
const result = fn(...args);
|
|
189
|
+
if (!result) {
|
|
190
|
+
throw new TestAssertionError(`expected truthy, got ${JSON.stringify(result)}`);
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case "false": {
|
|
196
|
+
const result = fn(...args);
|
|
197
|
+
if (result) {
|
|
198
|
+
throw new TestAssertionError(`expected falsy, got ${JSON.stringify(result)}`);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
default:
|
|
204
|
+
throw new Error(`unknown assertion type: ${type}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function assertionLabel(assertion: Assertion, fnName: string): string {
|
|
209
|
+
const args = JSON.stringify(assertion.args);
|
|
210
|
+
switch (assertion.type) {
|
|
211
|
+
case "equal":
|
|
212
|
+
return `${fnName}(${args}) == ${JSON.stringify(assertion.expected)}`;
|
|
213
|
+
case "raises":
|
|
214
|
+
return `${fnName}(${args}) raises ${assertion.exception!.name}`;
|
|
215
|
+
case "true":
|
|
216
|
+
return `${fnName}(${args}) is truthy`;
|
|
217
|
+
case "false":
|
|
218
|
+
return `${fnName}(${args}) is falsy`;
|
|
219
|
+
default:
|
|
220
|
+
return `${fnName} [${assertion.type}]`;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function printSummary(results: TestResults, quiet: boolean): void {
|
|
225
|
+
if (quiet) return;
|
|
226
|
+
const total = results.passed + results.failed + results.errors;
|
|
227
|
+
console.log(
|
|
228
|
+
`\n ${total} tests: ` +
|
|
229
|
+
`\x1b[32m${results.passed} passed\x1b[0m, ` +
|
|
230
|
+
`\x1b[31m${results.failed} failed\x1b[0m, ` +
|
|
231
|
+
`\x1b[33m${results.errors} errors\x1b[0m\n`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
export interface UploadedFile {
|
|
4
|
+
fieldName: string;
|
|
5
|
+
filename: string;
|
|
6
|
+
contentType: string;
|
|
7
|
+
data: Buffer;
|
|
8
|
+
size: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Tina4Request extends IncomingMessage {
|
|
12
|
+
params: Record<string, string>;
|
|
13
|
+
query: Record<string, string>;
|
|
14
|
+
body: unknown;
|
|
15
|
+
ip: string;
|
|
16
|
+
files: UploadedFile[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CookieOptions {
|
|
20
|
+
maxAge?: number;
|
|
21
|
+
expires?: Date;
|
|
22
|
+
path?: string;
|
|
23
|
+
domain?: string;
|
|
24
|
+
secure?: boolean;
|
|
25
|
+
httpOnly?: boolean;
|
|
26
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Tina4ResponseMethods {
|
|
30
|
+
json(data: unknown, status?: number): Tina4Response;
|
|
31
|
+
html(content: string, status?: number): Tina4Response;
|
|
32
|
+
text(content: string, status?: number): Tina4Response;
|
|
33
|
+
status(code: number): Tina4Response;
|
|
34
|
+
header(name: string, value: string | number | readonly string[]): Tina4Response;
|
|
35
|
+
send(data: unknown, statusCode?: number, contentType?: string): Tina4Response;
|
|
36
|
+
redirect(url: string, code?: number): Tina4Response;
|
|
37
|
+
cookie(name: string, value: string, options?: CookieOptions): Tina4Response;
|
|
38
|
+
clearCookie(name: string, options?: CookieOptions): Tina4Response;
|
|
39
|
+
render?(template: string, data?: Record<string, unknown>): void;
|
|
40
|
+
template(name: string, data?: Record<string, unknown>): Promise<Tina4Response>;
|
|
41
|
+
/** The underlying ServerResponse for advanced use */
|
|
42
|
+
raw: ServerResponse;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tina4 Response — callable AND has methods.
|
|
47
|
+
*
|
|
48
|
+
* return response({ users: [] }); // Auto-JSON
|
|
49
|
+
* return response({ ok: true }, HTTP_CREATED); // JSON with status
|
|
50
|
+
* return response("<h1>Hi</h1>"); // Auto-HTML
|
|
51
|
+
* return response("Not found", HTTP_NOT_FOUND); // Plain text
|
|
52
|
+
* return response(data, HTTP_OK, APPLICATION_JSON); // Explicit
|
|
53
|
+
* return response.json(data, 201); // Explicit method
|
|
54
|
+
* return response.redirect("/login"); // Special case
|
|
55
|
+
*/
|
|
56
|
+
export type Tina4Response =
|
|
57
|
+
((data?: unknown, statusCode?: number, contentType?: string) => Tina4Response)
|
|
58
|
+
& Tina4ResponseMethods;
|
|
59
|
+
|
|
60
|
+
export type RouteHandler = (req: Tina4Request, res: Tina4Response) => Promise<void> | void;
|
|
61
|
+
|
|
62
|
+
export interface RouteDefinition {
|
|
63
|
+
method: string;
|
|
64
|
+
pattern: string;
|
|
65
|
+
handler: RouteHandler;
|
|
66
|
+
filePath?: string;
|
|
67
|
+
meta?: RouteMeta;
|
|
68
|
+
middlewares?: Middleware[];
|
|
69
|
+
/** Template file to render when handler returns a plain object */
|
|
70
|
+
template?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface RouteMeta {
|
|
74
|
+
summary?: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
tags?: string[];
|
|
77
|
+
responses?: Record<string, { description: string }>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface Tina4Config {
|
|
81
|
+
port?: number;
|
|
82
|
+
host?: string;
|
|
83
|
+
routesDir?: string;
|
|
84
|
+
modelsDir?: string;
|
|
85
|
+
templatesDir?: string;
|
|
86
|
+
staticDir?: string;
|
|
87
|
+
database?: {
|
|
88
|
+
type?: "sqlite" | "postgres" | "mysql";
|
|
89
|
+
path?: string;
|
|
90
|
+
url?: string;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type Middleware = (
|
|
95
|
+
req: Tina4Request,
|
|
96
|
+
res: Tina4Response,
|
|
97
|
+
next: () => void
|
|
98
|
+
) => void | Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { watch, existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function watchForChanges(
|
|
5
|
+
dirs: string[],
|
|
6
|
+
onChange: () => void
|
|
7
|
+
): { close: () => void } {
|
|
8
|
+
const watchers: ReturnType<typeof watch>[] = [];
|
|
9
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
10
|
+
|
|
11
|
+
const debouncedOnChange = () => {
|
|
12
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
13
|
+
debounceTimer = setTimeout(() => {
|
|
14
|
+
console.log("\n \x1b[33mFile change detected, reloading routes...\x1b[0m\n");
|
|
15
|
+
onChange();
|
|
16
|
+
}, 200);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
for (const dir of dirs) {
|
|
20
|
+
if (!existsSync(dir)) continue;
|
|
21
|
+
try {
|
|
22
|
+
const watcher = watch(resolve(dir), { recursive: true }, () => {
|
|
23
|
+
debouncedOnChange();
|
|
24
|
+
});
|
|
25
|
+
watchers.push(watcher);
|
|
26
|
+
} catch {
|
|
27
|
+
console.warn(` Warning: Could not watch ${dir}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
close: () => {
|
|
33
|
+
for (const w of watchers) w.close();
|
|
34
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|