@moonsjs/vite 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +9 -2
- package/dist/index-8xq8fcgq.js +222 -0
- package/dist/index-8xq8fcgq.js.map +14 -0
- package/dist/index-fg6nqart.js +776 -0
- package/dist/index-fg6nqart.js.map +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +423 -0
- package/dist/index.js.map +11 -0
- package/dist/leak-check.d.ts +14 -0
- package/dist/leak-check.d.ts.map +1 -0
- package/dist/manifest.d.ts +21 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/server/dev.d.ts +9 -0
- package/dist/server/dev.d.ts.map +1 -0
- package/dist/server/handler.d.ts +18 -0
- package/dist/server/handler.d.ts.map +1 -0
- package/dist/server/index.d.ts +13 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +311 -0
- package/dist/server/index.js.map +13 -0
- package/dist/server/node-server.d.ts +17 -0
- package/dist/server/node-server.d.ts.map +1 -0
- package/dist/server/node.d.ts +6 -0
- package/dist/server/node.d.ts.map +1 -0
- package/dist/server/shell.d.ts +18 -0
- package/dist/server/shell.d.ts.map +1 -0
- package/dist/server/shutdown.d.ts +17 -0
- package/dist/server/shutdown.d.ts.map +1 -0
- package/dist/server/test-server.d.ts +21 -0
- package/dist/server/test-server.d.ts.map +1 -0
- package/dist/transform.d.ts +101 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +34 -0
- package/dist/transform.js.map +9 -0
- package/dist/virtual-modules.d.ts +12 -0
- package/dist/virtual-modules.d.ts.map +1 -0
- package/package.json +51 -4
- package/src/ctx-fallback.d.ts +9 -0
- package/src/index.ts +2 -0
- package/src/leak-check.ts +36 -0
- package/src/manifest.ts +79 -0
- package/src/plugin.ts +471 -0
- package/src/server/dev.ts +54 -0
- package/src/server/handler.ts +249 -0
- package/src/server/index.ts +12 -0
- package/src/server/node-server.ts +115 -0
- package/src/server/node.ts +61 -0
- package/src/server/shell.ts +77 -0
- package/src/server/shutdown.ts +64 -0
- package/src/server/test-server.ts +44 -0
- package/src/transform.ts +1073 -0
- package/src/virtual-modules.ts +23 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface BundleOutputLike {
|
|
2
|
+
type: string;
|
|
3
|
+
fileName?: string;
|
|
4
|
+
code?: string;
|
|
5
|
+
source?: string | Uint8Array;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* §9.1 (`leakTest.canary`) / §10.4: cari canary pada output bundle client.
|
|
9
|
+
* Dipakai `generateBundle` plugin — build gagal bila canary ditemukan.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findLeak(outputs: Iterable<BundleOutputLike>, canary: string, decode?: (source: Uint8Array) => string): {
|
|
12
|
+
fileName: string;
|
|
13
|
+
} | null;
|
|
14
|
+
//# sourceMappingURL=leak-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leak-check.d.ts","sourceRoot":"","sources":["../src/leak-check.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EACnC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAsB,GACrD;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgB7B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type RouteDefinition } from "@moonsjs/core";
|
|
2
|
+
export interface ClientManifestEntry {
|
|
3
|
+
page: string;
|
|
4
|
+
layouts: string[];
|
|
5
|
+
}
|
|
6
|
+
/** Key manifest untuk halaman kustom `_404` dan `_error` (§11.2). */
|
|
7
|
+
export declare const NOT_FOUND_ROUTE_KEY = "__moons/404";
|
|
8
|
+
export declare const ERROR_ROUTE_KEY = "__moons/error";
|
|
9
|
+
/**
|
|
10
|
+
* Manifest route untuk client (PRD §8.2): key = key route file
|
|
11
|
+
* (routeKeyFromFile), nilai = page + layout chain dari root ke leaf.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildClientManifest(routes: RouteDefinition[], routesDir: string): Record<string, ClientManifestEntry>;
|
|
14
|
+
/**
|
|
15
|
+
* Isi `virtual:moons/routes`: daftar RouteDefinition, manifest client, dan
|
|
16
|
+
* peta import() dinamis per file route (specifier root-relative).
|
|
17
|
+
*/
|
|
18
|
+
export declare function renderRoutesModule(routes: RouteDefinition[], routesDir: string, projectRoot: string): string;
|
|
19
|
+
/** Path file absolut project → specifier root-relative yang bisa di-resolve Vite. */
|
|
20
|
+
export declare function rootSpecifier(projectRoot: string, file: string): string;
|
|
21
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGpF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AACjD,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EAAE,EACzB,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAuBrC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,eAAe,EAAE,EACzB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,MAAM,CAmBR;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIvE"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Plugin } from "vite";
|
|
2
|
+
export interface MoonsVitePluginOptions {
|
|
3
|
+
/** Default: "moons.config.ts". */
|
|
4
|
+
configPath?: string;
|
|
5
|
+
/** Hanya dipakai di CI (M2 §21). */
|
|
6
|
+
leakTest?: {
|
|
7
|
+
canary?: string;
|
|
8
|
+
};
|
|
9
|
+
/** Default: root Vite. */
|
|
10
|
+
projectRoot?: string;
|
|
11
|
+
}
|
|
12
|
+
export default function moons(options?: MoonsVitePluginOptions): Plugin;
|
|
13
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsB,KAAK,MAAM,EAAsB,MAAM,MAAM,CAAC;AAoB3E,MAAM,WAAW,sBAAsB;IACrC,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,QAAQ,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAQD,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,OAAO,GAAE,sBAA2B,GAAG,MAAM,CA2V1E"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ViteDevServer } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Middleware SSR dev: dipasang sebelum middleware internal Vite, hanya
|
|
4
|
+
* menangani request HTML (modul/aset tetap dilayani Vite).
|
|
5
|
+
*/
|
|
6
|
+
export declare function installDevMiddleware(server: ViteDevServer, options: {
|
|
7
|
+
templatePath: string;
|
|
8
|
+
}): void;
|
|
9
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/server/dev.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAU1C;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAChC,IAAI,CAUN"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type MoonsConfig, type RouteDefinition, type RouteModule } from "@moonsjs/core";
|
|
2
|
+
import type { Shell } from "./shell";
|
|
3
|
+
export interface RequestHandlerOptions {
|
|
4
|
+
routes: RouteDefinition[];
|
|
5
|
+
/** Config yang sudah divalidasi. */
|
|
6
|
+
config: MoonsConfig;
|
|
7
|
+
/** Direktori route absolut; dipakai untuk derivasi key stabil. */
|
|
8
|
+
routesDir: string;
|
|
9
|
+
/** Peta import() modul route, key = routeKeyFromFile. */
|
|
10
|
+
loadRoute: Record<string, () => Promise<RouteModule>>;
|
|
11
|
+
shell: Shell;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Handler HTML: context → middleware global → resolve route → loader paralel
|
|
15
|
+
* → render React → inject payload devalue (PRD §11.2, §14.2, §17).
|
|
16
|
+
*/
|
|
17
|
+
export declare function createRequestHandler(options: RequestHandlerOptions): (request: Request) => Promise<Response>;
|
|
18
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,WAAW,EAEhB,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AAMvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAKrC,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,oCAAoC;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACtD,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,qBAAqB,GAC7B,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAgJzC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { createRequestHandler } from "./handler";
|
|
2
|
+
export type { RequestHandlerOptions } from "./handler";
|
|
3
|
+
export { createDevShell, createFileShell, injectShell, DEFAULT_TEMPLATE, ROOT_PLACEHOLDER } from "./shell";
|
|
4
|
+
export type { Shell } from "./shell";
|
|
5
|
+
export { startNodeServer, resolveStaticFile } from "./node-server";
|
|
6
|
+
export type { NodeServerOptions } from "./node-server";
|
|
7
|
+
export { installGracefulShutdown } from "./shutdown";
|
|
8
|
+
export type { ShutdownOptions } from "./shutdown";
|
|
9
|
+
export { createTestServer } from "./test-server";
|
|
10
|
+
export type { TestServer, TestServerOptions } from "./test-server";
|
|
11
|
+
export { sendNodeResponse, toWebRequest } from "./node";
|
|
12
|
+
export { installDevMiddleware } from "./dev";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,YAAY,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3G,YAAY,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NOT_FOUND_ROUTE_KEY,
|
|
3
|
+
ERROR_ROUTE_KEY,
|
|
4
|
+
ROOT_PLACEHOLDER2,
|
|
5
|
+
DEFAULT_TEMPLATE2,
|
|
6
|
+
injectShell2,
|
|
7
|
+
createDevShell2,
|
|
8
|
+
createFileShell2,
|
|
9
|
+
toWebRequest2,
|
|
10
|
+
sendNodeResponse2,
|
|
11
|
+
installDevMiddleware2
|
|
12
|
+
} from "../index-8xq8fcgq.js";
|
|
13
|
+
|
|
14
|
+
// src/server/handler.ts
|
|
15
|
+
import {
|
|
16
|
+
MoonsError,
|
|
17
|
+
composeMiddleware,
|
|
18
|
+
resolveRoute,
|
|
19
|
+
routeKeyFromFile,
|
|
20
|
+
runLoaders
|
|
21
|
+
} from "@moonsjs/core";
|
|
22
|
+
import { encodeSsrPayload, reviveError, serializeError } from "@moonsjs/core/serialize";
|
|
23
|
+
import { renderToMarkup } from "@moonsjs/react/ssr";
|
|
24
|
+
import { createRpcHandler, runWithContext } from "@moonsjs/server";
|
|
25
|
+
var RPC_PREFIX = "/_moons/fn/";
|
|
26
|
+
var HEALTH_PATH = "/_moons/health";
|
|
27
|
+
function createRequestHandler(options) {
|
|
28
|
+
const { routes, config, routesDir, loadRoute, shell } = options;
|
|
29
|
+
const rpc = createRpcHandler(config.security === undefined ? {} : { security: config.security });
|
|
30
|
+
const notFoundRoute = routes.find((route) => route.kind === "404");
|
|
31
|
+
const errorRoute = routes.find((route) => route.kind === "error");
|
|
32
|
+
async function renderRoute(resolved, request, ctx) {
|
|
33
|
+
const entries = [...resolved.layouts, resolved.route];
|
|
34
|
+
const keys = entries.map((entry) => routeKeyFromFile(routesDir, entry.file));
|
|
35
|
+
const modules = await loadModules(keys, loadRoute);
|
|
36
|
+
const data = await runLoaders(entries.map((entry, index) => ({ file: keys[index], module: modules[index] })), { params: resolved.params, ctx, req: request });
|
|
37
|
+
const pageKey = routeKeyFromFile(routesDir, resolved.route.file);
|
|
38
|
+
const pageModule = modules[modules.length - 1];
|
|
39
|
+
const entry = { page: pageKey, layouts: keys.slice(0, -1) };
|
|
40
|
+
const searchParams = Object.fromEntries(new URL(request.url).searchParams);
|
|
41
|
+
const markup = renderToMarkup(entry, { page: pageModule, layouts: modules.slice(0, -1) }, resolved.params, data, searchParams);
|
|
42
|
+
const payload = encodeSsrPayload({
|
|
43
|
+
key: pageKey,
|
|
44
|
+
params: resolved.params,
|
|
45
|
+
searchParams,
|
|
46
|
+
data
|
|
47
|
+
});
|
|
48
|
+
return htmlResponse(await shell(markup, payload, request.url), 200);
|
|
49
|
+
}
|
|
50
|
+
async function notFoundResponse(request) {
|
|
51
|
+
if (notFoundRoute !== undefined) {
|
|
52
|
+
try {
|
|
53
|
+
const key = routeKeyFromFile(routesDir, notFoundRoute.file);
|
|
54
|
+
const [module] = await loadModules([key], loadRoute);
|
|
55
|
+
const payload = encodeSsrPayload({ key: NOT_FOUND_ROUTE_KEY, params: {}, data: {} });
|
|
56
|
+
const markup = renderToMarkup({ page: key, layouts: [] }, { page: module, layouts: [] }, {}, {});
|
|
57
|
+
return htmlResponse(await shell(markup, payload, request.url), 404);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("[moons] gagal merender _404 kustom:", error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const payload = encodeSsrPayload({ key: "", params: {}, data: {} });
|
|
63
|
+
const html = await shell("<h1>404</h1><p>Halaman tidak ditemukan.</p>", payload, request.url);
|
|
64
|
+
return htmlResponse(html, 404);
|
|
65
|
+
}
|
|
66
|
+
async function routeErrorResponse(error, request) {
|
|
67
|
+
console.error("[moons] route error:", error);
|
|
68
|
+
if (errorRoute !== undefined) {
|
|
69
|
+
try {
|
|
70
|
+
const key = routeKeyFromFile(routesDir, errorRoute.file);
|
|
71
|
+
const [module] = await loadModules([key], loadRoute);
|
|
72
|
+
const isProduction = process.env["NODE_ENV"] === "production";
|
|
73
|
+
const serialized = isProduction ? undefined : serializeError(error);
|
|
74
|
+
const payload = encodeSsrPayload({
|
|
75
|
+
key: ERROR_ROUTE_KEY,
|
|
76
|
+
params: {},
|
|
77
|
+
data: {},
|
|
78
|
+
...serialized === undefined ? {} : { error: serialized }
|
|
79
|
+
});
|
|
80
|
+
const markup = renderToMarkup({ page: key, layouts: [] }, { page: module, layouts: [] }, {}, {}, {}, serialized === undefined ? undefined : reviveError(serialized));
|
|
81
|
+
return htmlResponse(await shell(markup, payload, request.url), 500);
|
|
82
|
+
} catch (renderError) {
|
|
83
|
+
console.error("[moons] gagal merender _error kustom:", renderError);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return internalErrorResponse(error, { log: false });
|
|
87
|
+
}
|
|
88
|
+
return async function handler(request) {
|
|
89
|
+
try {
|
|
90
|
+
const url = new URL(request.url);
|
|
91
|
+
if (url.pathname === HEALTH_PATH) {
|
|
92
|
+
return healthResponse(request.method);
|
|
93
|
+
}
|
|
94
|
+
const ctx = await config.context(request);
|
|
95
|
+
const respond = composeMiddleware(config.middleware ?? [], ctx, async () => {
|
|
96
|
+
if (url.pathname.startsWith(RPC_PREFIX)) {
|
|
97
|
+
return await rpc(request, url.pathname.slice(RPC_PREFIX.length), ctx);
|
|
98
|
+
}
|
|
99
|
+
const resolved = resolveRoute(url.pathname, routes);
|
|
100
|
+
if (resolved === null) {
|
|
101
|
+
return await notFoundResponse(request);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
return await renderRoute(resolved, request, ctx);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return await routeErrorResponse(error, request);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
return await runWithContext(ctx, () => respond(request));
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return internalErrorResponse(error);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function loadModules(keys, loadRoute) {
|
|
116
|
+
return await Promise.all(keys.map(async (key) => {
|
|
117
|
+
const load = loadRoute[key];
|
|
118
|
+
if (load === undefined) {
|
|
119
|
+
throw new MoonsError({
|
|
120
|
+
code: "E_INTERNAL",
|
|
121
|
+
message: `modul route \`${key}\` tidak terdaftar`,
|
|
122
|
+
details: "Registry route tidak sinkron dengan hasil scan.",
|
|
123
|
+
suggestions: ["Jalankan ulang `bun run build`, atau restart dev server"]
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return await load();
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
function internalErrorResponse(error, options = {}) {
|
|
130
|
+
if (options.log !== false) {
|
|
131
|
+
console.error("[moons] request error:", error);
|
|
132
|
+
}
|
|
133
|
+
const detail = process.env["NODE_ENV"] !== "production" && error instanceof MoonsError ? `<pre>${escapeHtml(error.format())}</pre>` : "<p>Terjadi kesalahan internal.</p>";
|
|
134
|
+
return htmlResponse(`<!doctype html><html lang="id"><head><meta charset="utf-8"><title>500</title></head><body><h1>500</h1>${detail}</body></html>`, 500);
|
|
135
|
+
}
|
|
136
|
+
function htmlResponse(html, status) {
|
|
137
|
+
return new Response(html, {
|
|
138
|
+
status,
|
|
139
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function healthResponse(method) {
|
|
143
|
+
const headers = { "content-type": "application/json; charset=utf-8" };
|
|
144
|
+
if (method === "GET" || method === "HEAD") {
|
|
145
|
+
return new Response(method === "HEAD" ? null : JSON.stringify({ ok: true }), {
|
|
146
|
+
status: 200,
|
|
147
|
+
headers
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return new Response("Method Not Allowed", {
|
|
151
|
+
status: 405,
|
|
152
|
+
headers: { allow: "GET, HEAD" }
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function escapeHtml(text) {
|
|
156
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
157
|
+
}
|
|
158
|
+
// src/server/node-server.ts
|
|
159
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
160
|
+
import { createServer } from "node:http";
|
|
161
|
+
import { extname, join, resolve, sep } from "node:path";
|
|
162
|
+
|
|
163
|
+
// src/server/shutdown.ts
|
|
164
|
+
function installGracefulShutdown(server, options = {}) {
|
|
165
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
166
|
+
const log = options.log ?? ((message) => console.log(message));
|
|
167
|
+
const exit = options.exit ?? ((code) => process.exit(code));
|
|
168
|
+
let shuttingDown = false;
|
|
169
|
+
return (signal) => {
|
|
170
|
+
if (shuttingDown) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
shuttingDown = true;
|
|
174
|
+
log(`[moons] menerima ${signal} — berhenti menerima koneksi, drain...`);
|
|
175
|
+
const timer = setTimeout(() => {
|
|
176
|
+
log(`[moons] drain melewati ${timeoutMs}ms — keluar paksa`);
|
|
177
|
+
const closeAll = server.closeAllConnections;
|
|
178
|
+
try {
|
|
179
|
+
closeAll?.call(server);
|
|
180
|
+
} catch {}
|
|
181
|
+
exit(1);
|
|
182
|
+
}, timeoutMs);
|
|
183
|
+
timer.unref?.();
|
|
184
|
+
try {
|
|
185
|
+
server.closeIdleConnections?.();
|
|
186
|
+
} catch {}
|
|
187
|
+
server.close((error) => {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
if (error !== undefined) {
|
|
190
|
+
log(`[moons] penutupan server gagal: ${error.message}`);
|
|
191
|
+
exit(1);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
log("[moons] shutdown selesai (drain penuh)");
|
|
195
|
+
exit(0);
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/server/node-server.ts
|
|
201
|
+
var MIME_TYPES = {
|
|
202
|
+
".js": "text/javascript; charset=utf-8",
|
|
203
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
204
|
+
".css": "text/css; charset=utf-8",
|
|
205
|
+
".html": "text/html; charset=utf-8",
|
|
206
|
+
".json": "application/json; charset=utf-8",
|
|
207
|
+
".map": "application/json; charset=utf-8",
|
|
208
|
+
".ico": "image/x-icon",
|
|
209
|
+
".png": "image/png",
|
|
210
|
+
".jpg": "image/jpeg",
|
|
211
|
+
".jpeg": "image/jpeg",
|
|
212
|
+
".gif": "image/gif",
|
|
213
|
+
".svg": "image/svg+xml",
|
|
214
|
+
".webp": "image/webp",
|
|
215
|
+
".avif": "image/avif",
|
|
216
|
+
".woff": "font/woff",
|
|
217
|
+
".woff2": "font/woff2",
|
|
218
|
+
".ttf": "font/ttf",
|
|
219
|
+
".txt": "text/plain; charset=utf-8",
|
|
220
|
+
".wasm": "application/wasm"
|
|
221
|
+
};
|
|
222
|
+
function startNodeServer(handler, options = {}) {
|
|
223
|
+
const clientDir = resolve(options.clientDir ?? join(process.cwd(), "dist/client"));
|
|
224
|
+
const port = options.port ?? Number(process.env["PORT"] ?? 3000);
|
|
225
|
+
const server = createServer((req, res) => {
|
|
226
|
+
(async () => {
|
|
227
|
+
try {
|
|
228
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
229
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
230
|
+
const staticFile = resolveStaticFile(clientDir, url.pathname);
|
|
231
|
+
if (staticFile !== null) {
|
|
232
|
+
const body = readFileSync(staticFile);
|
|
233
|
+
res.statusCode = 200;
|
|
234
|
+
res.setHeader("content-type", MIME_TYPES[extname(staticFile).toLowerCase()] ?? "application/octet-stream");
|
|
235
|
+
res.end(req.method === "HEAD" ? undefined : body);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const response = await handler(await toWebRequest2(req));
|
|
240
|
+
await sendNodeResponse2(res, response);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error("[moons] server error:", error);
|
|
243
|
+
res.statusCode = 500;
|
|
244
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
245
|
+
res.end("Internal Server Error");
|
|
246
|
+
}
|
|
247
|
+
})();
|
|
248
|
+
});
|
|
249
|
+
server.listen(port, () => {
|
|
250
|
+
const address = server.address();
|
|
251
|
+
const boundPort = typeof address === "object" && address !== null ? address.port : port;
|
|
252
|
+
console.log(`[moons] server listening on http://localhost:${boundPort}`);
|
|
253
|
+
});
|
|
254
|
+
if (options.handleSignals !== false) {
|
|
255
|
+
const shutdown = installGracefulShutdown(server, options.shutdownTimeoutMs === undefined ? {} : { timeoutMs: options.shutdownTimeoutMs });
|
|
256
|
+
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
257
|
+
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
258
|
+
}
|
|
259
|
+
return server;
|
|
260
|
+
}
|
|
261
|
+
function resolveStaticFile(clientDir, pathname) {
|
|
262
|
+
let decoded;
|
|
263
|
+
try {
|
|
264
|
+
decoded = decodeURIComponent(pathname);
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const candidate = resolve(clientDir, `.${decoded}`);
|
|
269
|
+
if (candidate !== clientDir && !candidate.startsWith(clientDir + sep)) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (!existsSync(candidate)) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
if (!statSync(candidate).isFile()) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return candidate;
|
|
279
|
+
}
|
|
280
|
+
// src/server/test-server.ts
|
|
281
|
+
function createTestServer(options, baseUrl = "http://localhost") {
|
|
282
|
+
const shell = options.shell ?? ((markup, payload) => injectShell2(DEFAULT_TEMPLATE2, markup, payload));
|
|
283
|
+
const handler = createRequestHandler({
|
|
284
|
+
routes: options.routes,
|
|
285
|
+
config: options.config,
|
|
286
|
+
routesDir: options.routesDir,
|
|
287
|
+
loadRoute: options.loadRoute,
|
|
288
|
+
shell
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
handler,
|
|
292
|
+
fetch: (input, init) => handler(new Request(new URL(input, baseUrl), init))
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
export {
|
|
296
|
+
DEFAULT_TEMPLATE2 as DEFAULT_TEMPLATE,
|
|
297
|
+
ROOT_PLACEHOLDER2 as ROOT_PLACEHOLDER,
|
|
298
|
+
createDevShell2 as createDevShell,
|
|
299
|
+
createFileShell2 as createFileShell,
|
|
300
|
+
createRequestHandler,
|
|
301
|
+
createTestServer,
|
|
302
|
+
injectShell2 as injectShell,
|
|
303
|
+
installDevMiddleware2 as installDevMiddleware,
|
|
304
|
+
installGracefulShutdown,
|
|
305
|
+
resolveStaticFile,
|
|
306
|
+
sendNodeResponse2 as sendNodeResponse,
|
|
307
|
+
startNodeServer,
|
|
308
|
+
toWebRequest2 as toWebRequest
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
//# debugId=EE41489A37A603CE64756E2164756E21
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/server/handler.ts", "../src/server/node-server.ts", "../src/server/shutdown.ts", "../src/server/test-server.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import {\n MoonsError,\n composeMiddleware,\n resolveRoute,\n routeKeyFromFile,\n runLoaders,\n type MoonsConfig,\n type ResolvedRoute,\n type RouteDefinition,\n type RouteModule,\n} from \"@moonsjs/core\";\nimport { encodeSsrPayload, reviveError, serializeError } from \"@moonsjs/core/serialize\";\nimport { renderToMarkup } from \"@moonsjs/react/ssr\";\nimport type { ClientRouteEntry, RouteModuleShape } from \"@moonsjs/react/internal\";\nimport { createRpcHandler, runWithContext } from \"@moonsjs/server\";\nimport { ERROR_ROUTE_KEY, NOT_FOUND_ROUTE_KEY } from \"../manifest\";\nimport type { Shell } from \"./shell\";\n\nconst RPC_PREFIX = \"/_moons/fn/\";\nconst HEALTH_PATH = \"/_moons/health\";\n\nexport interface RequestHandlerOptions {\n routes: RouteDefinition[];\n /** Config yang sudah divalidasi. */\n config: MoonsConfig;\n /** Direktori route absolut; dipakai untuk derivasi key stabil. */\n routesDir: string;\n /** Peta import() modul route, key = routeKeyFromFile. */\n loadRoute: Record<string, () => Promise<RouteModule>>;\n shell: Shell;\n}\n\n/**\n * Handler HTML: context → middleware global → resolve route → loader paralel\n * → render React → inject payload devalue (PRD §11.2, §14.2, §17).\n */\nexport function createRequestHandler(\n options: RequestHandlerOptions,\n): (request: Request) => Promise<Response> {\n const { routes, config, routesDir, loadRoute, shell } = options;\n const rpc = createRpcHandler(\n config.security === undefined ? {} : { security: config.security },\n );\n const notFoundRoute = routes.find((route) => route.kind === \"404\");\n const errorRoute = routes.find((route) => route.kind === \"error\");\n\n async function renderRoute(\n resolved: ResolvedRoute,\n request: Request,\n ctx: Ctx,\n ): Promise<Response> {\n const entries = [...resolved.layouts, resolved.route];\n const keys = entries.map((entry) => routeKeyFromFile(routesDir, entry.file));\n const modules = await loadModules(keys, loadRoute);\n\n const data = await runLoaders(\n entries.map((entry, index) => ({ file: keys[index] as string, module: modules[index] as RouteModule })),\n { params: resolved.params, ctx, req: request },\n );\n\n const pageKey = routeKeyFromFile(routesDir, resolved.route.file);\n const pageModule = modules[modules.length - 1] as RouteModule;\n const entry: ClientRouteEntry = { page: pageKey, layouts: keys.slice(0, -1) };\n // Addendum A.3: query string ikut ke payload agar server/client identik.\n const searchParams = Object.fromEntries(new URL(request.url).searchParams);\n\n const markup = renderToMarkup(\n entry,\n { page: pageModule as RouteModuleShape, layouts: modules.slice(0, -1) as RouteModuleShape[] },\n resolved.params,\n data,\n searchParams,\n );\n const payload = encodeSsrPayload({\n key: pageKey,\n params: resolved.params,\n searchParams,\n data,\n });\n\n return htmlResponse(await shell(markup, payload, request.url), 200);\n }\n\n /** §11.2: halaman `_404.tsx` kustom (standalone), fallback bawaan. */\n async function notFoundResponse(request: Request): Promise<Response> {\n if (notFoundRoute !== undefined) {\n try {\n const key = routeKeyFromFile(routesDir, notFoundRoute.file);\n const [module] = await loadModules([key], loadRoute);\n const payload = encodeSsrPayload({ key: NOT_FOUND_ROUTE_KEY, params: {}, data: {} });\n const markup = renderToMarkup(\n { page: key, layouts: [] },\n { page: module as RouteModuleShape, layouts: [] },\n {},\n {},\n );\n return htmlResponse(await shell(markup, payload, request.url), 404);\n } catch (error) {\n console.error(\"[moons] gagal merender _404 kustom:\", error);\n }\n }\n\n const payload = encodeSsrPayload({ key: \"\", params: {}, data: {} });\n const html = await shell(\n \"<h1>404</h1><p>Halaman tidak ditemukan.</p>\",\n payload,\n request.url,\n );\n return htmlResponse(html, 404);\n }\n\n /**\n * §11.2: halaman `_error.tsx` kustom dengan `RouteErrorProvider`.\n * Production tidak men-serialisasi error apa pun (§16.6); dev mengirim aslinya.\n */\n async function routeErrorResponse(error: unknown, request: Request): Promise<Response> {\n console.error(\"[moons] route error:\", error);\n\n if (errorRoute !== undefined) {\n try {\n const key = routeKeyFromFile(routesDir, errorRoute.file);\n const [module] = await loadModules([key], loadRoute);\n const isProduction = process.env[\"NODE_ENV\"] === \"production\";\n const serialized = isProduction ? undefined : serializeError(error);\n const payload = encodeSsrPayload({\n key: ERROR_ROUTE_KEY,\n params: {},\n data: {},\n ...(serialized === undefined ? {} : { error: serialized }),\n });\n const markup = renderToMarkup(\n { page: key, layouts: [] },\n { page: module as RouteModuleShape, layouts: [] },\n {},\n {},\n {},\n serialized === undefined ? undefined : reviveError(serialized),\n );\n return htmlResponse(await shell(markup, payload, request.url), 500);\n } catch (renderError) {\n console.error(\"[moons] gagal merender _error kustom:\", renderError);\n }\n }\n\n return internalErrorResponse(error, { log: false });\n }\n\n return async function handler(request: Request): Promise<Response> {\n try {\n const url = new URL(request.url);\n\n // §19.5: health ditangani paling awal — tetap hidup walau kode user rusak.\n if (url.pathname === HEALTH_PATH) {\n return healthResponse(request.method);\n }\n\n // `MoonsConfig.context` bertipe `unknown` (menghindari referensi sirkular\n // ke global `Ctx` — §13); kontraknya tetap `Promise<Ctx>`, jadi cast di\n // batas runtime ini. Tanpa cast, tsc app user yang memuat `@moonsjs/vite/server`\n // (mis. `createTestServer` §19.5) melihat `unknown` vs `Ctx` global app.\n const ctx = (await config.context(request)) as Ctx;\n\n const respond = composeMiddleware(config.middleware ?? [], ctx, async () => {\n if (url.pathname.startsWith(RPC_PREFIX)) {\n return await rpc(request, url.pathname.slice(RPC_PREFIX.length), ctx);\n }\n const resolved = resolveRoute(url.pathname, routes);\n if (resolved === null) {\n return await notFoundResponse(request);\n }\n try {\n return await renderRoute(resolved, request, ctx);\n } catch (error) {\n return await routeErrorResponse(error, request);\n }\n });\n\n return await runWithContext(ctx, () => respond(request));\n } catch (error) {\n return internalErrorResponse(error);\n }\n };\n}\n\nasync function loadModules(\n keys: string[],\n loadRoute: Record<string, () => Promise<RouteModule>>,\n): Promise<RouteModule[]> {\n return await Promise.all(\n keys.map(async (key) => {\n const load = loadRoute[key];\n if (load === undefined) {\n throw new MoonsError({\n code: \"E_INTERNAL\",\n message: `modul route \\`${key}\\` tidak terdaftar`,\n details: \"Registry route tidak sinkron dengan hasil scan.\",\n suggestions: [\"Jalankan ulang `bun run build`, atau restart dev server\"],\n });\n }\n return await load();\n }),\n );\n}\n\nfunction internalErrorResponse(error: unknown, options: { log?: boolean } = {}): Response {\n if (options.log !== false) {\n console.error(\"[moons] request error:\", error);\n }\n\n const detail =\n process.env[\"NODE_ENV\"] !== \"production\" && error instanceof MoonsError\n ? `<pre>${escapeHtml(error.format())}</pre>`\n : \"<p>Terjadi kesalahan internal.</p>\";\n\n return htmlResponse(\n `<!doctype html><html lang=\"id\"><head><meta charset=\"utf-8\"><title>500</title></head><body><h1>500</h1>${detail}</body></html>`,\n 500,\n );\n}\n\nfunction htmlResponse(html: string, status: number): Response {\n return new Response(html, {\n status,\n headers: { \"content-type\": \"text/html; charset=utf-8\" },\n });\n}\n\n/** §19.5: `/_moons/health` → `{ ok: true }`. */\nfunction healthResponse(method: string): Response {\n const headers = { \"content-type\": \"application/json; charset=utf-8\" };\n if (method === \"GET\" || method === \"HEAD\") {\n return new Response(method === \"HEAD\" ? null : JSON.stringify({ ok: true }), {\n status: 200,\n headers,\n });\n }\n return new Response(\"Method Not Allowed\", {\n status: 405,\n headers: { allow: \"GET, HEAD\" },\n });\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n}\n",
|
|
6
|
+
"import { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { createServer, type Server } from \"node:http\";\nimport { extname, join, resolve, sep } from \"node:path\";\nimport { sendNodeResponse, toWebRequest } from \"./node\";\nimport { installGracefulShutdown } from \"./shutdown\";\n\nconst MIME_TYPES: Record<string, string> = {\n \".js\": \"text/javascript; charset=utf-8\",\n \".mjs\": \"text/javascript; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".html\": \"text/html; charset=utf-8\",\n \".json\": \"application/json; charset=utf-8\",\n \".map\": \"application/json; charset=utf-8\",\n \".ico\": \"image/x-icon\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".wasm\": \"application/wasm\",\n};\n\nexport interface NodeServerOptions {\n port?: number;\n clientDir?: string;\n /** Pasang handler SIGTERM/SIGINT (default true). */\n handleSignals?: boolean;\n /** Batas drain shutdown (default 10_000 ms). */\n shutdownTimeoutMs?: number;\n}\n\n/**\n * Serve produksi (M1 §19.2, §18): static dari `dist/client` + handler SSR.\n * Berjalan di Bun dan Node (hanya API `node:*`).\n */\nexport function startNodeServer(\n handler: (request: Request) => Promise<Response>,\n options: NodeServerOptions = {},\n): Server {\n const clientDir = resolve(options.clientDir ?? join(process.cwd(), \"dist/client\"));\n const port = options.port ?? Number(process.env[\"PORT\"] ?? 3000);\n\n const server = createServer((req, res) => {\n void (async () => {\n try {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n\n if (req.method === \"GET\" || req.method === \"HEAD\") {\n const staticFile = resolveStaticFile(clientDir, url.pathname);\n if (staticFile !== null) {\n const body = readFileSync(staticFile);\n res.statusCode = 200;\n res.setHeader(\"content-type\", MIME_TYPES[extname(staticFile).toLowerCase()] ?? \"application/octet-stream\");\n res.end(req.method === \"HEAD\" ? undefined : body);\n return;\n }\n }\n\n const response = await handler(await toWebRequest(req));\n await sendNodeResponse(res, response);\n } catch (error) {\n console.error(\"[moons] server error:\", error);\n res.statusCode = 500;\n res.setHeader(\"content-type\", \"text/plain; charset=utf-8\");\n res.end(\"Internal Server Error\");\n }\n })();\n });\n\n server.listen(port, () => {\n const address = server.address();\n const boundPort = typeof address === \"object\" && address !== null ? address.port : port;\n console.log(`[moons] server listening on http://localhost:${boundPort}`);\n });\n\n if (options.handleSignals !== false) {\n const shutdown = installGracefulShutdown(\n server,\n options.shutdownTimeoutMs === undefined ? {} : { timeoutMs: options.shutdownTimeoutMs },\n );\n // `once`: sinyal kedua memakai perilaku default (proses langsung berhenti).\n process.once(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n process.once(\"SIGINT\", () => shutdown(\"SIGINT\"));\n }\n\n return server;\n}\n\n/** Resolve file static dengan proteksi path traversal. */\nexport function resolveStaticFile(clientDir: string, pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n\n const candidate = resolve(clientDir, `.${decoded}`);\n if (candidate !== clientDir && !candidate.startsWith(clientDir + sep)) {\n return null;\n }\n if (!existsSync(candidate)) {\n return null;\n }\n if (!statSync(candidate).isFile()) {\n return null;\n }\n return candidate;\n}\n",
|
|
7
|
+
"import type { Server } from \"node:http\";\n\nexport interface ShutdownOptions {\n /** Batas drain sebelum keluar paksa (default 10_000 ms). */\n timeoutMs?: number;\n log?: (message: string) => void;\n /** Injectable untuk test; default `process.exit`. */\n exit?: (code: number) => void;\n}\n\n/**\n * Graceful shutdown (PRD §19.5): SIGTERM/SIGINT → stop menerima koneksi baru →\n * drain request berjalan → exit 0. Melewati timeout → keluar paksa exit 1.\n *\n * Handler dipasang `once` oleh pemanggil, sehingga sinyal kedua memakai\n * perilaku default (proses langsung berhenti).\n */\nexport function installGracefulShutdown(\n server: Server,\n options: ShutdownOptions = {},\n): (signal: NodeJS.Signals) => void {\n const timeoutMs = options.timeoutMs ?? 10_000;\n const log = options.log ?? ((message: string) => console.log(message));\n const exit = options.exit ?? ((code: number) => process.exit(code));\n\n let shuttingDown = false;\n\n return (signal: NodeJS.Signals): void => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n log(`[moons] menerima ${signal} — berhenti menerima koneksi, drain...`);\n\n const timer = setTimeout(() => {\n log(`[moons] drain melewati ${timeoutMs}ms — keluar paksa`);\n const closeAll = (server as { closeAllConnections?: () => void }).closeAllConnections;\n try {\n closeAll?.call(server);\n } catch {\n // abaikan: keluar paksa tetap jalan\n }\n exit(1);\n }, timeoutMs);\n timer.unref?.();\n\n try {\n (server as { closeIdleConnections?: () => void }).closeIdleConnections?.();\n } catch {\n // abaikan: fallback timeout tetap ada\n }\n\n server.close((error?: Error) => {\n clearTimeout(timer);\n if (error !== undefined) {\n log(`[moons] penutupan server gagal: ${error.message}`);\n exit(1);\n return;\n }\n log(\"[moons] shutdown selesai (drain penuh)\");\n exit(0);\n });\n };\n}\n",
|
|
8
|
+
"import type { MoonsConfig, RouteDefinition, RouteModule } from \"@moonsjs/core\";\nimport { createRequestHandler } from \"./handler\";\nimport { DEFAULT_TEMPLATE, injectShell, type Shell } from \"./shell\";\n\nexport interface TestServerOptions {\n routes: RouteDefinition[];\n config: MoonsConfig;\n routesDir: string;\n loadRoute: Record<string, () => Promise<RouteModule>>;\n /** Default: shell in-memory dari `DEFAULT_TEMPLATE` (tanpa file). */\n shell?: Shell;\n}\n\nexport interface TestServer {\n handler: (request: Request) => Promise<Response>;\n /** Dispatch request langsung ke handler — tanpa port/jaringan (§19.5). */\n fetch(input: string, init?: RequestInit): Promise<Response>;\n}\n\n/**\n * Utility test in-process (PRD §19.5): jalankan handler tanpa listen port.\n * Dipakai test aplikasi user maupun test internal framework.\n */\nexport function createTestServer(\n options: TestServerOptions,\n baseUrl = \"http://localhost\",\n): TestServer {\n const shell: Shell =\n options.shell ?? ((markup, payload) => injectShell(DEFAULT_TEMPLATE, markup, payload));\n\n const handler = createRequestHandler({\n routes: options.routes,\n config: options.config,\n routesDir: options.routesDir,\n loadRoute: options.loadRoute,\n shell,\n });\n\n return {\n handler,\n fetch: (input: string, init?: RequestInit) =>\n handler(new Request(new URL(input, baseUrl), init)),\n };\n}\n"
|
|
9
|
+
],
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "EE41489A37A603CE64756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Server } from "node:http";
|
|
2
|
+
export interface NodeServerOptions {
|
|
3
|
+
port?: number;
|
|
4
|
+
clientDir?: string;
|
|
5
|
+
/** Pasang handler SIGTERM/SIGINT (default true). */
|
|
6
|
+
handleSignals?: boolean;
|
|
7
|
+
/** Batas drain shutdown (default 10_000 ms). */
|
|
8
|
+
shutdownTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Serve produksi (M1 §19.2, §18): static dari `dist/client` + handler SSR.
|
|
12
|
+
* Berjalan di Bun dan Node (hanya API `node:*`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function startNodeServer(handler: (request: Request) => Promise<Response>, options?: NodeServerOptions): Server;
|
|
15
|
+
/** Resolve file static dengan proteksi path traversal. */
|
|
16
|
+
export declare function resolveStaticFile(clientDir: string, pathname: string): string | null;
|
|
17
|
+
//# sourceMappingURL=node-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-server.d.ts","sourceRoot":"","sources":["../../src/server/node-server.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AA2BtD,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAChD,OAAO,GAAE,iBAAsB,GAC9B,MAAM,CAgDR;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmBpF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
/** Konversi request Node → Web `Request` (body diteruskan untuk non-GET/HEAD, §7.3). */
|
|
3
|
+
export declare function toWebRequest(req: IncomingMessage): Promise<Request>;
|
|
4
|
+
/** Tulis Web `Response` ke `ServerResponse` Node. */
|
|
5
|
+
export declare function sendNodeResponse(res: ServerResponse, response: Response): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/server/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIjE,wFAAwF;AACxF,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BzE;AAqBD,qDAAqD;AACrD,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7F"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ViteDevServer } from "vite";
|
|
2
|
+
export type Shell = (markup: string, payload: string, url: string) => string | Promise<string>;
|
|
3
|
+
export declare const ROOT_PLACEHOLDER = "<div id=\"root\"></div>";
|
|
4
|
+
export declare const DEFAULT_TEMPLATE: string;
|
|
5
|
+
/** Inject markup SSR + payload devalue ke shell HTML (PRD §8.2, §17). */
|
|
6
|
+
export declare function injectShell(template: string, markup: string, payload: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Shell untuk dev: baca `index.html` dari disk setiap request (selalu segar)
|
|
9
|
+
* lalu lewatkan ke Vite `transformIndexHtml` (inject `/@vite/client`, dsb).
|
|
10
|
+
*/
|
|
11
|
+
export declare function createDevShell(server: ViteDevServer, options: {
|
|
12
|
+
templatePath: string;
|
|
13
|
+
}): Shell;
|
|
14
|
+
/** Shell untuk produksi: baca `dist/client/index.html` sekali dari cwd. */
|
|
15
|
+
export declare function createFileShell(options?: {
|
|
16
|
+
clientDir?: string;
|
|
17
|
+
}): Shell;
|
|
18
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/server/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAE1C,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAE/F,eAAO,MAAM,gBAAgB,4BAA0B,CAAC;AAExD,eAAO,MAAM,gBAAgB,QAcjB,CAAC;AAEb,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAcrF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAQ9F;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,KAAK,CAmB3E"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
export interface ShutdownOptions {
|
|
3
|
+
/** Batas drain sebelum keluar paksa (default 10_000 ms). */
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
log?: (message: string) => void;
|
|
6
|
+
/** Injectable untuk test; default `process.exit`. */
|
|
7
|
+
exit?: (code: number) => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Graceful shutdown (PRD §19.5): SIGTERM/SIGINT → stop menerima koneksi baru →
|
|
11
|
+
* drain request berjalan → exit 0. Melewati timeout → keluar paksa exit 1.
|
|
12
|
+
*
|
|
13
|
+
* Handler dipasang `once` oleh pemanggil, sehingga sinyal kedua memakai
|
|
14
|
+
* perilaku default (proses langsung berhenti).
|
|
15
|
+
*/
|
|
16
|
+
export declare function installGracefulShutdown(server: Server, options?: ShutdownOptions): (signal: NodeJS.Signals) => void;
|
|
17
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/server/shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,qDAAqD;IACrD,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,eAAoB,GAC5B,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,CA2ClC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MoonsConfig, RouteDefinition, RouteModule } from "@moonsjs/core";
|
|
2
|
+
import { type Shell } from "./shell";
|
|
3
|
+
export interface TestServerOptions {
|
|
4
|
+
routes: RouteDefinition[];
|
|
5
|
+
config: MoonsConfig;
|
|
6
|
+
routesDir: string;
|
|
7
|
+
loadRoute: Record<string, () => Promise<RouteModule>>;
|
|
8
|
+
/** Default: shell in-memory dari `DEFAULT_TEMPLATE` (tanpa file). */
|
|
9
|
+
shell?: Shell;
|
|
10
|
+
}
|
|
11
|
+
export interface TestServer {
|
|
12
|
+
handler: (request: Request) => Promise<Response>;
|
|
13
|
+
/** Dispatch request langsung ke handler — tanpa port/jaringan (§19.5). */
|
|
14
|
+
fetch(input: string, init?: RequestInit): Promise<Response>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Utility test in-process (PRD §19.5): jalankan handler tanpa listen port.
|
|
18
|
+
* Dipakai test aplikasi user maupun test internal framework.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createTestServer(options: TestServerOptions, baseUrl?: string): TestServer;
|
|
21
|
+
//# sourceMappingURL=test-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-server.d.ts","sourceRoot":"","sources":["../../src/server/test-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE/E,OAAO,EAAiC,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACtD,qEAAqE;IACrE,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjD,0EAA0E;IAC1E,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC7D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,iBAAiB,EAC1B,OAAO,SAAqB,GAC3B,UAAU,CAiBZ"}
|