theokit 0.1.0-alpha.0
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/dist/build-NSKFOAFX.js +49 -0
- package/dist/build-NSKFOAFX.js.map +1 -0
- package/dist/chunk-ASGEGAWL.js +480 -0
- package/dist/chunk-ASGEGAWL.js.map +1 -0
- package/dist/chunk-ATSTRYYT.js +894 -0
- package/dist/chunk-ATSTRYYT.js.map +1 -0
- package/dist/chunk-KPU44T6G.js +71 -0
- package/dist/chunk-KPU44T6G.js.map +1 -0
- package/dist/chunk-MMZZBPMX.js +335 -0
- package/dist/chunk-MMZZBPMX.js.map +1 -0
- package/dist/chunk-N5YH2UDG.js +85 -0
- package/dist/chunk-N5YH2UDG.js.map +1 -0
- package/dist/chunk-SAVVU5LG.js +66 -0
- package/dist/chunk-SAVVU5LG.js.map +1 -0
- package/dist/chunk-TXMUCDJT.js +43 -0
- package/dist/chunk-TXMUCDJT.js.map +1 -0
- package/dist/chunk-U3OJFWK3.js +162 -0
- package/dist/chunk-U3OJFWK3.js.map +1 -0
- package/dist/cli/index.js +79 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/index.d.ts +37 -0
- package/dist/client/index.js +57 -0
- package/dist/client/index.js.map +1 -0
- package/dist/cloudflare-CVGN7FOU.js +88 -0
- package/dist/cloudflare-CVGN7FOU.js.map +1 -0
- package/dist/dev-7UJK3M2O.js +47 -0
- package/dist/dev-7UJK3M2O.js.map +1 -0
- package/dist/docker-M253W54T.js +101 -0
- package/dist/docker-M253W54T.js.map +1 -0
- package/dist/generate-AA7ZE42F.js +116 -0
- package/dist/generate-AA7ZE42F.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/rate-limit-C6hHXIj1.d.ts +13 -0
- package/dist/routes-YP357VAC.js +60 -0
- package/dist/routes-YP357VAC.js.map +1 -0
- package/dist/server/index.d.ts +94 -0
- package/dist/server/index.js +148 -0
- package/dist/server/index.js.map +1 -0
- package/dist/start-BIS3RCFQ.js +243 -0
- package/dist/start-BIS3RCFQ.js.map +1 -0
- package/dist/vercel-747SR2FB.js +80 -0
- package/dist/vercel-747SR2FB.js.map +1 -0
- package/dist/vite-plugin/index.d.ts +12 -0
- package/dist/vite-plugin/index.js +8 -0
- package/dist/vite-plugin/index.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { IncomingMessage } from 'node:http';
|
|
2
|
+
|
|
3
|
+
interface RateLimitConfig {
|
|
4
|
+
windowMs: number;
|
|
5
|
+
max: number;
|
|
6
|
+
}
|
|
7
|
+
interface RateLimitResult {
|
|
8
|
+
limited: boolean;
|
|
9
|
+
headers: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
declare function createRateLimiter(config: RateLimitConfig): (req: IncomingMessage) => RateLimitResult;
|
|
12
|
+
|
|
13
|
+
export { type RateLimitConfig as R, type RateLimitResult as a, createRateLimiter as c };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
validateProjectStructure
|
|
4
|
+
} from "./chunk-KPU44T6G.js";
|
|
5
|
+
import {
|
|
6
|
+
loadConfig
|
|
7
|
+
} from "./chunk-N5YH2UDG.js";
|
|
8
|
+
import {
|
|
9
|
+
scanServerActions,
|
|
10
|
+
scanServerRoutes,
|
|
11
|
+
scanWebSocketRoutes
|
|
12
|
+
} from "./chunk-U3OJFWK3.js";
|
|
13
|
+
|
|
14
|
+
// src/cli/commands/routes.ts
|
|
15
|
+
import { resolve, relative } from "path";
|
|
16
|
+
async function routesCommand() {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const config = await loadConfig(cwd);
|
|
19
|
+
validateProjectStructure(cwd);
|
|
20
|
+
const serverDir = resolve(cwd, config.serverDir);
|
|
21
|
+
const apiRoutes = scanServerRoutes(serverDir);
|
|
22
|
+
const actions = scanServerActions(serverDir);
|
|
23
|
+
const wsRoutes = scanWebSocketRoutes(serverDir);
|
|
24
|
+
const totalCount = apiRoutes.length + actions.length + wsRoutes.length;
|
|
25
|
+
if (totalCount === 0) {
|
|
26
|
+
console.log("\n No routes found.\n");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (apiRoutes.length > 0) {
|
|
30
|
+
console.log("\n API Routes");
|
|
31
|
+
console.log(" " + "\u2500".repeat(60));
|
|
32
|
+
for (const route of apiRoutes) {
|
|
33
|
+
const rel = relative(cwd, route.filePath);
|
|
34
|
+
console.log(` GET/POST ${route.routePath.padEnd(30)} ${rel}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (actions.length > 0) {
|
|
38
|
+
console.log("\n Actions");
|
|
39
|
+
console.log(" " + "\u2500".repeat(60));
|
|
40
|
+
for (const action of actions) {
|
|
41
|
+
const rel = relative(cwd, action.filePath);
|
|
42
|
+
console.log(` POST /api/__actions/${action.actionPath.padEnd(18)} ${rel}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (wsRoutes.length > 0) {
|
|
46
|
+
console.log("\n WebSocket");
|
|
47
|
+
console.log(" " + "\u2500".repeat(60));
|
|
48
|
+
for (const route of wsRoutes) {
|
|
49
|
+
const rel = relative(cwd, route.filePath);
|
|
50
|
+
console.log(` WS ${route.wsPath.padEnd(30)} ${rel}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
console.log(`
|
|
54
|
+
Total: ${totalCount} endpoints
|
|
55
|
+
`);
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
routesCommand
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=routes-YP357VAC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/routes.ts"],"sourcesContent":["import { resolve, relative } from 'node:path'\nimport { loadConfig } from '../../config/load-config.js'\nimport { validateProjectStructure } from '../../core/validate-structure.js'\nimport { scanServerRoutes } from '../../server/scan.js'\nimport { scanServerActions } from '../../server/action-scan.js'\nimport { scanWebSocketRoutes } from '../../server/ws-scan.js'\n\nexport async function routesCommand(): Promise<void> {\n const cwd = process.cwd()\n const config = await loadConfig(cwd)\n validateProjectStructure(cwd)\n\n const serverDir = resolve(cwd, config.serverDir)\n\n const apiRoutes = scanServerRoutes(serverDir)\n const actions = scanServerActions(serverDir)\n const wsRoutes = scanWebSocketRoutes(serverDir)\n\n const totalCount = apiRoutes.length + actions.length + wsRoutes.length\n\n if (totalCount === 0) {\n console.log('\\n No routes found.\\n')\n return\n }\n\n // API Routes\n if (apiRoutes.length > 0) {\n console.log('\\n API Routes')\n console.log(' ' + '─'.repeat(60))\n for (const route of apiRoutes) {\n const rel = relative(cwd, route.filePath)\n console.log(` GET/POST ${route.routePath.padEnd(30)} ${rel}`)\n }\n }\n\n // Actions\n if (actions.length > 0) {\n console.log('\\n Actions')\n console.log(' ' + '─'.repeat(60))\n for (const action of actions) {\n const rel = relative(cwd, action.filePath)\n console.log(` POST /api/__actions/${action.actionPath.padEnd(18)} ${rel}`)\n }\n }\n\n // WebSocket\n if (wsRoutes.length > 0) {\n console.log('\\n WebSocket')\n console.log(' ' + '─'.repeat(60))\n for (const route of wsRoutes) {\n const rel = relative(cwd, route.filePath)\n console.log(` WS ${route.wsPath.padEnd(30)} ${rel}`)\n }\n }\n\n console.log(`\\n Total: ${totalCount} endpoints\\n`)\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,SAAS,gBAAgB;AAOlC,eAAsB,gBAA+B;AACnD,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,WAAW,GAAG;AACnC,2BAAyB,GAAG;AAE5B,QAAM,YAAY,QAAQ,KAAK,OAAO,SAAS;AAE/C,QAAM,YAAY,iBAAiB,SAAS;AAC5C,QAAM,UAAU,kBAAkB,SAAS;AAC3C,QAAM,WAAW,oBAAoB,SAAS;AAE9C,QAAM,aAAa,UAAU,SAAS,QAAQ,SAAS,SAAS;AAEhE,MAAI,eAAe,GAAG;AACpB,YAAQ,IAAI,wBAAwB;AACpC;AAAA,EACF;AAGA,MAAI,UAAU,SAAS,GAAG;AACxB,YAAQ,IAAI,gBAAgB;AAC5B,YAAQ,IAAI,OAAO,SAAI,OAAO,EAAE,CAAC;AACjC,eAAW,SAAS,WAAW;AAC7B,YAAM,MAAM,SAAS,KAAK,MAAM,QAAQ;AACxC,cAAQ,IAAI,eAAe,MAAM,UAAU,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;AAAA,IAChE;AAAA,EACF;AAGA,MAAI,QAAQ,SAAS,GAAG;AACtB,YAAQ,IAAI,aAAa;AACzB,YAAQ,IAAI,OAAO,SAAI,OAAO,EAAE,CAAC;AACjC,eAAW,UAAU,SAAS;AAC5B,YAAM,MAAM,SAAS,KAAK,OAAO,QAAQ;AACzC,cAAQ,IAAI,8BAA8B,OAAO,WAAW,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;AAAA,IACjF;AAAA,EACF;AAGA,MAAI,SAAS,SAAS,GAAG;AACvB,YAAQ,IAAI,eAAe;AAC3B,YAAQ,IAAI,OAAO,SAAI,OAAO,EAAE,CAAC;AACjC,eAAW,SAAS,UAAU;AAC5B,YAAM,MAAM,SAAS,KAAK,MAAM,QAAQ;AACxC,cAAQ,IAAI,eAAe,MAAM,OAAO,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;AAAA,IAC7D;AAAA,EACF;AAEA,UAAQ,IAAI;AAAA,WAAc,UAAU;AAAA,CAAc;AACpD;","names":[]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import * as node_http from 'node:http';
|
|
3
|
+
import { ServerResponse, IncomingMessage } from 'node:http';
|
|
4
|
+
export { R as RateLimitConfig, a as RateLimitResult, c as createRateLimiter } from '../rate-limit-C6hHXIj1.js';
|
|
5
|
+
|
|
6
|
+
interface RouteConfig<TQuery extends z.ZodType = z.ZodUndefined, TBody extends z.ZodType = z.ZodUndefined, TParams extends z.ZodType = z.ZodUndefined, TCtx = unknown, TResponse = unknown> {
|
|
7
|
+
query?: TQuery;
|
|
8
|
+
body?: TBody;
|
|
9
|
+
params?: TParams;
|
|
10
|
+
status?: number;
|
|
11
|
+
handler: (ctx: {
|
|
12
|
+
query: z.infer<TQuery>;
|
|
13
|
+
body: z.infer<TBody>;
|
|
14
|
+
params: z.infer<TParams>;
|
|
15
|
+
request: Request;
|
|
16
|
+
ctx: TCtx;
|
|
17
|
+
}) => TResponse | Promise<TResponse>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Define a typed HTTP route.
|
|
21
|
+
* Identity function — provides type inference for route handlers.
|
|
22
|
+
*/
|
|
23
|
+
declare function defineRoute<TQuery extends z.ZodType = z.ZodUndefined, TBody extends z.ZodType = z.ZodUndefined, TParams extends z.ZodType = z.ZodUndefined, TCtx = unknown, TResponse = unknown>(config: RouteConfig<TQuery, TBody, TParams, TCtx, TResponse>): RouteConfig<TQuery, TBody, TParams, TCtx, TResponse>;
|
|
24
|
+
|
|
25
|
+
interface ActionConfig<TInput extends z.ZodType, TCtx = unknown> {
|
|
26
|
+
input: TInput;
|
|
27
|
+
handler: (ctx: {
|
|
28
|
+
input: z.infer<TInput>;
|
|
29
|
+
ctx: TCtx;
|
|
30
|
+
}) => unknown | Promise<unknown>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Define a typed server action.
|
|
34
|
+
* Identity function — provides type inference for action handlers.
|
|
35
|
+
*/
|
|
36
|
+
declare function defineAction<TInput extends z.ZodType, TCtx = unknown>(config: ActionConfig<TInput, TCtx>): ActionConfig<TInput, TCtx>;
|
|
37
|
+
|
|
38
|
+
type MiddlewareHandler = (request: Request, next: (request: Request) => Promise<Response>) => Response | Promise<Response>;
|
|
39
|
+
/**
|
|
40
|
+
* Define a middleware handler.
|
|
41
|
+
* Identity function — provides type annotation for middleware.
|
|
42
|
+
*/
|
|
43
|
+
declare function defineMiddleware(handler: MiddlewareHandler): MiddlewareHandler;
|
|
44
|
+
|
|
45
|
+
interface CookieOptions {
|
|
46
|
+
httpOnly?: boolean;
|
|
47
|
+
secure?: boolean;
|
|
48
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
49
|
+
maxAge?: number;
|
|
50
|
+
path?: string;
|
|
51
|
+
domain?: string;
|
|
52
|
+
}
|
|
53
|
+
declare function getCookie(req: IncomingMessage, name: string): string | undefined;
|
|
54
|
+
declare function setCookie(res: ServerResponse, name: string, value: string, options?: CookieOptions): void;
|
|
55
|
+
declare function deleteCookie(res: ServerResponse, name: string, options?: {
|
|
56
|
+
path?: string;
|
|
57
|
+
}): void;
|
|
58
|
+
|
|
59
|
+
interface SessionConfig {
|
|
60
|
+
secret: string;
|
|
61
|
+
cookieName?: string;
|
|
62
|
+
maxAge?: number;
|
|
63
|
+
}
|
|
64
|
+
interface SessionManager<TSession> {
|
|
65
|
+
getSession(req: IncomingMessage): Promise<TSession | null>;
|
|
66
|
+
createSession(res: ServerResponse, data: TSession): Promise<void>;
|
|
67
|
+
destroySession(res: ServerResponse): void;
|
|
68
|
+
}
|
|
69
|
+
declare function createSessionManager<TSession>(config: SessionConfig): SessionManager<TSession>;
|
|
70
|
+
|
|
71
|
+
declare class AuthRequiredError extends Error {
|
|
72
|
+
code: "AUTH_REQUIRED";
|
|
73
|
+
status: number;
|
|
74
|
+
constructor(message?: string);
|
|
75
|
+
}
|
|
76
|
+
declare function requireAuth<T>(session: T | null | undefined): asserts session is T;
|
|
77
|
+
|
|
78
|
+
interface WebSocketLike {
|
|
79
|
+
send(data: string | Buffer): void;
|
|
80
|
+
close(code?: number, reason?: string): void;
|
|
81
|
+
}
|
|
82
|
+
interface WebSocketHandler {
|
|
83
|
+
onOpen?: (ws: WebSocketLike, req: node_http.IncomingMessage) => void;
|
|
84
|
+
onMessage?: (ws: WebSocketLike, data: string | Buffer) => void;
|
|
85
|
+
onClose?: (ws: WebSocketLike, code: number, reason: Buffer) => void;
|
|
86
|
+
onError?: (ws: WebSocketLike, error: Error) => void;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Define a WebSocket endpoint handler.
|
|
90
|
+
* Identity function — provides type inference for WebSocket handlers.
|
|
91
|
+
*/
|
|
92
|
+
declare function defineWebSocket(handler: WebSocketHandler): WebSocketHandler;
|
|
93
|
+
|
|
94
|
+
export { type ActionConfig, AuthRequiredError, type CookieOptions, type MiddlewareHandler, type RouteConfig, type SessionConfig, type SessionManager, type WebSocketHandler, type WebSocketLike, createSessionManager, defineAction, defineMiddleware, defineRoute, defineWebSocket, deleteCookie, getCookie, requireAuth, setCookie };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthRequiredError,
|
|
3
|
+
createRateLimiter,
|
|
4
|
+
requireAuth
|
|
5
|
+
} from "../chunk-SAVVU5LG.js";
|
|
6
|
+
|
|
7
|
+
// src/server/define-route.ts
|
|
8
|
+
function defineRoute(config) {
|
|
9
|
+
return config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/server/define-action.ts
|
|
13
|
+
function defineAction(config) {
|
|
14
|
+
return config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/server/define-middleware.ts
|
|
18
|
+
function defineMiddleware(handler) {
|
|
19
|
+
return handler;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/server/cookies.ts
|
|
23
|
+
function getCookie(req, name) {
|
|
24
|
+
const header = req.headers.cookie ?? "";
|
|
25
|
+
for (const pair of header.split(";")) {
|
|
26
|
+
const trimmed = pair.trim();
|
|
27
|
+
const eqIdx = trimmed.indexOf("=");
|
|
28
|
+
if (eqIdx === -1) continue;
|
|
29
|
+
const key = trimmed.slice(0, eqIdx);
|
|
30
|
+
if (key === name) {
|
|
31
|
+
return decodeURIComponent(trimmed.slice(eqIdx + 1));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
function setCookie(res, name, value, options) {
|
|
37
|
+
const opts = {
|
|
38
|
+
httpOnly: true,
|
|
39
|
+
secure: process.env.NODE_ENV === "production",
|
|
40
|
+
sameSite: "lax",
|
|
41
|
+
path: "/",
|
|
42
|
+
...options
|
|
43
|
+
};
|
|
44
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
45
|
+
if (opts.httpOnly) parts.push("HttpOnly");
|
|
46
|
+
if (opts.secure) parts.push("Secure");
|
|
47
|
+
if (opts.sameSite) parts.push(`SameSite=${opts.sameSite.charAt(0).toUpperCase() + opts.sameSite.slice(1)}`);
|
|
48
|
+
if (opts.maxAge !== void 0) parts.push(`Max-Age=${opts.maxAge}`);
|
|
49
|
+
if (opts.path) parts.push(`Path=${opts.path}`);
|
|
50
|
+
if (opts.domain) parts.push(`Domain=${opts.domain}`);
|
|
51
|
+
const existing = res.getHeader("Set-Cookie");
|
|
52
|
+
const cookies = existing ? Array.isArray(existing) ? existing.map(String) : [String(existing)] : [];
|
|
53
|
+
cookies.push(parts.join("; "));
|
|
54
|
+
res.setHeader("Set-Cookie", cookies);
|
|
55
|
+
}
|
|
56
|
+
function deleteCookie(res, name, options) {
|
|
57
|
+
setCookie(res, name, "", { maxAge: 0, path: options?.path ?? "/" });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/server/crypto.ts
|
|
61
|
+
var encoder = new TextEncoder();
|
|
62
|
+
var decoder = new TextDecoder();
|
|
63
|
+
async function deriveKey(secret) {
|
|
64
|
+
const keyMaterial = await crypto.subtle.digest("SHA-256", encoder.encode(secret));
|
|
65
|
+
return crypto.subtle.importKey("raw", keyMaterial, "AES-GCM", false, ["encrypt", "decrypt"]);
|
|
66
|
+
}
|
|
67
|
+
function toBase64Url(data) {
|
|
68
|
+
return Buffer.from(data).toString("base64url");
|
|
69
|
+
}
|
|
70
|
+
function fromBase64Url(str) {
|
|
71
|
+
return new Uint8Array(Buffer.from(str, "base64url"));
|
|
72
|
+
}
|
|
73
|
+
async function encrypt(data, secret) {
|
|
74
|
+
const key = await deriveKey(secret);
|
|
75
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
76
|
+
const plaintext = encoder.encode(JSON.stringify(data));
|
|
77
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, plaintext));
|
|
78
|
+
return `${toBase64Url(iv)}:${toBase64Url(ciphertext)}`;
|
|
79
|
+
}
|
|
80
|
+
async function decrypt(token, secret) {
|
|
81
|
+
try {
|
|
82
|
+
const parts = token.split(":");
|
|
83
|
+
if (parts.length !== 2) return null;
|
|
84
|
+
const iv = fromBase64Url(parts[0]);
|
|
85
|
+
const ciphertext = fromBase64Url(parts[1]);
|
|
86
|
+
const key = await deriveKey(secret);
|
|
87
|
+
const ivBuf = iv.buffer.slice(iv.byteOffset, iv.byteOffset + iv.byteLength);
|
|
88
|
+
const dataBuf = ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength);
|
|
89
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: ivBuf }, key, dataBuf);
|
|
90
|
+
return JSON.parse(decoder.decode(plaintext));
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/server/session.ts
|
|
97
|
+
function createSessionManager(config) {
|
|
98
|
+
if (config.secret.length < 32) {
|
|
99
|
+
throw new Error("Session secret must be at least 32 characters for secure encryption");
|
|
100
|
+
}
|
|
101
|
+
const cookieName = config.cookieName ?? "theo_session";
|
|
102
|
+
const maxAge = config.maxAge ?? 604800;
|
|
103
|
+
return {
|
|
104
|
+
async getSession(req) {
|
|
105
|
+
const raw = getCookie(req, cookieName);
|
|
106
|
+
if (!raw) return null;
|
|
107
|
+
const envelope = await decrypt(raw, config.secret);
|
|
108
|
+
if (!envelope) return null;
|
|
109
|
+
if (envelope.exp < Date.now()) return null;
|
|
110
|
+
return envelope.data;
|
|
111
|
+
},
|
|
112
|
+
async createSession(res, data) {
|
|
113
|
+
const envelope = {
|
|
114
|
+
data,
|
|
115
|
+
exp: Date.now() + maxAge * 1e3
|
|
116
|
+
};
|
|
117
|
+
const token = await encrypt(envelope, config.secret);
|
|
118
|
+
setCookie(res, cookieName, token, {
|
|
119
|
+
httpOnly: true,
|
|
120
|
+
sameSite: "lax",
|
|
121
|
+
maxAge,
|
|
122
|
+
path: "/"
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
destroySession(res) {
|
|
126
|
+
deleteCookie(res, cookieName);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/server/define-websocket.ts
|
|
132
|
+
function defineWebSocket(handler) {
|
|
133
|
+
return handler;
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
AuthRequiredError,
|
|
137
|
+
createRateLimiter,
|
|
138
|
+
createSessionManager,
|
|
139
|
+
defineAction,
|
|
140
|
+
defineMiddleware,
|
|
141
|
+
defineRoute,
|
|
142
|
+
defineWebSocket,
|
|
143
|
+
deleteCookie,
|
|
144
|
+
getCookie,
|
|
145
|
+
requireAuth,
|
|
146
|
+
setCookie
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/define-route.ts","../../src/server/define-action.ts","../../src/server/define-middleware.ts","../../src/server/cookies.ts","../../src/server/crypto.ts","../../src/server/session.ts","../../src/server/define-websocket.ts"],"sourcesContent":["import type { z } from 'zod'\n\nexport interface RouteConfig<\n TQuery extends z.ZodType = z.ZodUndefined,\n TBody extends z.ZodType = z.ZodUndefined,\n TParams extends z.ZodType = z.ZodUndefined,\n TCtx = unknown,\n TResponse = unknown,\n> {\n query?: TQuery\n body?: TBody\n params?: TParams\n status?: number\n handler: (ctx: {\n query: z.infer<TQuery>\n body: z.infer<TBody>\n params: z.infer<TParams>\n request: Request\n ctx: TCtx\n }) => TResponse | Promise<TResponse>\n}\n\n/**\n * Define a typed HTTP route.\n * Identity function — provides type inference for route handlers.\n */\nexport function defineRoute<\n TQuery extends z.ZodType = z.ZodUndefined,\n TBody extends z.ZodType = z.ZodUndefined,\n TParams extends z.ZodType = z.ZodUndefined,\n TCtx = unknown,\n TResponse = unknown,\n>(config: RouteConfig<TQuery, TBody, TParams, TCtx, TResponse>): RouteConfig<TQuery, TBody, TParams, TCtx, TResponse> {\n return config\n}\n","import type { z } from 'zod'\n\nexport interface ActionConfig<TInput extends z.ZodType, TCtx = unknown> {\n input: TInput\n handler: (ctx: { input: z.infer<TInput>; ctx: TCtx }) => unknown | Promise<unknown>\n}\n\n/**\n * Define a typed server action.\n * Identity function — provides type inference for action handlers.\n */\nexport function defineAction<TInput extends z.ZodType, TCtx = unknown>(\n config: ActionConfig<TInput, TCtx>,\n): ActionConfig<TInput, TCtx> {\n return config\n}\n","export type MiddlewareHandler = (\n request: Request,\n next: (request: Request) => Promise<Response>,\n) => Response | Promise<Response>\n\n/**\n * Define a middleware handler.\n * Identity function — provides type annotation for middleware.\n */\nexport function defineMiddleware(handler: MiddlewareHandler): MiddlewareHandler {\n return handler\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\nexport interface CookieOptions {\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n maxAge?: number\n path?: string\n domain?: string\n}\n\nexport function getCookie(req: IncomingMessage, name: string): string | undefined {\n const header = req.headers.cookie ?? ''\n for (const pair of header.split(';')) {\n const trimmed = pair.trim()\n const eqIdx = trimmed.indexOf('=')\n if (eqIdx === -1) continue\n const key = trimmed.slice(0, eqIdx)\n if (key === name) {\n return decodeURIComponent(trimmed.slice(eqIdx + 1))\n }\n }\n return undefined\n}\n\nexport function setCookie(\n res: ServerResponse,\n name: string,\n value: string,\n options?: CookieOptions,\n): void {\n const opts: Required<Omit<CookieOptions, 'maxAge' | 'domain'>> & Pick<CookieOptions, 'maxAge' | 'domain'> = {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n path: '/',\n ...options,\n }\n\n const parts = [`${name}=${encodeURIComponent(value)}`]\n if (opts.httpOnly) parts.push('HttpOnly')\n if (opts.secure) parts.push('Secure')\n if (opts.sameSite) parts.push(`SameSite=${opts.sameSite.charAt(0).toUpperCase() + opts.sameSite.slice(1)}`)\n if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`)\n if (opts.path) parts.push(`Path=${opts.path}`)\n if (opts.domain) parts.push(`Domain=${opts.domain}`)\n\n // Append to existing Set-Cookie headers (EC-1: don't overwrite)\n const existing = res.getHeader('Set-Cookie')\n const cookies: string[] = existing\n ? Array.isArray(existing) ? existing.map(String) : [String(existing)]\n : []\n cookies.push(parts.join('; '))\n res.setHeader('Set-Cookie', cookies)\n}\n\nexport function deleteCookie(\n res: ServerResponse,\n name: string,\n options?: { path?: string },\n): void {\n setCookie(res, name, '', { maxAge: 0, path: options?.path ?? '/' })\n}\n","const encoder = new TextEncoder()\nconst decoder = new TextDecoder()\n\nasync function deriveKey(secret: string): Promise<CryptoKey> {\n const keyMaterial = await crypto.subtle.digest('SHA-256', encoder.encode(secret))\n return crypto.subtle.importKey('raw', keyMaterial, 'AES-GCM', false, ['encrypt', 'decrypt'])\n}\n\nfunction toBase64Url(data: Uint8Array): string {\n return Buffer.from(data).toString('base64url')\n}\n\nfunction fromBase64Url(str: string): Uint8Array {\n return new Uint8Array(Buffer.from(str, 'base64url'))\n}\n\nexport async function encrypt<T>(data: T, secret: string): Promise<string> {\n const key = await deriveKey(secret)\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const plaintext = encoder.encode(JSON.stringify(data))\n const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext))\n return `${toBase64Url(iv)}:${toBase64Url(ciphertext)}`\n}\n\nexport async function decrypt<T>(token: string, secret: string): Promise<T | null> {\n try {\n const parts = token.split(':')\n if (parts.length !== 2) return null\n\n const iv = fromBase64Url(parts[0])\n const ciphertext = fromBase64Url(parts[1])\n const key = await deriveKey(secret)\n\n const ivBuf = iv.buffer.slice(iv.byteOffset, iv.byteOffset + iv.byteLength) as unknown as ArrayBuffer\n const dataBuf = ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength) as unknown as ArrayBuffer\n const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: ivBuf }, key, dataBuf)\n return JSON.parse(decoder.decode(plaintext)) as T\n } catch {\n return null\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport { encrypt, decrypt } from './crypto.js'\nimport { getCookie, setCookie, deleteCookie } from './cookies.js'\n\nexport interface SessionConfig {\n secret: string\n cookieName?: string\n maxAge?: number\n}\n\ninterface SessionEnvelope<T> {\n data: T\n exp: number\n}\n\nexport interface SessionManager<TSession> {\n getSession(req: IncomingMessage): Promise<TSession | null>\n createSession(res: ServerResponse, data: TSession): Promise<void>\n destroySession(res: ServerResponse): void\n}\n\nexport function createSessionManager<TSession>(config: SessionConfig): SessionManager<TSession> {\n if (config.secret.length < 32) {\n throw new Error('Session secret must be at least 32 characters for secure encryption')\n }\n\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n return {\n async getSession(req: IncomingMessage): Promise<TSession | null> {\n const raw = getCookie(req, cookieName)\n if (!raw) return null\n\n const envelope = await decrypt<SessionEnvelope<TSession>>(raw, config.secret)\n if (!envelope) return null\n\n if (envelope.exp < Date.now()) return null\n\n return envelope.data\n },\n\n async createSession(res: ServerResponse, data: TSession): Promise<void> {\n const envelope: SessionEnvelope<TSession> = {\n data,\n exp: Date.now() + maxAge * 1000,\n }\n const token = await encrypt(envelope, config.secret)\n setCookie(res, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n },\n\n destroySession(res: ServerResponse): void {\n deleteCookie(res, cookieName)\n },\n }\n}\n","export interface WebSocketLike {\n send(data: string | Buffer): void\n close(code?: number, reason?: string): void\n}\n\nexport interface WebSocketHandler {\n onOpen?: (ws: WebSocketLike, req: import('node:http').IncomingMessage) => void\n onMessage?: (ws: WebSocketLike, data: string | Buffer) => void\n onClose?: (ws: WebSocketLike, code: number, reason: Buffer) => void\n onError?: (ws: WebSocketLike, error: Error) => void\n}\n\n/**\n * Define a WebSocket endpoint handler.\n * Identity function — provides type inference for WebSocket handlers.\n */\nexport function defineWebSocket(handler: WebSocketHandler): WebSocketHandler {\n return handler\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createProductionLoader,
|
|
4
|
+
createRateLimiter,
|
|
5
|
+
executeAction,
|
|
6
|
+
executeRoute,
|
|
7
|
+
logRequest,
|
|
8
|
+
sendError
|
|
9
|
+
} from "./chunk-MMZZBPMX.js";
|
|
10
|
+
import {
|
|
11
|
+
loadConfig
|
|
12
|
+
} from "./chunk-N5YH2UDG.js";
|
|
13
|
+
import {
|
|
14
|
+
matchRoute,
|
|
15
|
+
scanServerActions,
|
|
16
|
+
scanServerRoutes,
|
|
17
|
+
scanWebSocketRoutes
|
|
18
|
+
} from "./chunk-U3OJFWK3.js";
|
|
19
|
+
|
|
20
|
+
// src/cli/commands/start.ts
|
|
21
|
+
import { createServer } from "http";
|
|
22
|
+
import { randomUUID } from "crypto";
|
|
23
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
24
|
+
import { resolve as resolve2, join, extname as extname2 } from "path";
|
|
25
|
+
|
|
26
|
+
// src/server/static.ts
|
|
27
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
28
|
+
import { resolve, extname } from "path";
|
|
29
|
+
var MIME_TYPES = {
|
|
30
|
+
".html": "text/html",
|
|
31
|
+
".js": "application/javascript",
|
|
32
|
+
".mjs": "application/javascript",
|
|
33
|
+
".css": "text/css",
|
|
34
|
+
".json": "application/json",
|
|
35
|
+
".png": "image/png",
|
|
36
|
+
".jpg": "image/jpeg",
|
|
37
|
+
".jpeg": "image/jpeg",
|
|
38
|
+
".gif": "image/gif",
|
|
39
|
+
".svg": "image/svg+xml",
|
|
40
|
+
".ico": "image/x-icon",
|
|
41
|
+
".woff": "font/woff",
|
|
42
|
+
".woff2": "font/woff2",
|
|
43
|
+
".ttf": "font/ttf",
|
|
44
|
+
".txt": "text/plain",
|
|
45
|
+
".map": "application/json"
|
|
46
|
+
};
|
|
47
|
+
function serveStaticFile(req, res, clientDir) {
|
|
48
|
+
const urlPath = (req.url ?? "/").split("?")[0];
|
|
49
|
+
const filePath = resolve(clientDir, "." + urlPath);
|
|
50
|
+
if (!filePath.startsWith(clientDir)) {
|
|
51
|
+
res.writeHead(403);
|
|
52
|
+
res.end("Forbidden");
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (!existsSync(filePath)) return false;
|
|
56
|
+
const stat = statSync(filePath);
|
|
57
|
+
if (!stat.isFile()) return false;
|
|
58
|
+
const ext = extname(filePath);
|
|
59
|
+
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
60
|
+
const content = readFileSync(filePath);
|
|
61
|
+
res.writeHead(200, {
|
|
62
|
+
"Content-Type": contentType,
|
|
63
|
+
"Content-Length": content.length
|
|
64
|
+
});
|
|
65
|
+
res.end(content);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/cli/commands/start.ts
|
|
70
|
+
async function startCommand(options) {
|
|
71
|
+
const cwd = process.cwd();
|
|
72
|
+
const config = await loadConfig(cwd);
|
|
73
|
+
const distDir = resolve2(cwd, ".theo");
|
|
74
|
+
const clientDir = resolve2(distDir, "client");
|
|
75
|
+
const serverDir = resolve2(cwd, "server");
|
|
76
|
+
if (!existsSync2(clientDir)) {
|
|
77
|
+
throw new Error("No build found. Run `theo build` first.");
|
|
78
|
+
}
|
|
79
|
+
const indexHtml = readFileSync2(join(clientDir, "index.html"), "utf-8");
|
|
80
|
+
const loadModule = createProductionLoader();
|
|
81
|
+
const port = options.port ?? config.port;
|
|
82
|
+
const custom404Path = join(clientDir, "404.html");
|
|
83
|
+
const custom500Path = join(clientDir, "500.html");
|
|
84
|
+
const custom404Html = existsSync2(custom404Path) ? readFileSync2(custom404Path, "utf-8") : null;
|
|
85
|
+
const custom500Html = existsSync2(custom500Path) ? readFileSync2(custom500Path, "utf-8") : null;
|
|
86
|
+
const rateLimiter = config.rateLimit ? createRateLimiter(config.rateLimit) : null;
|
|
87
|
+
const ssrServerPath = resolve2(distDir, "server/entry-server.js");
|
|
88
|
+
const ssrEnabled = config.ssr && existsSync2(ssrServerPath);
|
|
89
|
+
let ssrRender = null;
|
|
90
|
+
let htmlHead = "";
|
|
91
|
+
let htmlTail = "";
|
|
92
|
+
if (ssrEnabled) {
|
|
93
|
+
const mod = await import(ssrServerPath);
|
|
94
|
+
ssrRender = mod.render;
|
|
95
|
+
const rootDivMatch = indexHtml.match(/<div id=["']root["'][^>]*>/);
|
|
96
|
+
if (rootDivMatch) {
|
|
97
|
+
const splitIdx = indexHtml.indexOf(rootDivMatch[0]) + rootDivMatch[0].length;
|
|
98
|
+
htmlHead = indexHtml.slice(0, splitIdx);
|
|
99
|
+
htmlTail = indexHtml.slice(splitIdx);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const server = createServer(async (req, res) => {
|
|
103
|
+
const url = req.url ?? "/";
|
|
104
|
+
const requestId = randomUUID();
|
|
105
|
+
const start = Date.now();
|
|
106
|
+
try {
|
|
107
|
+
if (url.startsWith("/api/__actions/")) {
|
|
108
|
+
res.setHeader("x-request-id", requestId);
|
|
109
|
+
if (rateLimiter) {
|
|
110
|
+
const check = rateLimiter(req);
|
|
111
|
+
for (const [k, v] of Object.entries(check.headers)) res.setHeader(k, v);
|
|
112
|
+
if (check.limited) {
|
|
113
|
+
sendError(res, "RATE_LIMITED", "Too many requests", 429, void 0, requestId);
|
|
114
|
+
logRequest({ method: req.method ?? "POST", url, status: 429, duration: Date.now() - start, requestId });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const pathAfterPrefix = url.slice("/api/__actions/".length).split("?")[0];
|
|
119
|
+
const segments = pathAfterPrefix.split("/").filter(Boolean);
|
|
120
|
+
if (segments.length < 2) {
|
|
121
|
+
sendError(res, "BAD_REQUEST", "Action URL must be /api/__actions/{file}/{exportName}", 400, void 0, requestId);
|
|
122
|
+
logRequest({ method: req.method ?? "POST", url, status: 400, duration: Date.now() - start, requestId });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const exportName = segments[segments.length - 1];
|
|
126
|
+
const actionPath = segments.slice(0, -1).join("/");
|
|
127
|
+
const actions = scanServerActions(serverDir);
|
|
128
|
+
const action = actions.find((a) => a.actionPath === actionPath);
|
|
129
|
+
if (!action) {
|
|
130
|
+
sendError(res, "NOT_FOUND", `Action "${actionPath}" not found`, 404, void 0, requestId);
|
|
131
|
+
logRequest({ method: req.method ?? "POST", url, status: 404, duration: Date.now() - start, requestId });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
await executeAction(action.filePath, exportName, req, res, loadModule, serverDir, requestId);
|
|
135
|
+
logRequest({ method: req.method ?? "POST", url, status: res.statusCode, duration: Date.now() - start, requestId });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (url.startsWith("/api/")) {
|
|
139
|
+
res.setHeader("x-request-id", requestId);
|
|
140
|
+
if (rateLimiter) {
|
|
141
|
+
const check = rateLimiter(req);
|
|
142
|
+
for (const [k, v] of Object.entries(check.headers)) res.setHeader(k, v);
|
|
143
|
+
if (check.limited) {
|
|
144
|
+
sendError(res, "RATE_LIMITED", "Too many requests", 429, void 0, requestId);
|
|
145
|
+
logRequest({ method: req.method ?? "GET", url, status: 429, duration: Date.now() - start, requestId });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const routes = scanServerRoutes(serverDir);
|
|
150
|
+
const match = matchRoute(url, routes);
|
|
151
|
+
if (!match) {
|
|
152
|
+
sendError(res, "NOT_FOUND", "API route not found", 404, void 0, requestId);
|
|
153
|
+
logRequest({ method: req.method ?? "GET", url, status: 404, duration: Date.now() - start, requestId });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
157
|
+
await executeRoute(match.route, method, match.params, req, res, loadModule, serverDir, requestId);
|
|
158
|
+
logRequest({ method, url, status: res.statusCode, duration: Date.now() - start, requestId });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (serveStaticFile(req, res, clientDir)) return;
|
|
162
|
+
const urlPath = url.split("?")[0];
|
|
163
|
+
if (custom404Html && extname2(urlPath)) {
|
|
164
|
+
res.writeHead(404, { "Content-Type": "text/html" });
|
|
165
|
+
res.end(custom404Html);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (ssrRender) {
|
|
169
|
+
try {
|
|
170
|
+
const result = await ssrRender(url);
|
|
171
|
+
if (result && typeof result === "object" && "redirect" in result) {
|
|
172
|
+
res.writeHead(302, { Location: result.redirect.headers.get("location") ?? "/" });
|
|
173
|
+
res.end();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const ssrHtml = result;
|
|
177
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
178
|
+
res.end(htmlHead + ssrHtml + htmlTail);
|
|
179
|
+
return;
|
|
180
|
+
} catch (ssrErr) {
|
|
181
|
+
console.error("[SSR Error] Falling back to CSR:", ssrErr.message);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
185
|
+
res.end(indexHtml);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
if (custom500Html && !res.headersSent) {
|
|
188
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
189
|
+
res.end(custom500Html);
|
|
190
|
+
} else if (!res.headersSent) {
|
|
191
|
+
sendError(res, "INTERNAL_ERROR", err.message, 500);
|
|
192
|
+
} else {
|
|
193
|
+
res.end();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
const wsRoutes = scanWebSocketRoutes(serverDir);
|
|
198
|
+
if (wsRoutes.length > 0) {
|
|
199
|
+
try {
|
|
200
|
+
const { WebSocketServer } = await import("ws");
|
|
201
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
202
|
+
server.on("upgrade", async (request, socket, head) => {
|
|
203
|
+
const url = request.url ?? "/";
|
|
204
|
+
if (!url.startsWith("/ws/")) {
|
|
205
|
+
socket.destroy();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const wsPath = url.split("?")[0];
|
|
209
|
+
const match = wsRoutes.find((r) => r.wsPath === wsPath);
|
|
210
|
+
if (!match) {
|
|
211
|
+
socket.destroy();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const mod = await loadModule(match.filePath);
|
|
216
|
+
const handler = mod.default ?? mod;
|
|
217
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
218
|
+
handler.onOpen?.(ws, request);
|
|
219
|
+
ws.on("message", (data) => handler.onMessage?.(ws, data.toString()));
|
|
220
|
+
ws.on("close", (code, reason) => handler.onClose?.(ws, code, reason));
|
|
221
|
+
ws.on("error", (err) => handler.onError?.(ws, err));
|
|
222
|
+
});
|
|
223
|
+
} catch {
|
|
224
|
+
socket.destroy();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
} catch {
|
|
228
|
+
throw new Error(
|
|
229
|
+
'WebSocket routes found but "ws" package is not installed. Run: npm install ws'
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
server.listen(port, () => {
|
|
234
|
+
console.log(`
|
|
235
|
+
Theo production server`);
|
|
236
|
+
console.log(` \u2192 http://localhost:${port}
|
|
237
|
+
`);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
export {
|
|
241
|
+
startCommand
|
|
242
|
+
};
|
|
243
|
+
//# sourceMappingURL=start-BIS3RCFQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/start.ts","../src/server/static.ts"],"sourcesContent":["import { createServer } from 'node:http'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve, join, extname } from 'node:path'\nimport { loadConfig } from '../../config/load-config.js'\nimport { scanServerRoutes } from '../../server/scan.js'\nimport { scanServerActions } from '../../server/action-scan.js'\nimport { matchRoute } from '../../server/match.js'\nimport { executeRoute, sendError } from '../../server/execute.js'\nimport { executeAction } from '../../server/action-execute.js'\nimport { createProductionLoader } from '../../server/module-loader.js'\nimport { serveStaticFile } from '../../server/static.js'\nimport { logRequest } from '../../server/logger.js'\nimport { createRateLimiter } from '../../server/rate-limit.js'\nimport { scanWebSocketRoutes } from '../../server/ws-scan.js'\n\ninterface StartOptions {\n port?: number\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n const cwd = process.cwd()\n const config = await loadConfig(cwd)\n\n const distDir = resolve(cwd, '.theo')\n const clientDir = resolve(distDir, 'client')\n const serverDir = resolve(cwd, 'server')\n\n if (!existsSync(clientDir)) {\n throw new Error('No build found. Run `theo build` first.')\n }\n\n const indexHtml = readFileSync(join(clientDir, 'index.html'), 'utf-8')\n const loadModule = createProductionLoader()\n const port = options.port ?? config.port\n\n // Custom error pages (optional)\n const custom404Path = join(clientDir, '404.html')\n const custom500Path = join(clientDir, '500.html')\n const custom404Html = existsSync(custom404Path) ? readFileSync(custom404Path, 'utf-8') : null\n const custom500Html = existsSync(custom500Path) ? readFileSync(custom500Path, 'utf-8') : null\n\n // Rate limiter (opt-in)\n const rateLimiter = config.rateLimit\n ? createRateLimiter(config.rateLimit)\n : null\n\n // SSR setup (opt-in)\n const ssrServerPath = resolve(distDir, 'server/entry-server.js')\n const ssrEnabled = config.ssr && existsSync(ssrServerPath)\n let ssrRender: ((url: string) => Promise<string | { redirect: Response }>) | null = null\n let htmlHead = ''\n let htmlTail = ''\n\n if (ssrEnabled) {\n const mod = await import(ssrServerPath)\n ssrRender = mod.render\n // Split HTML template on root div\n const rootDivMatch = indexHtml.match(/<div id=[\"']root[\"'][^>]*>/)\n if (rootDivMatch) {\n const splitIdx = indexHtml.indexOf(rootDivMatch[0]) + rootDivMatch[0].length\n htmlHead = indexHtml.slice(0, splitIdx)\n htmlTail = indexHtml.slice(splitIdx)\n }\n }\n\n const server = createServer(async (req, res) => {\n const url = req.url ?? '/'\n const requestId = randomUUID()\n const start = Date.now()\n\n try {\n // 1. Action routes\n if (url.startsWith('/api/__actions/')) {\n res.setHeader('x-request-id', requestId)\n\n // Rate limit check\n if (rateLimiter) {\n const check = rateLimiter(req)\n for (const [k, v] of Object.entries(check.headers)) res.setHeader(k, v)\n if (check.limited) {\n sendError(res, 'RATE_LIMITED', 'Too many requests', 429, undefined, requestId)\n logRequest({ method: req.method ?? 'POST', url, status: 429, duration: Date.now() - start, requestId })\n return\n }\n }\n\n const pathAfterPrefix = url.slice('/api/__actions/'.length).split('?')[0]\n const segments = pathAfterPrefix.split('/').filter(Boolean)\n if (segments.length < 2) {\n sendError(res, 'BAD_REQUEST', 'Action URL must be /api/__actions/{file}/{exportName}', 400, undefined, requestId)\n logRequest({ method: req.method ?? 'POST', url, status: 400, duration: Date.now() - start, requestId })\n return\n }\n const exportName = segments[segments.length - 1]\n const actionPath = segments.slice(0, -1).join('/')\n const actions = scanServerActions(serverDir)\n const action = actions.find((a) => a.actionPath === actionPath)\n if (!action) {\n sendError(res, 'NOT_FOUND', `Action \"${actionPath}\" not found`, 404, undefined, requestId)\n logRequest({ method: req.method ?? 'POST', url, status: 404, duration: Date.now() - start, requestId })\n return\n }\n await executeAction(action.filePath, exportName, req, res, loadModule, serverDir, requestId)\n logRequest({ method: req.method ?? 'POST', url, status: res.statusCode, duration: Date.now() - start, requestId })\n return\n }\n\n // 2. API routes\n if (url.startsWith('/api/')) {\n res.setHeader('x-request-id', requestId)\n\n // Rate limit check\n if (rateLimiter) {\n const check = rateLimiter(req)\n for (const [k, v] of Object.entries(check.headers)) res.setHeader(k, v)\n if (check.limited) {\n sendError(res, 'RATE_LIMITED', 'Too many requests', 429, undefined, requestId)\n logRequest({ method: req.method ?? 'GET', url, status: 429, duration: Date.now() - start, requestId })\n return\n }\n }\n\n const routes = scanServerRoutes(serverDir)\n const match = matchRoute(url, routes)\n if (!match) {\n sendError(res, 'NOT_FOUND', 'API route not found', 404, undefined, requestId)\n logRequest({ method: req.method ?? 'GET', url, status: 404, duration: Date.now() - start, requestId })\n return\n }\n const method = (req.method ?? 'GET').toUpperCase()\n await executeRoute(match.route, method, match.params, req, res, loadModule, serverDir, requestId)\n logRequest({ method, url, status: res.statusCode, duration: Date.now() - start, requestId })\n return\n }\n\n // 3. Static files\n if (serveStaticFile(req, res, clientDir)) return\n\n // 4. Custom 404 for URLs with file extensions (missing static files)\n const urlPath = url.split('?')[0]\n if (custom404Html && extname(urlPath)) {\n res.writeHead(404, { 'Content-Type': 'text/html' })\n res.end(custom404Html)\n return\n }\n\n // 5. SSR or SPA fallback\n if (ssrRender) {\n try {\n const result = await ssrRender(url)\n if (result && typeof result === 'object' && 'redirect' in result) {\n res.writeHead(302, { Location: result.redirect.headers.get('location') ?? '/' })\n res.end()\n return\n }\n const ssrHtml = result as string\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(htmlHead + ssrHtml + htmlTail)\n return\n } catch (ssrErr) {\n console.error('[SSR Error] Falling back to CSR:', (ssrErr as Error).message)\n // Fall through to CSR fallback\n }\n }\n\n // CSR fallback\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(indexHtml)\n } catch (err) {\n // Custom 500 page for non-API errors\n if (custom500Html && !res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/html' })\n res.end(custom500Html)\n } else if (!res.headersSent) {\n sendError(res, 'INTERNAL_ERROR', (err as Error).message, 500)\n } else {\n res.end()\n }\n }\n })\n\n // WebSocket upgrade handler (opt-in: only if server/ws/ exists)\n const wsRoutes = scanWebSocketRoutes(serverDir)\n if (wsRoutes.length > 0) {\n try {\n const { WebSocketServer } = await import('ws')\n const wss = new WebSocketServer({ noServer: true })\n\n server.on('upgrade', async (request, socket, head) => {\n const url = request.url ?? '/'\n if (!url.startsWith('/ws/')) {\n socket.destroy()\n return\n }\n\n const wsPath = url.split('?')[0]\n const match = wsRoutes.find(r => r.wsPath === wsPath)\n if (!match) {\n socket.destroy()\n return\n }\n\n try {\n const mod = await loadModule(match.filePath)\n const handler = (mod.default ?? mod) as import('../../server/define-websocket.js').WebSocketHandler\n\n wss.handleUpgrade(request, socket, head, (ws) => {\n handler.onOpen?.(ws, request)\n ws.on('message', (data: Buffer) => handler.onMessage?.(ws, data.toString()))\n ws.on('close', (code: number, reason: Buffer) => handler.onClose?.(ws, code, reason))\n ws.on('error', (err: Error) => handler.onError?.(ws, err))\n })\n } catch {\n socket.destroy()\n }\n })\n } catch {\n throw new Error(\n 'WebSocket routes found but \"ws\" package is not installed. Run: npm install ws',\n )\n }\n }\n\n server.listen(port, () => {\n console.log(`\\n Theo production server`)\n console.log(` → http://localhost:${port}\\n`)\n })\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { resolve, extname } from 'node:path'\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.mjs': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain',\n '.map': 'application/json',\n}\n\nexport function serveStaticFile(\n req: IncomingMessage,\n res: ServerResponse,\n clientDir: string,\n): boolean {\n const urlPath = (req.url ?? '/').split('?')[0]\n\n // Path traversal prevention (EC-1)\n const filePath = resolve(clientDir, '.' + urlPath)\n if (!filePath.startsWith(clientDir)) {\n res.writeHead(403)\n res.end('Forbidden')\n return true\n }\n\n if (!existsSync(filePath)) return false\n\n const stat = statSync(filePath)\n if (!stat.isFile()) return false\n\n const ext = extname(filePath)\n const contentType = MIME_TYPES[ext] ?? 'application/octet-stream'\n const content = readFileSync(filePath)\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Content-Length': content.length,\n })\n res.end(content)\n return true\n}\n"],"mappings":"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","names":["existsSync","readFileSync","resolve","extname","resolve","existsSync","readFileSync","extname"]}
|