@moonsjs/vite 0.1.3 → 0.1.4
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/{index-1sm9pvvd.js → index-33p1r65s.js} +58 -15
- package/dist/index-33p1r65s.js.map +14 -0
- package/dist/index.js +11 -7
- package/dist/index.js.map +3 -3
- package/dist/server/dev.d.ts +1 -0
- package/dist/server/dev.d.ts.map +1 -1
- package/dist/server/handler.d.ts +5 -0
- package/dist/server/handler.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +135 -15
- package/dist/server/index.js.map +8 -7
- package/dist/server/node-server.d.ts +3 -0
- package/dist/server/node-server.d.ts.map +1 -1
- package/dist/server/plugins.d.ts +26 -0
- package/dist/server/plugins.d.ts.map +1 -0
- package/dist/server/shell.d.ts +3 -3
- package/dist/server/shell.d.ts.map +1 -1
- package/dist/server/shutdown.d.ts +6 -0
- package/dist/server/shutdown.d.ts.map +1 -1
- package/dist/server/test-server.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/plugin.ts +9 -5
- package/src/server/dev.ts +36 -9
- package/src/server/handler.ts +58 -3
- package/src/server/index.ts +2 -0
- package/src/server/node-server.ts +23 -9
- package/src/server/plugins.ts +85 -0
- package/src/server/shell.ts +62 -8
- package/src/server/shutdown.ts +19 -2
- package/src/server/test-server.ts +2 -1
- package/dist/index-1sm9pvvd.js.map +0 -14
package/dist/server/index.js
CHANGED
|
@@ -9,19 +9,21 @@ import {
|
|
|
9
9
|
toWebRequest2,
|
|
10
10
|
sendNodeResponse2,
|
|
11
11
|
installDevMiddleware2
|
|
12
|
-
} from "../index-
|
|
12
|
+
} from "../index-33p1r65s.js";
|
|
13
13
|
|
|
14
14
|
// src/server/handler.ts
|
|
15
15
|
import {
|
|
16
16
|
MoonsError,
|
|
17
17
|
composeMiddleware,
|
|
18
|
+
isNotFoundSignal,
|
|
19
|
+
isRedirectSignal,
|
|
18
20
|
resolveRoute,
|
|
19
21
|
routeKeyFromFile,
|
|
20
22
|
runLoaders
|
|
21
23
|
} from "@moonsjs/core";
|
|
22
24
|
import { encodeSsrPayload, reviveError, serializeError } from "@moonsjs/core/serialize";
|
|
23
25
|
import { renderToMarkup } from "@moonsjs/react/ssr";
|
|
24
|
-
import { createRpcHandler, runWithContext } from "@moonsjs/server";
|
|
26
|
+
import { createRpcHandler, runWithContext, runWithRequestCache } from "@moonsjs/server";
|
|
25
27
|
var RPC_PREFIX = "/_moons/fn/";
|
|
26
28
|
var HEALTH_PATH = "/_moons/health";
|
|
27
29
|
function createRequestHandler(options) {
|
|
@@ -38,14 +40,16 @@ function createRequestHandler(options) {
|
|
|
38
40
|
const pageModule = modules[modules.length - 1];
|
|
39
41
|
const entry = { page: pageKey, layouts: keys.slice(0, -1) };
|
|
40
42
|
const searchParams = Object.fromEntries(new URL(request.url).searchParams);
|
|
43
|
+
const meta = await runRouteMeta(pageModule, data[pageKey], resolved.route.file);
|
|
41
44
|
const markup = renderToMarkup(entry, { page: pageModule, layouts: modules.slice(0, -1) }, resolved.params, data, searchParams);
|
|
42
45
|
const payload = encodeSsrPayload({
|
|
43
46
|
key: pageKey,
|
|
44
47
|
params: resolved.params,
|
|
45
48
|
searchParams,
|
|
46
|
-
data
|
|
49
|
+
data,
|
|
50
|
+
...meta === undefined ? {} : { meta }
|
|
47
51
|
});
|
|
48
|
-
return htmlResponse(await shell(markup, payload, request.url), 200);
|
|
52
|
+
return htmlResponse(await shell(markup, payload, request.url, meta), 200);
|
|
49
53
|
}
|
|
50
54
|
async function notFoundResponse(request) {
|
|
51
55
|
if (notFoundRoute !== undefined) {
|
|
@@ -92,6 +96,7 @@ function createRequestHandler(options) {
|
|
|
92
96
|
return healthResponse(request.method);
|
|
93
97
|
}
|
|
94
98
|
const ctx = await config.context(request);
|
|
99
|
+
options.onContext?.(ctx);
|
|
95
100
|
const respond = composeMiddleware(config.middleware ?? [], ctx, async () => {
|
|
96
101
|
if (url.pathname.startsWith(RPC_PREFIX)) {
|
|
97
102
|
return await rpc(request, url.pathname.slice(RPC_PREFIX.length), ctx);
|
|
@@ -103,10 +108,19 @@ function createRequestHandler(options) {
|
|
|
103
108
|
try {
|
|
104
109
|
return await renderRoute(resolved, request, ctx);
|
|
105
110
|
} catch (error) {
|
|
111
|
+
if (isRedirectSignal(error)) {
|
|
112
|
+
return new Response(null, {
|
|
113
|
+
status: error.status,
|
|
114
|
+
headers: { location: new URL(error.to, request.url).toString() }
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (isNotFoundSignal(error)) {
|
|
118
|
+
return await notFoundResponse(request);
|
|
119
|
+
}
|
|
106
120
|
return await routeErrorResponse(error, request);
|
|
107
121
|
}
|
|
108
122
|
});
|
|
109
|
-
return await runWithContext(ctx, () => respond(request));
|
|
123
|
+
return await runWithRequestCache(() => runWithContext(ctx, () => respond(request)));
|
|
110
124
|
} catch (error) {
|
|
111
125
|
return internalErrorResponse(error);
|
|
112
126
|
}
|
|
@@ -126,6 +140,35 @@ async function loadModules(keys, loadRoute) {
|
|
|
126
140
|
return await load();
|
|
127
141
|
}));
|
|
128
142
|
}
|
|
143
|
+
async function runRouteMeta(pageModule, pageData, file) {
|
|
144
|
+
const meta = pageModule.meta;
|
|
145
|
+
if (meta === undefined) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (typeof meta !== "function") {
|
|
149
|
+
throw new MoonsError({
|
|
150
|
+
code: "E_INTERNAL",
|
|
151
|
+
file,
|
|
152
|
+
message: "export `meta` harus berupa fungsi `({ data }) => RouteMeta`",
|
|
153
|
+
details: "Bentuk `meta` mengikuti PRD §11.2 (Addendum B.1).",
|
|
154
|
+
suggestions: [
|
|
155
|
+
'Ubah menjadi `export const meta = ({ data }) => ({ title: "..." })`',
|
|
156
|
+
"Atau hapus export `meta`"
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const result = await meta({ data: pageData });
|
|
161
|
+
if (result === null || typeof result !== "object" || Array.isArray(result)) {
|
|
162
|
+
throw new MoonsError({
|
|
163
|
+
code: "E_INTERNAL",
|
|
164
|
+
file,
|
|
165
|
+
message: "`meta()` harus mengembalikan objek RouteMeta",
|
|
166
|
+
details: "Nilai yang dikembalikan dipakai untuk <title>/description/og (Addendum B.1).",
|
|
167
|
+
suggestions: ['Kembalikan objek, mis. `({ title: "Halaman" })`']
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
129
172
|
function internalErrorResponse(error, options = {}) {
|
|
130
173
|
if (options.log !== false) {
|
|
131
174
|
console.error("[moons] request error:", error);
|
|
@@ -184,13 +227,22 @@ function installGracefulShutdown(server, options = {}) {
|
|
|
184
227
|
try {
|
|
185
228
|
server.closeIdleConnections?.();
|
|
186
229
|
} catch {}
|
|
187
|
-
server.close((error) => {
|
|
188
|
-
clearTimeout(timer);
|
|
230
|
+
server.close(async (error) => {
|
|
189
231
|
if (error !== undefined) {
|
|
232
|
+
clearTimeout(timer);
|
|
190
233
|
log(`[moons] penutupan server gagal: ${error.message}`);
|
|
191
234
|
exit(1);
|
|
192
235
|
return;
|
|
193
236
|
}
|
|
237
|
+
if (options.onShutdown !== undefined) {
|
|
238
|
+
timer.ref?.();
|
|
239
|
+
try {
|
|
240
|
+
await options.onShutdown();
|
|
241
|
+
} catch (hookError) {
|
|
242
|
+
log(`[moons] onShutdown gagal: ${String(hookError)}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
clearTimeout(timer);
|
|
194
246
|
log("[moons] shutdown selesai (drain penuh)");
|
|
195
247
|
exit(0);
|
|
196
248
|
});
|
|
@@ -246,13 +298,24 @@ function startNodeServer(handler, options = {}) {
|
|
|
246
298
|
}
|
|
247
299
|
})();
|
|
248
300
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
301
|
+
const listenAfterStart = () => {
|
|
302
|
+
server.listen(port, () => {
|
|
303
|
+
const address = server.address();
|
|
304
|
+
const boundPort = typeof address === "object" && address !== null ? address.port : port;
|
|
305
|
+
console.log(`[moons] server listening on http://localhost:${boundPort}`);
|
|
306
|
+
options.plugins?.ready();
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
if (options.plugins !== undefined) {
|
|
310
|
+
options.plugins.start().then(listenAfterStart);
|
|
311
|
+
} else {
|
|
312
|
+
listenAfterStart();
|
|
313
|
+
}
|
|
254
314
|
if (options.handleSignals !== false) {
|
|
255
|
-
const shutdown = installGracefulShutdown(server,
|
|
315
|
+
const shutdown = installGracefulShutdown(server, {
|
|
316
|
+
...options.shutdownTimeoutMs === undefined ? {} : { timeoutMs: options.shutdownTimeoutMs },
|
|
317
|
+
...options.plugins === undefined ? {} : { onShutdown: () => options.plugins?.shutdown() }
|
|
318
|
+
});
|
|
256
319
|
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
257
320
|
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
258
321
|
}
|
|
@@ -279,7 +342,7 @@ function resolveStaticFile(clientDir, pathname) {
|
|
|
279
342
|
}
|
|
280
343
|
// src/server/test-server.ts
|
|
281
344
|
function createTestServer(options, baseUrl = "http://localhost") {
|
|
282
|
-
const shell = options.shell ?? ((markup, payload) => injectShell2(DEFAULT_TEMPLATE2, markup, payload));
|
|
345
|
+
const shell = options.shell ?? ((markup, payload, _url, meta) => injectShell2(DEFAULT_TEMPLATE2, markup, payload, meta));
|
|
283
346
|
const handler = createRequestHandler({
|
|
284
347
|
routes: options.routes,
|
|
285
348
|
config: options.config,
|
|
@@ -292,11 +355,68 @@ function createTestServer(options, baseUrl = "http://localhost") {
|
|
|
292
355
|
fetch: (input, init) => handler(new Request(new URL(input, baseUrl), init))
|
|
293
356
|
};
|
|
294
357
|
}
|
|
358
|
+
// src/server/plugins.ts
|
|
359
|
+
import { MOONS_VERSION } from "@moonsjs/core";
|
|
360
|
+
function createPluginRuntime(options) {
|
|
361
|
+
const log = options.log ?? ((message) => console.warn(message));
|
|
362
|
+
const plugins = options.config.plugins ?? [];
|
|
363
|
+
const state = { ready: false, version: MOONS_VERSION };
|
|
364
|
+
const app = state;
|
|
365
|
+
const extensions = [];
|
|
366
|
+
const register = (fn) => {
|
|
367
|
+
extensions.push(fn);
|
|
368
|
+
};
|
|
369
|
+
for (const plugin of plugins) {
|
|
370
|
+
for (const route of options.routes) {
|
|
371
|
+
const frozen = Object.freeze({
|
|
372
|
+
...route,
|
|
373
|
+
params: [...route.params]
|
|
374
|
+
});
|
|
375
|
+
plugin.addRoute?.(frozen);
|
|
376
|
+
}
|
|
377
|
+
for (const middleware of options.middlewares) {
|
|
378
|
+
plugin.addMiddleware?.(middleware);
|
|
379
|
+
}
|
|
380
|
+
plugin.extendContext?.(register);
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
app,
|
|
384
|
+
async start() {
|
|
385
|
+
for (const plugin of plugins) {
|
|
386
|
+
await plugin.onStart?.(app);
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
async ready() {
|
|
390
|
+
state.ready = true;
|
|
391
|
+
for (const plugin of plugins) {
|
|
392
|
+
await plugin.onReady?.(app);
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
async shutdown() {
|
|
396
|
+
for (const plugin of plugins) {
|
|
397
|
+
try {
|
|
398
|
+
await plugin.onShutdown?.(app);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
log(`[moons] plugin \`${plugin.name}\` gagal saat onShutdown: ${String(error)}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
applyExtensions(ctx) {
|
|
405
|
+
for (const extend of extensions) {
|
|
406
|
+
const result = extend(ctx);
|
|
407
|
+
if (result !== null && typeof result === "object" && !Array.isArray(result)) {
|
|
408
|
+
Object.assign(ctx, result);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
295
414
|
export {
|
|
296
415
|
DEFAULT_TEMPLATE2 as DEFAULT_TEMPLATE,
|
|
297
416
|
ROOT_PLACEHOLDER2 as ROOT_PLACEHOLDER,
|
|
298
417
|
createDevShell2 as createDevShell,
|
|
299
418
|
createFileShell2 as createFileShell,
|
|
419
|
+
createPluginRuntime,
|
|
300
420
|
createRequestHandler,
|
|
301
421
|
createTestServer,
|
|
302
422
|
injectShell2 as injectShell,
|
|
@@ -308,4 +428,4 @@ export {
|
|
|
308
428
|
toWebRequest2 as toWebRequest
|
|
309
429
|
};
|
|
310
430
|
|
|
311
|
-
//# debugId=
|
|
431
|
+
//# debugId=871C5ACF2A35737064756E2164756E21
|
package/dist/server/index.js.map
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/server/handler.ts", "../src/server/node-server.ts", "../src/server/shutdown.ts", "../src/server/test-server.ts"],
|
|
3
|
+
"sources": ["../src/server/handler.ts", "../src/server/node-server.ts", "../src/server/shutdown.ts", "../src/server/test-server.ts", "../src/server/plugins.ts"],
|
|
4
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
|
|
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
|
|
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
|
|
5
|
+
"import {\n MoonsError,\n composeMiddleware,\n isNotFoundSignal,\n isRedirectSignal,\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, runWithRequestCache } 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 * Dipanggil setelah `config.context` per request (Addendum B.4: extender\n * `extendContext` plugin di-merge ke ctx).\n */\n onContext?: (ctx: Ctx) => void;\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 // Addendum B.1: eksekusi `meta()` page (data = hasil loader page itu sendiri).\n const meta = await runRouteMeta(pageModule, data[pageKey], resolved.route.file);\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 ...(meta === undefined ? {} : { meta }),\n });\n\n return htmlResponse(await shell(markup, payload, request.url, meta), 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 options.onContext?.(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 // Addendum B.2: sinyal routing dari loader/meta.\n if (isRedirectSignal(error)) {\n return new Response(null, {\n status: error.status,\n headers: { location: new URL(error.to, request.url).toString() },\n });\n }\n if (isNotFoundSignal(error)) {\n return await notFoundResponse(request);\n }\n return await routeErrorResponse(error, request);\n }\n });\n\n return await runWithRequestCache(() => 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\nasync function runRouteMeta(\n pageModule: RouteModule,\n pageData: unknown,\n file: string,\n): Promise<Record<string, unknown> | undefined> {\n const meta = (pageModule as { meta?: unknown }).meta;\n if (meta === undefined) {\n return undefined;\n }\n if (typeof meta !== \"function\") {\n throw new MoonsError({\n code: \"E_INTERNAL\",\n file,\n message: \"export `meta` harus berupa fungsi `({ data }) => RouteMeta`\",\n details: \"Bentuk `meta` mengikuti PRD §11.2 (Addendum B.1).\",\n suggestions: [\n \"Ubah menjadi `export const meta = ({ data }) => ({ title: \\\"...\\\" })`\",\n \"Atau hapus export `meta`\",\n ],\n });\n }\n const result = await (meta as (args: { data: unknown }) => unknown)({ data: pageData });\n if (result === null || typeof result !== \"object\" || Array.isArray(result)) {\n throw new MoonsError({\n code: \"E_INTERNAL\",\n file,\n message: \"`meta()` harus mengembalikan objek RouteMeta\",\n details: \"Nilai yang dikembalikan dipakai untuk <title>/description/og (Addendum B.1).\",\n suggestions: [\"Kembalikan objek, mis. `({ title: \\\"Halaman\\\" })`\"],\n });\n }\n return result as Record<string, unknown>;\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 type { PluginRuntime } from \"./plugins\";\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 /** Lifecycle plugin (Addendum B.4) — onStart/onReady/onShutdown. */\n plugins?: PluginRuntime;\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 // Addendum B.4: onStart sebelum listen, onReady setelah listen.\n const listenAfterStart = (): void => {\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 void options.plugins?.ready();\n });\n };\n if (options.plugins !== undefined) {\n void options.plugins.start().then(listenAfterStart);\n } else {\n listenAfterStart();\n }\n\n if (options.handleSignals !== false) {\n const shutdown = installGracefulShutdown(server, {\n ...(options.shutdownTimeoutMs === undefined ? {} : { timeoutMs: options.shutdownTimeoutMs }),\n ...(options.plugins === undefined\n ? {}\n : { onShutdown: () => options.plugins?.shutdown() }),\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 * Dipanggil setelah drain penuh, sebelum exit (Addendum B.4: onShutdown\n * plugin). Berbagi batas waktu dengan drain; error dilaporkan, tidak\n * menggagalkan shutdown.\n */\n onShutdown?: () => void | Promise<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(async (error?: Error) => {\n if (error !== undefined) {\n clearTimeout(timer);\n log(`[moons] penutupan server gagal: ${error.message}`);\n exit(1);\n return;\n }\n if (options.onShutdown !== undefined) {\n // Tetap berbagi batas waktu dengan drain: timer di-ref kembali agar\n // proses tidak keluar sebelum hook selesai.\n (timer as { ref?: () => void }).ref?.();\n try {\n await options.onShutdown();\n } catch (hookError) {\n log(`[moons] onShutdown gagal: ${String(hookError)}`);\n }\n }\n clearTimeout(timer);\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 ??\n ((markup, payload, _url, meta) => injectShell(DEFAULT_TEMPLATE, markup, payload, meta));\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
|
+
"import { MOONS_VERSION, type App, type Middleware, type MoonsConfig, type RouteDefinition } from \"@moonsjs/core\";\n\n/**\n * Eksekusi lifecycle plugin (PRD §14.4 / Addendum B.4).\n * Urutan: registrasi (addRoute per route → addMiddleware per middleware →\n * extendContext(register)) → onStart → ready/onReady → shutdown/onShutdown.\n */\nexport interface PluginRuntime {\n readonly app: App;\n /** Registrasi + `onStart` (sebelum server siap). */\n start(): Promise<void>;\n /** Tandai `app.ready = true` lalu panggil `onReady` (server siap). */\n ready(): Promise<void>;\n /** Panggil `onShutdown` (selama drain; error dilaporkan, tidak menggagalkan). */\n shutdown(): Promise<void>;\n /** Terapkan extender `extendContext` ke ctx request (dipanggil handler). */\n applyExtensions(ctx: unknown): void;\n}\n\nexport interface PluginRuntimeOptions {\n config: MoonsConfig;\n routes: RouteDefinition[];\n /** Rantai middleware final (config.middleware). */\n middlewares: Middleware[];\n log?: (message: string) => void;\n}\n\nexport function createPluginRuntime(options: PluginRuntimeOptions): PluginRuntime {\n const log = options.log ?? ((message: string) => console.warn(message));\n const plugins = options.config.plugins ?? [];\n const state = { ready: false, version: MOONS_VERSION };\n const app = state as App;\n const extensions: Array<(ctx: unknown) => unknown> = [];\n const register = (fn: (ctx: unknown) => unknown): void => {\n extensions.push(fn);\n };\n\n // Fase registrasi (sinkron): addRoute → addMiddleware → extendContext.\n for (const plugin of plugins) {\n for (const route of options.routes) {\n // Objek beku: hook bersifat observasi di v1 (lihat docs/decisions/0022).\n const frozen = Object.freeze({\n ...route,\n params: [...route.params],\n }) as RouteDefinition;\n plugin.addRoute?.(frozen);\n }\n for (const middleware of options.middlewares) {\n plugin.addMiddleware?.(middleware);\n }\n plugin.extendContext?.(register as never);\n }\n\n return {\n app,\n async start(): Promise<void> {\n for (const plugin of plugins) {\n await plugin.onStart?.(app);\n }\n },\n async ready(): Promise<void> {\n state.ready = true;\n for (const plugin of plugins) {\n await plugin.onReady?.(app);\n }\n },\n async shutdown(): Promise<void> {\n for (const plugin of plugins) {\n try {\n await plugin.onShutdown?.(app);\n } catch (error) {\n log(`[moons] plugin \\`${plugin.name}\\` gagal saat onShutdown: ${String(error)}`);\n }\n }\n },\n applyExtensions(ctx: unknown): void {\n for (const extend of extensions) {\n const result = extend(ctx);\n if (result !== null && typeof result === \"object\" && !Array.isArray(result)) {\n Object.assign(ctx as Record<string, unknown>, result);\n }\n }\n },\n };\n}\n"
|
|
9
10
|
],
|
|
10
|
-
"mappings": "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",
|
|
11
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "871C5ACF2A35737064756E2164756E21",
|
|
12
13
|
"names": []
|
|
13
14
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Server } from "node:http";
|
|
2
|
+
import type { PluginRuntime } from "./plugins";
|
|
2
3
|
export interface NodeServerOptions {
|
|
3
4
|
port?: number;
|
|
4
5
|
clientDir?: string;
|
|
@@ -6,6 +7,8 @@ export interface NodeServerOptions {
|
|
|
6
7
|
handleSignals?: boolean;
|
|
7
8
|
/** Batas drain shutdown (default 10_000 ms). */
|
|
8
9
|
shutdownTimeoutMs?: number;
|
|
10
|
+
/** Lifecycle plugin (Addendum B.4) — onStart/onReady/onShutdown. */
|
|
11
|
+
plugins?: PluginRuntime;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
14
|
* Serve produksi (M1 §19.2, §18): static dari `dist/client` + handler SSR.
|
|
@@ -1 +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;
|
|
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;AAGtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAyB/C,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;IAC3B,oEAAoE;IACpE,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAChD,OAAO,GAAE,iBAAsB,GAC9B,MAAM,CA2DR;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmBpF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type App, type Middleware, type MoonsConfig, type RouteDefinition } from "@moonsjs/core";
|
|
2
|
+
/**
|
|
3
|
+
* Eksekusi lifecycle plugin (PRD §14.4 / Addendum B.4).
|
|
4
|
+
* Urutan: registrasi (addRoute per route → addMiddleware per middleware →
|
|
5
|
+
* extendContext(register)) → onStart → ready/onReady → shutdown/onShutdown.
|
|
6
|
+
*/
|
|
7
|
+
export interface PluginRuntime {
|
|
8
|
+
readonly app: App;
|
|
9
|
+
/** Registrasi + `onStart` (sebelum server siap). */
|
|
10
|
+
start(): Promise<void>;
|
|
11
|
+
/** Tandai `app.ready = true` lalu panggil `onReady` (server siap). */
|
|
12
|
+
ready(): Promise<void>;
|
|
13
|
+
/** Panggil `onShutdown` (selama drain; error dilaporkan, tidak menggagalkan). */
|
|
14
|
+
shutdown(): Promise<void>;
|
|
15
|
+
/** Terapkan extender `extendContext` ke ctx request (dipanggil handler). */
|
|
16
|
+
applyExtensions(ctx: unknown): void;
|
|
17
|
+
}
|
|
18
|
+
export interface PluginRuntimeOptions {
|
|
19
|
+
config: MoonsConfig;
|
|
20
|
+
routes: RouteDefinition[];
|
|
21
|
+
/** Rantai middleware final (config.middleware). */
|
|
22
|
+
middlewares: Middleware[];
|
|
23
|
+
log?: (message: string) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function createPluginRuntime(options: PluginRuntimeOptions): PluginRuntime;
|
|
26
|
+
//# sourceMappingURL=plugins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/server/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,GAAG,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEjH;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,oDAAoD;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,sEAAsE;IACtE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,iFAAiF;IACjF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,4EAA4E;IAC5E,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,mDAAmD;IACnD,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAyDhF"}
|
package/dist/server/shell.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ViteDevServer } from "vite";
|
|
2
|
-
export type Shell = (markup: string, payload: string, url: string) => string | Promise<string>;
|
|
2
|
+
export type Shell = (markup: string, payload: string, url: string, meta?: Record<string, unknown>) => string | Promise<string>;
|
|
3
3
|
export declare const ROOT_PLACEHOLDER = "<div id=\"root\"></div>";
|
|
4
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;
|
|
5
|
+
/** Inject markup SSR + payload devalue ke shell HTML (PRD §8.2, §17, Addendum B.1). */
|
|
6
|
+
export declare function injectShell(template: string, markup: string, payload: string, meta?: Record<string, unknown>): string;
|
|
7
7
|
/**
|
|
8
8
|
* Shell untuk dev: baca `index.html` dari disk setiap request (selalu segar)
|
|
9
9
|
* lalu lewatkan ke Vite `transformIndexHtml` (inject `/@vite/client`, dsb).
|
|
@@ -1 +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,
|
|
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,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAE9B,eAAO,MAAM,gBAAgB,4BAA0B,CAAC;AAExD,eAAO,MAAM,gBAAgB,QAcjB,CAAC;AAEb,uFAAuF;AACvF,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAeR;AA6CD;;;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"}
|
|
@@ -5,6 +5,12 @@ export interface ShutdownOptions {
|
|
|
5
5
|
log?: (message: string) => void;
|
|
6
6
|
/** Injectable untuk test; default `process.exit`. */
|
|
7
7
|
exit?: (code: number) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Dipanggil setelah drain penuh, sebelum exit (Addendum B.4: onShutdown
|
|
10
|
+
* plugin). Berbagi batas waktu dengan drain; error dilaporkan, tidak
|
|
11
|
+
* menggagalkan shutdown.
|
|
12
|
+
*/
|
|
13
|
+
onShutdown?: () => void | Promise<void>;
|
|
8
14
|
}
|
|
9
15
|
/**
|
|
10
16
|
* Graceful shutdown (PRD §19.5): SIGTERM/SIGINT → stop menerima koneksi baru →
|
|
@@ -1 +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;
|
|
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;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,eAAoB,GAC5B,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,CAsDlC"}
|
|
@@ -1 +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,
|
|
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,CAkBZ"}
|
package/package.json
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -414,13 +414,15 @@ function renderConfigModule(root: string, configFilePath: string): string {
|
|
|
414
414
|
function renderServerEntryDev(routesDir: string, eagerImports: string[]): string {
|
|
415
415
|
return [
|
|
416
416
|
"// Generated by @moons. Jangan diubah manual.",
|
|
417
|
-
'import { createRequestHandler } from "@moonsjs/vite/server";',
|
|
417
|
+
'import { createPluginRuntime, createRequestHandler } from "@moonsjs/vite/server";',
|
|
418
418
|
'import config from "virtual:moons/config";',
|
|
419
419
|
'import { routes, loadRoute } from "virtual:moons/routes";',
|
|
420
420
|
...eagerImports.map((specifier) => `import ${JSON.stringify(specifier)};`),
|
|
421
421
|
"",
|
|
422
|
-
"export function
|
|
423
|
-
|
|
422
|
+
"export function createDevApp(shell) {",
|
|
423
|
+
" const plugins = createPluginRuntime({ config, routes, middlewares: config.middleware ?? [] });",
|
|
424
|
+
` const handler = createRequestHandler({ routes, config, routesDir: ${JSON.stringify(routesDir)}, loadRoute, shell, onContext: (ctx) => plugins.applyExtensions(ctx) });`,
|
|
425
|
+
" return { handler, plugins };",
|
|
424
426
|
"}",
|
|
425
427
|
"",
|
|
426
428
|
].join("\n");
|
|
@@ -429,20 +431,22 @@ function renderServerEntryDev(routesDir: string, eagerImports: string[]): string
|
|
|
429
431
|
function renderServerEntryBuild(routesDir: string, eagerImports: string[]): string {
|
|
430
432
|
return [
|
|
431
433
|
"// Generated by @moons. Jangan diubah manual.",
|
|
432
|
-
'import { createFileShell, createRequestHandler, startNodeServer } from "@moonsjs/vite/server";',
|
|
434
|
+
'import { createFileShell, createPluginRuntime, createRequestHandler, startNodeServer } from "@moonsjs/vite/server";',
|
|
433
435
|
'import config from "virtual:moons/config";',
|
|
434
436
|
'import { routes, loadRoute } from "virtual:moons/routes";',
|
|
435
437
|
...eagerImports.map((specifier) => `import ${JSON.stringify(specifier)};`),
|
|
436
438
|
"",
|
|
439
|
+
"const plugins = createPluginRuntime({ config, routes, middlewares: config.middleware ?? [] });",
|
|
437
440
|
"const handler = createRequestHandler({",
|
|
438
441
|
" routes,",
|
|
439
442
|
" config,",
|
|
440
443
|
` routesDir: ${JSON.stringify(routesDir)},`,
|
|
441
444
|
" loadRoute,",
|
|
442
445
|
" shell: createFileShell(),",
|
|
446
|
+
" onContext: (ctx) => plugins.applyExtensions(ctx),",
|
|
443
447
|
"});",
|
|
444
448
|
"",
|
|
445
|
-
"startNodeServer(handler);",
|
|
449
|
+
"startNodeServer(handler, { plugins });",
|
|
446
450
|
"",
|
|
447
451
|
].join("\n");
|
|
448
452
|
}
|
package/src/server/dev.ts
CHANGED
|
@@ -1,40 +1,67 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import type { ViteDevServer } from "vite";
|
|
3
3
|
import { VIRTUAL_MODULES } from "../virtual-modules";
|
|
4
|
+
import type { PluginRuntime } from "./plugins";
|
|
4
5
|
import type { Shell } from "./shell";
|
|
5
6
|
import { createDevShell } from "./shell";
|
|
6
7
|
import { sendNodeResponse, toWebRequest } from "./node";
|
|
7
8
|
|
|
9
|
+
interface DevApp {
|
|
10
|
+
handler: (request: Request) => Promise<Response>;
|
|
11
|
+
plugins: PluginRuntime;
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
type DevServerEntry = {
|
|
9
|
-
|
|
15
|
+
createDevApp: (shell: Shell) => DevApp;
|
|
10
16
|
};
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* Middleware SSR dev: dipasang sebelum middleware internal Vite, menangani
|
|
14
20
|
* request HTML dan RPC serverFn (`/_moons/fn/*`) — modul/aset tetap Vite.
|
|
21
|
+
* Handler + lifecycle plugin (Addendum B.4) diinisialisasi sekali.
|
|
15
22
|
*/
|
|
16
23
|
export function installDevMiddleware(
|
|
17
24
|
server: ViteDevServer,
|
|
18
25
|
options: { templatePath: string },
|
|
19
26
|
): void {
|
|
27
|
+
let appPromise: Promise<DevApp> | null = null;
|
|
28
|
+
|
|
29
|
+
const getApp = (): Promise<DevApp> => {
|
|
30
|
+
if (appPromise === null) {
|
|
31
|
+
appPromise = (async () => {
|
|
32
|
+
const entry = (await server.ssrLoadModule(VIRTUAL_MODULES.serverEntry)) as DevServerEntry;
|
|
33
|
+
const app = entry.createDevApp(createDevShell(server, options));
|
|
34
|
+
await app.plugins.start();
|
|
35
|
+
await app.plugins.ready();
|
|
36
|
+
return app;
|
|
37
|
+
})();
|
|
38
|
+
}
|
|
39
|
+
return appPromise;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
server.httpServer?.once("close", () => {
|
|
43
|
+
void appPromise?.then((app) => app.plugins.shutdown());
|
|
44
|
+
});
|
|
45
|
+
|
|
20
46
|
server.middlewares.use((req, res, next) => {
|
|
21
47
|
if (!shouldHandle(req)) {
|
|
22
48
|
next();
|
|
23
49
|
return;
|
|
24
50
|
}
|
|
25
|
-
void handleRequest(
|
|
51
|
+
void handleRequest(getApp, req, res).catch((error: unknown) => {
|
|
26
52
|
next(error as Error);
|
|
27
53
|
});
|
|
28
54
|
});
|
|
29
55
|
}
|
|
30
56
|
|
|
31
|
-
function handleRequest(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
async function handleRequest(
|
|
58
|
+
getApp: () => Promise<DevApp>,
|
|
59
|
+
req: IncomingMessage,
|
|
60
|
+
res: ServerResponse,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
const app = await getApp();
|
|
63
|
+
const response = await app.handler(await toWebRequest(req));
|
|
64
|
+
await sendNodeResponse(res, response);
|
|
38
65
|
}
|
|
39
66
|
|
|
40
67
|
function shouldHandle(req: IncomingMessage): boolean {
|