tina4-nodejs 3.11.8 → 3.11.9
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
CHANGED
|
@@ -27,7 +27,6 @@ export async function serveProject(options: ServeOptions): Promise<void> {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const { startServer } = await import("../../../core/src/index.js");
|
|
30
|
-
const { watchForChanges } = await import("../../../core/src/watcher.js");
|
|
31
30
|
|
|
32
31
|
const server = await startServer({
|
|
33
32
|
port,
|
|
@@ -185,6 +185,13 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
185
185
|
return response;
|
|
186
186
|
};
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Add a single response header (primary method — parity with Python/PHP/Ruby).
|
|
190
|
+
*/
|
|
191
|
+
response.addHeader = function (name: string, value: string): void {
|
|
192
|
+
safeSetHeader(name, value);
|
|
193
|
+
};
|
|
194
|
+
|
|
188
195
|
response.redirect = function (url: string, code?: number): Tina4Response {
|
|
189
196
|
if (res.headersSent) return response;
|
|
190
197
|
res.statusCode = code ?? 302;
|
|
@@ -54,6 +54,7 @@ export interface Tina4ResponseMethods {
|
|
|
54
54
|
xml(content: string, status?: number): Tina4Response;
|
|
55
55
|
status(code: number): Tina4Response;
|
|
56
56
|
header(name: string, value: string | number | readonly string[]): Tina4Response;
|
|
57
|
+
addHeader(name: string, value: string): void;
|
|
57
58
|
send(data: unknown, statusCode?: number, contentType?: string): Tina4Response;
|
|
58
59
|
redirect(url: string, code?: number): Tina4Response;
|
|
59
60
|
cookie(name: string, value: string, options?: CookieOptions): Tina4Response;
|
|
@@ -15,6 +15,8 @@ export interface WebSocketConnection {
|
|
|
15
15
|
params: Record<string, string>;
|
|
16
16
|
/** Send a message to this connection only */
|
|
17
17
|
send(message: string): void;
|
|
18
|
+
/** Serialize an object to JSON and send it to this connection. Parity with Python/PHP. */
|
|
19
|
+
sendJson(data: unknown): void;
|
|
18
20
|
/** Broadcast a message to all connections on the same path (path-scoped) */
|
|
19
21
|
broadcast(message: string): void;
|
|
20
22
|
/** Join a room */
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { watch, existsSync } from "node:fs";
|
|
2
|
-
import { resolve, extname } from "node:path";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* File types that indicate a code change and require route re-discovery.
|
|
6
|
-
* Template/CSS/JS asset changes don't need the router to be touched.
|
|
7
|
-
*/
|
|
8
|
-
const CODE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
9
|
-
|
|
10
|
-
// Module-level state for start()/stop() API
|
|
11
|
-
let _watchers: ReturnType<typeof watch>[] = [];
|
|
12
|
-
let _debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
13
|
-
let _codeChangePending = false;
|
|
14
|
-
let _running = false;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Start the DevReload file watcher.
|
|
18
|
-
*
|
|
19
|
-
* Watches the given directories for file changes and calls onChange when
|
|
20
|
-
* a change is detected. Mirrors the Python DevReload.start() API.
|
|
21
|
-
*
|
|
22
|
-
* @param dirs - Directories to watch. Defaults to ["src", "public"].
|
|
23
|
-
* @param onChange - Callback invoked on file change. Receives `{ code: boolean }`.
|
|
24
|
-
*/
|
|
25
|
-
export function start(
|
|
26
|
-
dirs: string[] = ["src", "public"],
|
|
27
|
-
onChange: (info: { code: boolean }) => void = () => {},
|
|
28
|
-
): void {
|
|
29
|
-
if (_running) return;
|
|
30
|
-
_running = true;
|
|
31
|
-
|
|
32
|
-
const debouncedOnChange = () => {
|
|
33
|
-
if (_debounceTimer) clearTimeout(_debounceTimer);
|
|
34
|
-
_debounceTimer = setTimeout(() => {
|
|
35
|
-
const code = _codeChangePending;
|
|
36
|
-
_codeChangePending = false;
|
|
37
|
-
console.log(
|
|
38
|
-
`\n \x1b[33mFile change detected${code ? ", reloading routes" : ""}...\x1b[0m\n`,
|
|
39
|
-
);
|
|
40
|
-
onChange({ code });
|
|
41
|
-
}, 200);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
for (const dir of dirs) {
|
|
45
|
-
if (!existsSync(dir)) continue;
|
|
46
|
-
try {
|
|
47
|
-
const watcher = watch(resolve(dir), { recursive: true }, (_event, filename) => {
|
|
48
|
-
if (filename && CODE_EXTENSIONS.has(extname(filename))) {
|
|
49
|
-
_codeChangePending = true;
|
|
50
|
-
}
|
|
51
|
-
debouncedOnChange();
|
|
52
|
-
});
|
|
53
|
-
_watchers.push(watcher);
|
|
54
|
-
} catch {
|
|
55
|
-
console.warn(` Warning: Could not watch ${dir}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Stop the DevReload file watcher.
|
|
62
|
-
*
|
|
63
|
-
* Closes all active file watchers and resets internal state.
|
|
64
|
-
* Mirrors the Python DevReload.stop() API.
|
|
65
|
-
*/
|
|
66
|
-
export function stop(): void {
|
|
67
|
-
if (!_running) return;
|
|
68
|
-
_running = false;
|
|
69
|
-
for (const w of _watchers) w.close();
|
|
70
|
-
_watchers = [];
|
|
71
|
-
if (_debounceTimer) clearTimeout(_debounceTimer);
|
|
72
|
-
_debounceTimer = null;
|
|
73
|
-
_codeChangePending = false;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Watch directories for file changes.
|
|
78
|
-
*
|
|
79
|
-
* The callback receives `{ code: boolean }` indicating whether any of the
|
|
80
|
-
* changed files were source code (.ts/.js). If only templates/CSS/JS assets
|
|
81
|
-
* changed, `code` is false and the caller should skip route re-discovery —
|
|
82
|
-
* clearing the router for an asset edit leaves a brief window of 404s that
|
|
83
|
-
* make the dev toolbar vanish.
|
|
84
|
-
*/
|
|
85
|
-
export function watchForChanges(
|
|
86
|
-
dirs: string[],
|
|
87
|
-
onChange: (info: { code: boolean }) => void
|
|
88
|
-
): { close: () => void } {
|
|
89
|
-
const watchers: ReturnType<typeof watch>[] = [];
|
|
90
|
-
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
91
|
-
let codeChangePending = false;
|
|
92
|
-
|
|
93
|
-
const debouncedOnChange = () => {
|
|
94
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
95
|
-
debounceTimer = setTimeout(() => {
|
|
96
|
-
const code = codeChangePending;
|
|
97
|
-
codeChangePending = false;
|
|
98
|
-
console.log(
|
|
99
|
-
`\n \x1b[33mFile change detected${code ? ", reloading routes" : ""}...\x1b[0m\n`,
|
|
100
|
-
);
|
|
101
|
-
onChange({ code });
|
|
102
|
-
}, 200);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
for (const dir of dirs) {
|
|
106
|
-
if (!existsSync(dir)) continue;
|
|
107
|
-
try {
|
|
108
|
-
const watcher = watch(resolve(dir), { recursive: true }, (_event, filename) => {
|
|
109
|
-
if (filename && CODE_EXTENSIONS.has(extname(filename))) {
|
|
110
|
-
codeChangePending = true;
|
|
111
|
-
}
|
|
112
|
-
debouncedOnChange();
|
|
113
|
-
});
|
|
114
|
-
watchers.push(watcher);
|
|
115
|
-
} catch {
|
|
116
|
-
console.warn(` Warning: Could not watch ${dir}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
close: () => {
|
|
122
|
-
for (const w of watchers) w.close();
|
|
123
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
}
|