@moonsjs/vite 0.1.4 → 0.1.6

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.
Files changed (52) hide show
  1. package/dist/api.d.ts +8 -0
  2. package/dist/api.d.ts.map +1 -0
  3. package/dist/{index-33p1r65s.js → index-1fnt137k.js} +50 -5
  4. package/dist/index-1fnt137k.js.map +14 -0
  5. package/dist/{index-3ec9vpek.js → index-zs92aaz4.js} +232 -24
  6. package/dist/index-zs92aaz4.js.map +11 -0
  7. package/dist/index.js +216 -19
  8. package/dist/index.js.map +5 -4
  9. package/dist/manifest.d.ts +6 -1
  10. package/dist/manifest.d.ts.map +1 -1
  11. package/dist/payload-scan.d.ts +18 -0
  12. package/dist/payload-scan.d.ts.map +1 -0
  13. package/dist/plugin.d.ts.map +1 -1
  14. package/dist/server/dev.d.ts +3 -0
  15. package/dist/server/dev.d.ts.map +1 -1
  16. package/dist/server/handler.d.ts +16 -1
  17. package/dist/server/handler.d.ts.map +1 -1
  18. package/dist/server/index.d.ts +4 -0
  19. package/dist/server/index.d.ts.map +1 -1
  20. package/dist/server/index.js +561 -17
  21. package/dist/server/index.js.map +9 -6
  22. package/dist/server/isr.d.ts +25 -0
  23. package/dist/server/isr.d.ts.map +1 -0
  24. package/dist/server/node-server.d.ts +7 -1
  25. package/dist/server/node-server.d.ts.map +1 -1
  26. package/dist/server/node.d.ts.map +1 -1
  27. package/dist/server/prerender.d.ts +28 -0
  28. package/dist/server/prerender.d.ts.map +1 -0
  29. package/dist/server-only.d.ts +57 -0
  30. package/dist/server-only.d.ts.map +1 -0
  31. package/dist/transform.d.ts +9 -1
  32. package/dist/transform.d.ts.map +1 -1
  33. package/dist/transform.js +1 -1
  34. package/dist/virtual-modules.d.ts +1 -0
  35. package/dist/virtual-modules.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/api.ts +124 -0
  38. package/src/manifest.ts +42 -1
  39. package/src/payload-scan.ts +53 -0
  40. package/src/plugin.ts +137 -10
  41. package/src/server/dev.ts +14 -5
  42. package/src/server/handler.ts +317 -13
  43. package/src/server/index.ts +4 -0
  44. package/src/server/isr.ts +81 -0
  45. package/src/server/node-server.ts +120 -7
  46. package/src/server/node.ts +17 -0
  47. package/src/server/prerender.ts +256 -0
  48. package/src/server-only.ts +220 -0
  49. package/src/transform.ts +138 -8
  50. package/src/virtual-modules.ts +1 -0
  51. package/dist/index-33p1r65s.js.map +0 -14
  52. package/dist/index-3ec9vpek.js.map +0 -10
package/dist/api.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Validasi statis export modul `src/api` (PRD Addendum D.1):
3
+ * hanya handler method `GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS` sebagai fungsi;
4
+ * `default`/export lain ditolak → `E_API_HANDLER_INVALID`.
5
+ */
6
+ export declare function assertApiModuleExports(code: string, file: string): string[];
7
+ export declare function assertMiddlewareExports(code: string, file: string): void;
8
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA6E3E;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA0BxE"}
@@ -137,8 +137,23 @@ function readBody(req) {
137
137
  async function sendNodeResponse2(res, response) {
138
138
  res.statusCode = response.status;
139
139
  response.headers.forEach((value, key) => {
140
+ if (key === "set-cookie") {
141
+ return;
142
+ }
140
143
  res.setHeader(key, value);
141
144
  });
145
+ const getSetCookie = response.headers.getSetCookie;
146
+ if (typeof getSetCookie === "function") {
147
+ const cookies = getSetCookie.call(response.headers);
148
+ if (cookies.length > 0) {
149
+ res.setHeader("set-cookie", cookies);
150
+ }
151
+ } else {
152
+ const single = response.headers.get("set-cookie");
153
+ if (single !== null) {
154
+ res.setHeader("set-cookie", single);
155
+ }
156
+ }
142
157
  const body = Buffer.from(await response.arrayBuffer());
143
158
  res.end(body);
144
159
  }
@@ -146,6 +161,7 @@ async function sendNodeResponse2(res, response) {
146
161
  // src/virtual-modules.ts
147
162
  var VIRTUAL_MODULES = {
148
163
  routes: "virtual:moons/routes",
164
+ api: "virtual:moons/api",
149
165
  config: "virtual:moons/config",
150
166
  clientEntry: "virtual:moons/client-entry",
151
167
  serverEntry: "virtual:moons/server-entry"
@@ -166,7 +182,7 @@ function installDevMiddleware2(server, options) {
166
182
  if (appPromise === null) {
167
183
  appPromise = (async () => {
168
184
  const entry = await server.ssrLoadModule(VIRTUAL_MODULES.serverEntry);
169
- const app = entry.createDevApp(createDevShell2(server, options));
185
+ const app = entry.createDevApp(createDevShell2(server, options), options.payloadScan);
170
186
  await app.plugins.start();
171
187
  await app.plugins.ready();
172
188
  return app;
@@ -178,7 +194,7 @@ function installDevMiddleware2(server, options) {
178
194
  appPromise?.then((app) => app.plugins.shutdown());
179
195
  });
180
196
  server.middlewares.use((req, res, next) => {
181
- if (!shouldHandle(req)) {
197
+ if (!shouldHandle(req, options.apiPrefix)) {
182
198
  next();
183
199
  return;
184
200
  }
@@ -192,11 +208,14 @@ async function handleRequest(getApp, req, res) {
192
208
  const response = await app.handler(await toWebRequest2(req));
193
209
  await sendNodeResponse2(res, response);
194
210
  }
195
- function shouldHandle(req) {
211
+ function shouldHandle(req, apiPrefix) {
196
212
  const path = new URL(req.url ?? "/", "http://localhost").pathname;
197
213
  if (path.startsWith("/_moons/fn/")) {
198
214
  return true;
199
215
  }
216
+ if (apiPrefix !== undefined && (path === apiPrefix || path.startsWith(`${apiPrefix}/`))) {
217
+ return true;
218
+ }
200
219
  if (req.method !== "GET" && req.method !== "HEAD") {
201
220
  return false;
202
221
  }
@@ -236,8 +255,34 @@ function buildClientManifest(routes, routesDir) {
236
255
  if (errorRoute !== undefined) {
237
256
  manifest[ERROR_ROUTE_KEY] = { page: routeKeyFromFile(routesDir, errorRoute.file), layouts: [] };
238
257
  }
258
+ for (const route of routes) {
259
+ if (route.kind !== "error") {
260
+ continue;
261
+ }
262
+ const key = routeKeyFromFile(routesDir, route.file);
263
+ manifest[key] = { page: key, layouts: [] };
264
+ }
239
265
  return manifest;
240
266
  }
267
+ function renderApiModule(apiRoutes, apiMiddlewares, projectRoot) {
268
+ const loadEntries = apiRoutes.map((route) => ` ${JSON.stringify(route.path)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, route.file))}),`).join(`
269
+ `);
270
+ const middlewareEntries = apiMiddlewares.map((middleware) => ` ${JSON.stringify(middleware.file)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, middleware.file))}),`).join(`
271
+ `);
272
+ return [
273
+ "// Generated by @moons. Jangan diubah manual.",
274
+ `export const apiRoutes = ${JSON.stringify(apiRoutes, null, 2)};`,
275
+ `export const apiMiddlewares = ${JSON.stringify(apiMiddlewares, null, 2)};`,
276
+ "export const loadApi = {",
277
+ loadEntries,
278
+ "};",
279
+ "export const loadApiMiddleware = {",
280
+ middlewareEntries,
281
+ "};",
282
+ ""
283
+ ].join(`
284
+ `);
285
+ }
241
286
  function renderRoutesModule(routes, routesDir, projectRoot) {
242
287
  const manifest = buildClientManifest(routes, routesDir);
243
288
  const loadEntries = routes.map((route) => {
@@ -263,6 +308,6 @@ function rootSpecifier(projectRoot, file) {
263
308
  return rel.startsWith("/") ? rel : `/${rel}`;
264
309
  }
265
310
 
266
- export { NOT_FOUND_ROUTE_KEY, ERROR_ROUTE_KEY, renderRoutesModule, rootSpecifier, VIRTUAL_MODULES, fromNullPrefixed, ROOT_PLACEHOLDER2, DEFAULT_TEMPLATE2, injectShell2, createDevShell2, createFileShell2, toWebRequest2, sendNodeResponse2, installDevMiddleware2 };
311
+ export { NOT_FOUND_ROUTE_KEY, renderApiModule, renderRoutesModule, rootSpecifier, VIRTUAL_MODULES, fromNullPrefixed, ROOT_PLACEHOLDER2, DEFAULT_TEMPLATE2, injectShell2, createDevShell2, createFileShell2, toWebRequest2, sendNodeResponse2, installDevMiddleware2 };
267
312
 
268
- //# debugId=F771C1CCD222FC0D64756E2164756E21
313
+ //# debugId=04E46A508CF0D84F64756E2164756E21
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/server/shell.ts", "../src/server/node.ts", "../src/virtual-modules.ts", "../src/server/dev.ts", "../src/manifest.ts"],
4
+ "sourcesContent": [
5
+ "import { MoonsError } from \"@moonsjs/core\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { ViteDevServer } from \"vite\";\n\nexport type Shell = (\n markup: string,\n payload: string,\n url: string,\n meta?: Record<string, unknown>,\n) => string | Promise<string>;\n\nexport const ROOT_PLACEHOLDER = '<div id=\"root\"></div>';\n\nexport const DEFAULT_TEMPLATE = [\n \"<!doctype html>\",\n '<html lang=\"id\">',\n \"<head>\",\n ' <meta charset=\"utf-8\" />',\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />',\n \" <title>Moons</title>\",\n \"</head>\",\n \"<body>\",\n ` ${ROOT_PLACEHOLDER}`,\n ' <script type=\"module\" src=\"/src/client/main.tsx\"></script>',\n \"</body>\",\n \"</html>\",\n \"\",\n].join(\"\\n\");\n\n/** Inject markup SSR + payload devalue ke shell HTML (PRD §8.2, §17, Addendum B.1). */\nexport function injectShell(\n template: string,\n markup: string,\n payload: string,\n meta?: Record<string, unknown>,\n): string {\n if (!template.includes(ROOT_PLACEHOLDER)) {\n throw new MoonsError({\n code: \"E_INTERNAL\",\n message: `shell HTML tidak memuat \\`${ROOT_PLACEHOLDER}\\``,\n details: \"index.html aplikasi wajib memuat elemen root tersebut agar SSR bisa meng-inject markup.\",\n suggestions: [\n `Tambahkan ${ROOT_PLACEHOLDER} ke index.html`,\n \"Atau hapus index.html untuk memakai shell bawaan\",\n ],\n });\n }\n const dataScript = `<script id=\"__moons_data\" type=\"application/x-moons\">${payload}</script>`;\n const withRoot = template.replace(ROOT_PLACEHOLDER, `<div id=\"root\">${markup}</div>${dataScript}`);\n return applyMeta(withRoot, meta);\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\");\n}\n\n/**\n * Terapkan meta route ke `<head>` (Addendum B.1): `<title>` + description/og.\n * Tag yang sudah ada (mis. di index.html) ditimpa; yang belum ada disisipkan\n * sebelum `</head>`.\n */\nfunction applyMeta(html: string, meta: Record<string, unknown> | undefined): string {\n if (meta === undefined) {\n return html;\n }\n let out = html;\n if (typeof meta.title === \"string\") {\n const title = `<title>${escapeHtml(meta.title)}</title>`;\n out = /<title>[\\s\\S]*?<\\/title>/.test(out)\n ? out.replace(/<title>[\\s\\S]*?<\\/title>/, title)\n : out.replace(\"</head>\", ` ${title}\\n</head>`);\n }\n\n const tags: Array<{ attr: \"name\" | \"property\"; key: string; value: unknown }> = [\n { attr: \"name\", key: \"description\", value: meta.description },\n { attr: \"property\", key: \"og:title\", value: meta.title },\n { attr: \"property\", key: \"og:description\", value: meta.description },\n ];\n for (const tag of tags) {\n if (typeof tag.value !== \"string\") {\n continue;\n }\n const rendered = `<meta ${tag.attr}=\"${tag.key}\" content=\"${escapeHtml(tag.value)}\" />`;\n const pattern = new RegExp(`<meta[^>]*${tag.attr}=\"${tag.key}\"[^>]*>`);\n out = pattern.test(out)\n ? out.replace(pattern, rendered)\n : out.replace(\"</head>\", ` ${rendered}\\n</head>`);\n }\n return out;\n}\n\n/**\n * Shell untuk dev: baca `index.html` dari disk setiap request (selalu segar)\n * lalu lewatkan ke Vite `transformIndexHtml` (inject `/@vite/client`, dsb).\n */\nexport function createDevShell(server: ViteDevServer, options: { templatePath: string }): Shell {\n return async (markup, payload, url, meta) => {\n const template = existsSync(options.templatePath)\n ? readFileSync(options.templatePath, \"utf8\")\n : DEFAULT_TEMPLATE;\n const html = injectShell(template, markup, payload, meta);\n return await server.transformIndexHtml(url, html);\n };\n}\n\n/** Shell untuk produksi: baca `dist/client/index.html` sekali dari cwd. */\nexport function createFileShell(options: { clientDir?: string } = {}): Shell {\n let cached: string | null = null;\n return async (markup, payload, _url, meta) => {\n if (cached === null) {\n const clientDir = resolve(options.clientDir ?? process.cwd(), \"dist/client\");\n const file = join(clientDir, \"index.html\");\n if (!existsSync(file)) {\n throw new MoonsError({\n code: \"E_INTERNAL\",\n file,\n message: \"`dist/client/index.html` tidak ditemukan\",\n details: \"Build client harus dijalankan sebelum server produksi dinyalakan (PRD §18).\",\n suggestions: [\"Jalankan `bun run build` lebih dulu\"],\n });\n }\n cached = readFileSync(file, \"utf8\");\n }\n return injectShell(cached, markup, payload, meta);\n };\n}\n",
6
+ "import type { IncomingMessage, ServerResponse } from \"node:http\";\n\nconst MAX_BODY_BYTES = 32 * 1024 * 1024; // batas defensif transport; limit RPC §7.3 tetap berlaku\n\n/** Konversi request Node → Web `Request` (body diteruskan untuk non-GET/HEAD, §7.3). */\nexport async function toWebRequest(req: IncomingMessage): Promise<Request> {\n const host = req.headers.host ?? \"localhost\";\n const url = `http://${host}${req.url ?? \"/\"}`;\n const method = req.method ?? \"GET\";\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined || key === \"content-length\") {\n // content-length tidak disalin; fetch menghitung ulang dari body.\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(key, item);\n }\n } else {\n headers.set(key, value);\n }\n }\n\n if (method === \"GET\" || method === \"HEAD\") {\n return new Request(url, { method, headers });\n }\n\n const body = await readBody(req);\n return new Request(url, { method, headers, body: new Uint8Array(body) });\n}\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolvePromise, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n\n req.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n req.destroy();\n reject(new Error(\"request body melebihi batas transport\"));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolvePromise(Buffer.concat(chunks)));\n req.on(\"error\", (error) => reject(error));\n });\n}\n\n/** Tulis Web `Response` ke `ServerResponse` Node. */\nexport async function sendNodeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n // `set-cookie` ditangani terpisah: forEach menggabungkan nilai ganda\n // menjadi satu string (cookie kedua hilang — D.2).\n if (key === \"set-cookie\") {\n return;\n }\n res.setHeader(key, value);\n });\n const getSetCookie = (response.headers as { getSetCookie?: () => string[] }).getSetCookie;\n if (typeof getSetCookie === \"function\") {\n const cookies = getSetCookie.call(response.headers);\n if (cookies.length > 0) {\n res.setHeader(\"set-cookie\", cookies);\n }\n } else {\n const single = response.headers.get(\"set-cookie\");\n if (single !== null) {\n res.setHeader(\"set-cookie\", single);\n }\n }\n const body = Buffer.from(await response.arrayBuffer());\n res.end(body);\n}\n",
7
+ "/** Nama virtual module PRD §9.4. Semua di-resolve ke id `\\0`-prefix. */\nexport const VIRTUAL_MODULES = {\n routes: \"virtual:moons/routes\",\n api: \"virtual:moons/api\",\n config: \"virtual:moons/config\",\n clientEntry: \"virtual:moons/client-entry\",\n serverEntry: \"virtual:moons/server-entry\",\n} as const;\n\nexport type VirtualModuleName = (typeof VIRTUAL_MODULES)[keyof typeof VIRTUAL_MODULES];\n\nexport function isVirtualModuleId(id: string): id is VirtualModuleName {\n const clean = id.startsWith(\"\\0\") ? id.slice(1) : id;\n return (Object.values(VIRTUAL_MODULES) as string[]).includes(clean);\n}\n\nexport function toNullPrefixed(id: VirtualModuleName): `\\0${string}` {\n return `\\0${id}`;\n}\n\nexport function fromNullPrefixed(id: string): VirtualModuleName | null {\n const clean = id.startsWith(\"\\0\") ? id.slice(1) : id;\n return isVirtualModuleId(clean) ? (clean as VirtualModuleName) : null;\n}\n",
8
+ "import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ViteDevServer } from \"vite\";\nimport { VIRTUAL_MODULES } from \"../virtual-modules\";\nimport type { PayloadScanOptions } from \"../payload-scan\";\nimport type { PluginRuntime } from \"./plugins\";\nimport type { Shell } from \"./shell\";\nimport { createDevShell } from \"./shell\";\nimport { sendNodeResponse, toWebRequest } from \"./node\";\n\ninterface DevApp {\n handler: (request: Request) => Promise<Response>;\n plugins: PluginRuntime;\n}\n\ntype DevServerEntry = {\n createDevApp: (\n shell: Shell,\n payloadScan?: import(\"../payload-scan\").PayloadScanOptions,\n ) => DevApp;\n};\n\n/**\n * Middleware SSR dev: dipasang sebelum middleware internal Vite, menangani\n * request HTML dan RPC serverFn (`/_moons/fn/*`) — modul/aset tetap Vite.\n * Handler + lifecycle plugin (Addendum B.4) diinisialisasi sekali.\n */\nexport function installDevMiddleware(\n server: ViteDevServer,\n options: { templatePath: string; payloadScan?: PayloadScanOptions; apiPrefix?: string },\n): void {\n let appPromise: Promise<DevApp> | null = null;\n\n const getApp = (): Promise<DevApp> => {\n if (appPromise === null) {\n appPromise = (async () => {\n const entry = (await server.ssrLoadModule(VIRTUAL_MODULES.serverEntry)) as DevServerEntry;\n const app = entry.createDevApp(createDevShell(server, options), options.payloadScan);\n await app.plugins.start();\n await app.plugins.ready();\n return app;\n })();\n }\n return appPromise;\n };\n\n server.httpServer?.once(\"close\", () => {\n void appPromise?.then((app) => app.plugins.shutdown());\n });\n\n server.middlewares.use((req, res, next) => {\n if (!shouldHandle(req, options.apiPrefix)) {\n next();\n return;\n }\n void handleRequest(getApp, req, res).catch((error: unknown) => {\n next(error as Error);\n });\n });\n}\n\nasync function handleRequest(\n getApp: () => Promise<DevApp>,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const app = await getApp();\n const response = await app.handler(await toWebRequest(req));\n await sendNodeResponse(res, response);\n}\n\nfunction shouldHandle(req: IncomingMessage, apiPrefix: string | undefined): boolean {\n const path = new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n\n // RPC serverFn (semua method): dulu hanya GET/HEAD HTML yang dirutekan,\n // sehingga `POST /_moons/fn/<hash>` jatuh ke Vite (\"Cannot POST\", 404) dan\n // serverFn tidak bisa dipakai di dev (temuan eval T2 ronde 2; decisions/0016).\n if (path.startsWith(\"/_moons/fn/\")) {\n return true;\n }\n\n // Addendum D.1: endpoint `src/api` (semua method) dirutekan ke runtime.\n if (apiPrefix !== undefined && (path === apiPrefix || path.startsWith(`${apiPrefix}/`))) {\n return true;\n }\n\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n return false;\n }\n if (path.startsWith(\"/@\") || path.startsWith(\"/node_modules/\") || path.startsWith(\"/__\")) {\n return false;\n }\n const lastSegment = path.split(\"/\").pop() ?? \"\";\n if (lastSegment.includes(\".\")) {\n return false;\n }\n const accept = req.headers.accept ?? \"\";\n return accept.includes(\"text/html\") || accept.includes(\"*/*\");\n}\n",
9
+ "import { findLayouts, routeKeyFromFile, type ApiRouteDefinition, type RouteDefinition } from \"@moonsjs/core\";\nimport { relative, sep } from \"node:path\";\n\nexport interface ClientManifestEntry {\n page: string;\n layouts: string[];\n}\n\n/** Key manifest untuk halaman kustom `_404` dan `_error` (§11.2). */\nexport const NOT_FOUND_ROUTE_KEY = \"__moons/404\";\nexport const ERROR_ROUTE_KEY = \"__moons/error\";\n\n/**\n * Manifest route untuk client (PRD §8.2): key = key route file\n * (routeKeyFromFile), nilai = page + layout chain dari root ke leaf.\n */\nexport function buildClientManifest(\n routes: RouteDefinition[],\n routesDir: string,\n): Record<string, ClientManifestEntry> {\n const manifest: Record<string, ClientManifestEntry> = {};\n for (const route of routes) {\n if (route.kind !== \"page\") {\n continue;\n }\n const key = routeKeyFromFile(routesDir, route.file);\n manifest[key] = {\n page: key,\n layouts: findLayouts(route, routes).map((layout) => routeKeyFromFile(routesDir, layout.file)),\n };\n }\n\n const notFound = routes.find((route) => route.kind === \"404\");\n if (notFound !== undefined) {\n manifest[NOT_FOUND_ROUTE_KEY] = { page: routeKeyFromFile(routesDir, notFound.file), layouts: [] };\n }\n const errorRoute = routes.find((route) => route.kind === \"error\");\n if (errorRoute !== undefined) {\n manifest[ERROR_ROUTE_KEY] = { page: routeKeyFromFile(routesDir, errorRoute.file), layouts: [] };\n }\n // D.3: semua boundary `_error` (termasuk bersarang) ikut manifest — payload\n // memakai key file boundary yang dipilih runtime.\n for (const route of routes) {\n if (route.kind !== \"error\") {\n continue;\n }\n const key = routeKeyFromFile(routesDir, route.file);\n manifest[key] = { page: key, layouts: [] };\n }\n\n return manifest;\n}\n\n/** `virtual:moons/api` (Addendum D.1 + D.3): tabel API + middleware + peta import(). */\nexport function renderApiModule(\n apiRoutes: ApiRouteDefinition[],\n apiMiddlewares: ReadonlyArray<{ file: string; dir: string }>,\n projectRoot: string,\n): string {\n const loadEntries = apiRoutes\n .map(\n (route) =>\n ` ${JSON.stringify(route.path)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, route.file))}),`,\n )\n .join(\"\\n\");\n const middlewareEntries = apiMiddlewares\n .map(\n (middleware) =>\n ` ${JSON.stringify(middleware.file)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, middleware.file))}),`,\n )\n .join(\"\\n\");\n return [\n \"// Generated by @moons. Jangan diubah manual.\",\n `export const apiRoutes = ${JSON.stringify(apiRoutes, null, 2)};`,\n `export const apiMiddlewares = ${JSON.stringify(apiMiddlewares, null, 2)};`,\n \"export const loadApi = {\",\n loadEntries,\n \"};\",\n \"export const loadApiMiddleware = {\",\n middlewareEntries,\n \"};\",\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Isi `virtual:moons/routes`: daftar RouteDefinition, manifest client, dan\n * peta import() dinamis per file route (specifier root-relative).\n */\nexport function renderRoutesModule(\n routes: RouteDefinition[],\n routesDir: string,\n projectRoot: string,\n): string {\n const manifest = buildClientManifest(routes, routesDir);\n\n const loadEntries = routes.map((route) => {\n const key = routeKeyFromFile(routesDir, route.file);\n return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, route.file))}),`;\n });\n\n return [\n \"// Generated by @moons. Jangan diubah manual.\",\n `export const routes = ${JSON.stringify(routes, null, 2)};`,\n \"\",\n `export const clientManifest = ${JSON.stringify(manifest, null, 2)};`,\n \"\",\n \"export const loadRoute = {\",\n ...loadEntries,\n \"};\",\n \"\",\n ].join(\"\\n\");\n}\n\n/** Path file absolut project → specifier root-relative yang bisa di-resolve Vite. */\nexport function rootSpecifier(projectRoot: string, file: string): string {\n let rel = relative(projectRoot, file);\n rel = sep === \"/\" ? rel : rel.split(sep).join(\"/\");\n return rel.startsWith(\"/\") ? rel : `/${rel}`;\n}\n"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "04E46A508CF0D84F64756E2164756E21",
13
+ "names": []
14
+ }
@@ -1,8 +1,124 @@
1
1
  // src/transform.ts
2
- import { MoonsError, computeFnHash, fnRegistryKey } from "@moonsjs/core";
2
+ import { MoonsError as MoonsError2, computeFnHash, fnRegistryKey } from "@moonsjs/core";
3
3
  import { parse, parseExpression } from "@babel/parser";
4
- import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs";
5
- import { dirname, isAbsolute, join, resolve } from "node:path";
4
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, realpathSync } from "node:fs";
5
+ import { dirname as dirname2, isAbsolute, join as join2, resolve } from "node:path";
6
+
7
+ // src/server-only.ts
8
+ import { MoonsError } from "@moonsjs/core";
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { dirname, join } from "node:path";
11
+ var DEFAULT_SERVER_ONLY_PACKAGES = [
12
+ "pg",
13
+ "postgres",
14
+ "mysql",
15
+ "mysql2",
16
+ "sqlite3",
17
+ "better-sqlite3",
18
+ "drizzle-orm",
19
+ "@prisma/client",
20
+ "prisma",
21
+ "mongodb",
22
+ "mongoose",
23
+ "redis",
24
+ "ioredis",
25
+ "bcrypt",
26
+ "bcryptjs",
27
+ "argon2",
28
+ "jsonwebtoken",
29
+ "nodemailer",
30
+ "sharp",
31
+ "execa",
32
+ "cross-spawn",
33
+ "dotenv",
34
+ "knex",
35
+ "typeorm",
36
+ "sequelize",
37
+ "@libsql/client"
38
+ ];
39
+ function tryReadPackageJson(dir) {
40
+ const candidate = join(dir, "package.json");
41
+ if (!existsSync(candidate)) {
42
+ return null;
43
+ }
44
+ try {
45
+ const json = JSON.parse(readFileSync(candidate, "utf8"));
46
+ const moons = json.moons;
47
+ return {
48
+ dir,
49
+ name: typeof json.name === "string" ? json.name : "",
50
+ serverOnlyMarker: moons?.serverOnly === true
51
+ };
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ function createServerOnlyClassifier(options = {}) {
57
+ const cache = options.cache ?? new Map;
58
+ const configured = new Set(options.configured ?? []);
59
+ const builtin = new Set(DEFAULT_SERVER_ONLY_PACKAGES);
60
+ const infoOf = (file) => {
61
+ let dir = dirname(file);
62
+ for (let depth = 0;depth < 64; depth += 1) {
63
+ if (cache.has(dir)) {
64
+ return cache.get(dir) ?? null;
65
+ }
66
+ const info = tryReadPackageJson(dir);
67
+ if (info !== null) {
68
+ cache.set(dir, info);
69
+ return info;
70
+ }
71
+ const parent = dirname(dir);
72
+ if (parent === dir) {
73
+ return null;
74
+ }
75
+ dir = parent;
76
+ }
77
+ return null;
78
+ };
79
+ return {
80
+ check({ specifier: _specifier, resolvedFile, importerFile }) {
81
+ if (resolvedFile === null) {
82
+ return null;
83
+ }
84
+ const info = infoOf(resolvedFile);
85
+ if (info === null || info.name.length === 0) {
86
+ return null;
87
+ }
88
+ const importer = infoOf(importerFile);
89
+ if (importer !== null && importer.dir === info.dir) {
90
+ return null;
91
+ }
92
+ if (builtin.has(info.name)) {
93
+ return { packageName: info.name, source: "builtin", packageDir: info.dir };
94
+ }
95
+ if (configured.has(info.name)) {
96
+ return { packageName: info.name, source: "config", packageDir: info.dir };
97
+ }
98
+ if (info.serverOnlyMarker) {
99
+ return { packageName: info.name, source: "marker", packageDir: info.dir };
100
+ }
101
+ return null;
102
+ }
103
+ };
104
+ }
105
+ function serverOnlyImportError(input) {
106
+ const sourceLabel = input.source === "builtin" ? "Sumber kecocokan: daftar bawaan paket server-only framework." : input.source === "config" ? "Sumber kecocokan: `security.serverOnlyPackages` di moons.config.ts." : `Sumber kecocokan: marker \`"moons": { "serverOnly": true }\` di ${input.markerPath ?? "package.json"}.`;
107
+ return new MoonsError({
108
+ code: "E_FORBIDDEN_SERVER_IMPORT",
109
+ file: input.relativeFile,
110
+ line: input.line,
111
+ message: `mengimpor${input.dynamic === true ? " (dynamic)" : ""} \`${input.specifier}\` — paket server-only \`${input.packageName}\``,
112
+ details: `${sourceLabel}
113
+ Paket server-only tidak boleh masuk bundle client (PRD Addendum C).`,
114
+ suggestions: [
115
+ "Pindahkan operasi tersebut ke `*.server.ts` dan panggil lewat serverFn",
116
+ "Atau ganti/hapus import tersebut di modul client"
117
+ ]
118
+ });
119
+ }
120
+
121
+ // src/transform.ts
6
122
  var SERVER_SPECIFIER = /\.server(\.(ts|tsx|js|jsx|mts|cts|mjs|cjs))?$/i;
7
123
  var CONTRACT_SERVER_FILE = /\.server\.(ts|tsx)$/i;
8
124
  var NON_CONTRACT_SERVER_FILE = /\.server\.(js|jsx|mts|cts|mjs|cjs)$/i;
@@ -19,12 +135,12 @@ function resolveServerSpecifierSync2(importerFile, specifier) {
19
135
  if (!isServerSpecifier2(specifier)) {
20
136
  return null;
21
137
  }
22
- const base = specifier.startsWith(".") ? resolve(dirname(importerFile), specifier) : isAbsolute(specifier) ? specifier : null;
138
+ const base = specifier.startsWith(".") ? resolve(dirname2(importerFile), specifier) : isAbsolute(specifier) ? specifier : null;
23
139
  if (base === null) {
24
140
  return null;
25
141
  }
26
142
  for (const candidate of [base, `${base}.ts`, `${base}.tsx`]) {
27
- if (existsSync(candidate)) {
143
+ if (existsSync2(candidate)) {
28
144
  return candidate;
29
145
  }
30
146
  }
@@ -68,6 +184,17 @@ function topLevelStatements(ast) {
68
184
  const program = ast["program"];
69
185
  return program?.body ?? [];
70
186
  }
187
+ function isTypeOnlyImportExport(statement) {
188
+ const declarationKind = statement["importKind"] ?? statement["exportKind"];
189
+ if (declarationKind === "type") {
190
+ return true;
191
+ }
192
+ const specifiers = statement["specifiers"] ?? [];
193
+ if (specifiers.length === 0) {
194
+ return false;
195
+ }
196
+ return specifiers.every((item) => (item["importKind"] ?? item["exportKind"]) === "type");
197
+ }
71
198
  function keyName(node) {
72
199
  const key = node;
73
200
  if (key?.type === "Identifier") {
@@ -359,16 +486,27 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
359
486
  if (reference.length === 0 || !localNames.has(reference)) {
360
487
  continue;
361
488
  }
362
- throw new MoonsError({
489
+ throw new MoonsError2({
363
490
  code: "E_SCHEMA_LOCAL_REF",
364
491
  file: filename,
365
492
  line: fn.line,
366
493
  annotation: fn.name,
367
494
  message: `schema ${kind} serverFn \`${fn.name}\` merujuk deklarasi lokal \`${reference}\``,
368
- details: "Stub client (PRD §12.2/§12.3) hanya membawa import dan ekspresi schema; " + "deklarasi lokal dibuang sehingga referensi menjadi `undefined` di browser (crash hydration).",
495
+ details: `\`${reference}\` dibuang dari stub client (PRD §12.2/§12.3), sehingga nilainya \`undefined\` di browser (crash hydration).
496
+
497
+ ` + `Sebelum (ditolak):
498
+ ` + ` const ${reference} = z.object({ … }); // deklarasi lokal di file yang sama
499
+ ` + ` export const ${fn.name} = serverFn({ ${kind}: ${reference}, … });
500
+
501
+ ` + `Sesudah (pilih salah satu):
502
+ ` + ` // a) inline schema di argumen
503
+ ` + ` export const ${fn.name} = serverFn({ ${kind}: z.object({ … }), … });
504
+ ` + ` // b) pindahkan deklarasi ke modul bersama yang boleh masuk client (mis. src/shared/schemas.ts)
505
+ ` + ` import { ${reference} } from "../shared/schemas";`,
369
506
  suggestions: [
370
- "Inline schema langsung di argumen `serverFn({ input, output })`, atau",
371
- "Impor schema dari modul lain (mis. `src/shared/schemas.ts`)"
507
+ `Inline schema di argumen \`serverFn({ ${kind}: … })\`, atau`,
508
+ `Pindahkan \`${reference}\` ke modul bersama (mis. \`src/shared/schemas.ts\`) lalu impor dari sana`,
509
+ "Jangan memindahkan nilai rahasia ke shared — schema harus client-safe (zod/Standard Schema)"
372
510
  ]
373
511
  });
374
512
  }
@@ -378,7 +516,7 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
378
516
  for (const reExport of analysis.reExports) {
379
517
  const target = dependencies.analyzeSpecifier(filename, reExport.source);
380
518
  if (target === null) {
381
- throw new MoonsError({
519
+ throw new MoonsError2({
382
520
  code: "E_SERVER_IMPORT_IN_CLIENT",
383
521
  file: filename,
384
522
  message: `tidak bisa me-resolve \`${reExport.source}\` untuk re-export`,
@@ -396,7 +534,7 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
396
534
  for (const specifier of analysis.exportAllFrom) {
397
535
  const target = dependencies.analyzeSpecifier(filename, specifier);
398
536
  if (target === null) {
399
- throw new MoonsError({
537
+ throw new MoonsError2({
400
538
  code: "E_SERVER_IMPORT_IN_CLIENT",
401
539
  file: filename,
402
540
  message: `\`export *\` dari \`${specifier}\` tidak bisa di-resolve`,
@@ -464,7 +602,7 @@ async function serverFnExportNamesAsync(analysis, analyzeSpecifier, seen = new S
464
602
  return names;
465
603
  }
466
604
  function nonContractServerFileError2(file, projectRoot) {
467
- return new MoonsError({
605
+ return new MoonsError2({
468
606
  code: "E_SERVER_IMPORT_IN_CLIENT",
469
607
  file: toRelative(projectRoot, file),
470
608
  message: "file server memakai ekstensi di luar kontrak §12",
@@ -479,6 +617,32 @@ async function checkClientModuleImports2(context) {
479
617
  const { file, code, projectRoot, resolve, analyze } = context;
480
618
  const ast = parseModule(code, file);
481
619
  const relativeFile = toRelative(projectRoot, file);
620
+ const serverOnly = createServerOnlyClassifier({
621
+ configured: context.serverOnlyPackages ?? [],
622
+ ...context.packageCache === undefined ? {} : { cache: context.packageCache }
623
+ });
624
+ let apiDirReal = null;
625
+ if (context.apiDir !== undefined && context.apiDir.length > 0) {
626
+ try {
627
+ apiDirReal = realpathSync(context.apiDir);
628
+ } catch {
629
+ apiDirReal = context.apiDir;
630
+ }
631
+ }
632
+ const rejectApiImport = (line, specifier, resolvedTarget) => {
633
+ const relApi = toRelative(projectRoot, resolvedTarget);
634
+ throw new MoonsError2({
635
+ code: "E_FORBIDDEN_SERVER_IMPORT",
636
+ file: relativeFile,
637
+ line,
638
+ message: `mengimpor \`${specifier}\` — \`${relApi}\` adalah modul API server-only`,
639
+ details: "`src/api/**` hanya berjalan di server (endpoint HTTP) dan tidak pernah masuk bundle client (PRD Addendum D.1).",
640
+ suggestions: [
641
+ "Pindahkan operasi tersebut ke `*.server.ts` dan panggil lewat serverFn",
642
+ "Jangan mengimpor file `src/api` dari route/komponen client"
643
+ ]
644
+ });
645
+ };
482
646
  const resolveSafe = async (specifier, importer) => {
483
647
  try {
484
648
  const result = await resolve(specifier, importer);
@@ -508,7 +672,7 @@ async function checkClientModuleImports2(context) {
508
672
  return names.has(name);
509
673
  };
510
674
  const reject = (line, message, details) => {
511
- throw new MoonsError({
675
+ throw new MoonsError2({
512
676
  code: "E_SERVER_IMPORT_IN_CLIENT",
513
677
  file: relativeFile,
514
678
  line,
@@ -521,7 +685,7 @@ async function checkClientModuleImports2(context) {
521
685
  });
522
686
  };
523
687
  const rejectForbidden = (line, specifier) => {
524
- throw new MoonsError({
688
+ throw new MoonsError2({
525
689
  code: "E_FORBIDDEN_SERVER_IMPORT",
526
690
  file: relativeFile,
527
691
  line,
@@ -542,10 +706,31 @@ async function checkClientModuleImports2(context) {
542
706
  const rawSpecifier = sourceNode["value"];
543
707
  const specifier = stripQuery2(rawSpecifier);
544
708
  const line = statement.loc?.start.line ?? 1;
709
+ const typeOnly = isTypeOnlyImportExport(statement);
545
710
  if (specifier.startsWith("node:") || specifier.startsWith("bun:")) {
546
711
  rejectForbidden(line, specifier);
547
712
  }
548
713
  const resolvedTarget = await resolveSafe(specifier, file);
714
+ if (apiDirReal !== null && resolvedTarget !== null && isInsideDir(resolvedTarget, apiDirReal)) {
715
+ rejectApiImport(line, rawSpecifier, resolvedTarget);
716
+ }
717
+ if (!typeOnly) {
718
+ const match = serverOnly.check({
719
+ specifier,
720
+ resolvedFile: resolvedTarget,
721
+ importerFile: file
722
+ });
723
+ if (match !== null) {
724
+ throw serverOnlyImportError({
725
+ relativeFile,
726
+ line,
727
+ specifier: rawSpecifier,
728
+ packageName: match.packageName,
729
+ source: match.source,
730
+ ...match.source === "marker" ? { markerPath: toRelative(projectRoot, join2(match.packageDir, "package.json")) } : {}
731
+ });
732
+ }
733
+ }
549
734
  if (resolvedTarget !== null && isNonContractServerFile2(resolvedTarget)) {
550
735
  throw nonContractServerFileError2(resolvedTarget, projectRoot);
551
736
  }
@@ -611,7 +796,7 @@ async function checkClientModuleImports2(context) {
611
796
  return;
612
797
  }
613
798
  const callText = code.slice(node.start ?? 0, node.end ?? 0).replace(/\s+/g, " ").slice(0, 80);
614
- throw new MoonsError({
799
+ throw new MoonsError2({
615
800
  code: "E_ENV_IN_CLIENT",
616
801
  file: relativeFile,
617
802
  line: node.loc?.start.line ?? 1,
@@ -640,6 +825,25 @@ async function checkClientModuleImports2(context) {
640
825
  }
641
826
  const specifier = stripQuery2(request.text);
642
827
  const resolvedTarget = await resolveSafe(specifier, file);
828
+ if (apiDirReal !== null && resolvedTarget !== null && isInsideDir(resolvedTarget, apiDirReal)) {
829
+ rejectApiImport(request.node.loc?.start.line ?? 1, request.text, resolvedTarget);
830
+ }
831
+ const match = serverOnly.check({
832
+ specifier,
833
+ resolvedFile: resolvedTarget,
834
+ importerFile: file
835
+ });
836
+ if (match !== null) {
837
+ throw serverOnlyImportError({
838
+ relativeFile,
839
+ line: request.node.loc?.start.line ?? 1,
840
+ specifier: request.text,
841
+ packageName: match.packageName,
842
+ source: match.source,
843
+ dynamic: true,
844
+ ...match.source === "marker" ? { markerPath: toRelative(projectRoot, join2(match.packageDir, "package.json")) } : {}
845
+ });
846
+ }
643
847
  if (resolvedTarget !== null && isNonContractServerFile2(resolvedTarget)) {
644
848
  throw nonContractServerFileError2(resolvedTarget, projectRoot);
645
849
  }
@@ -668,7 +872,7 @@ async function checkClientModuleImports2(context) {
668
872
  if (!isServerSpecifier2(pattern)) {
669
873
  continue;
670
874
  }
671
- throw new MoonsError({
875
+ throw new MoonsError2({
672
876
  code: "E_SERVER_IMPORT_IN_CLIENT",
673
877
  file: relativeFile,
674
878
  line: node.loc?.start.line ?? 1,
@@ -754,6 +958,10 @@ function toRelative(projectRoot, file) {
754
958
  const relative = file.startsWith(projectRoot) ? file.slice(projectRoot.length).replace(/^[/\\]/, "") : file;
755
959
  return relative.split("\\").join("/");
756
960
  }
961
+ function isInsideDir(file, dir) {
962
+ const normalized = dir.endsWith("/") ? dir : `${dir}/`;
963
+ return file === dir || file.startsWith(normalized);
964
+ }
757
965
  function checkRouteModuleExports2(context) {
758
966
  const { file, code, routesDir, projectRoot } = context;
759
967
  if (!file.startsWith(routesDir) || !file.endsWith(".tsx")) {
@@ -763,7 +971,7 @@ function checkRouteModuleExports2(context) {
763
971
  if (base.startsWith("_") && base !== "_layout.tsx" && base !== "_error.tsx" && base !== "_404.tsx") {
764
972
  return;
765
973
  }
766
- const allowed = base === "_layout.tsx" ? ["default", "loader"] : base === "_error.tsx" || base === "_404.tsx" ? ["default"] : ["default", "loader", "meta"];
974
+ const allowed = base === "_layout.tsx" ? ["default", "loader"] : base === "_error.tsx" || base === "_404.tsx" ? ["default"] : ["default", "loader", "meta", "prerender", "paths", "revalidate"];
767
975
  const allowedText = `Hanya ${allowed.map((name) => `\`${name}\``).join(", ")} yang boleh di-export.`;
768
976
  const relativeFile = toRelative(projectRoot, file);
769
977
  const ast = parseModule(code, file);
@@ -775,7 +983,7 @@ function checkRouteModuleExports2(context) {
775
983
  continue;
776
984
  }
777
985
  if (statement.type === "ExportAllDeclaration") {
778
- throw new MoonsError({
986
+ throw new MoonsError2({
779
987
  code: "E_ROUTE_EXPORT_INVALID",
780
988
  file: relativeFile,
781
989
  line: statement.loc?.start.line ?? 1,
@@ -806,7 +1014,7 @@ function checkRouteModuleExports2(context) {
806
1014
  }
807
1015
  for (const entry of declarations) {
808
1016
  if (!allowed.includes(entry.name)) {
809
- throw new MoonsError({
1017
+ throw new MoonsError2({
810
1018
  code: "E_ROUTE_EXPORT_INVALID",
811
1019
  file: relativeFile,
812
1020
  line: entry.line,
@@ -820,7 +1028,7 @@ function checkRouteModuleExports2(context) {
820
1028
  }
821
1029
  }
822
1030
  if (!hasDefault) {
823
- throw new MoonsError({
1031
+ throw new MoonsError2({
824
1032
  code: "E_ROUTE_EXPORT_INVALID",
825
1033
  file: relativeFile,
826
1034
  message: "modul route wajib mengekspor `default`",
@@ -852,9 +1060,9 @@ function scanServerFiles2(projectRoot) {
852
1060
  if (excluded.has(entry.name)) {
853
1061
  continue;
854
1062
  }
855
- walkDir(join(dir, entry.name));
1063
+ walkDir(join2(dir, entry.name));
856
1064
  } else if (entry.isFile() && isServerFilePath2(entry.name)) {
857
- result.push(join(dir, entry.name));
1065
+ result.push(join2(dir, entry.name));
858
1066
  }
859
1067
  }
860
1068
  };
@@ -866,7 +1074,7 @@ function buildFnRegistry2(projectRoot, files) {
866
1074
  for (const file of files) {
867
1075
  let source;
868
1076
  try {
869
- source = readFileSync(file, "utf8");
1077
+ source = readFileSync2(file, "utf8");
870
1078
  } catch {
871
1079
  continue;
872
1080
  }
@@ -880,4 +1088,4 @@ function buildFnRegistry2(projectRoot, files) {
880
1088
 
881
1089
  export { isServerFilePath2, isNonContractServerFile2, isServerSpecifier2, resolveServerSpecifierSync2, stripQuery2, analyzeServerFile2, serverFnExportNames2, renderServerFnStub2, injectServerFnMeta2, nonContractServerFileError2, checkClientModuleImports2, checkRouteModuleExports2, scanServerFiles2, buildFnRegistry2 };
882
1090
 
883
- //# debugId=6E8CE67B8B7578C064756E2164756E21
1091
+ //# debugId=DE98D1ED167FCF0E64756E2164756E21