@nuraly/lumenjs 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +82 -0
  11. package/dist/auth/native-auth.js +340 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +121 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/totp.d.ts +22 -0
  31. package/dist/auth/routes/totp.js +232 -0
  32. package/dist/auth/routes/utils.d.ts +7 -0
  33. package/dist/auth/routes/utils.js +35 -0
  34. package/dist/auth/routes/verify.d.ts +3 -0
  35. package/dist/auth/routes/verify.js +26 -0
  36. package/dist/auth/routes.d.ts +8 -0
  37. package/dist/auth/routes.js +124 -0
  38. package/dist/auth/session.d.ts +8 -0
  39. package/dist/auth/session.js +54 -0
  40. package/dist/auth/token.d.ts +33 -0
  41. package/dist/auth/token.js +90 -0
  42. package/dist/auth/types.d.ts +156 -0
  43. package/dist/auth/types.js +2 -0
  44. package/dist/build/build-client.d.ts +15 -0
  45. package/dist/build/build-client.js +45 -0
  46. package/dist/build/build-prerender.d.ts +11 -0
  47. package/dist/build/build-prerender.js +159 -0
  48. package/dist/build/build-server.d.ts +18 -0
  49. package/dist/build/build-server.js +107 -0
  50. package/dist/build/build.js +60 -123
  51. package/dist/build/scan.d.ts +18 -0
  52. package/dist/build/scan.js +77 -6
  53. package/dist/build/serve-api.js +8 -2
  54. package/dist/build/serve-loaders.d.ts +4 -4
  55. package/dist/build/serve-loaders.js +26 -18
  56. package/dist/build/serve-ssr.js +38 -11
  57. package/dist/build/serve-static.js +3 -3
  58. package/dist/build/serve.js +341 -18
  59. package/dist/cli.js +37 -6
  60. package/dist/communication/encryption.d.ts +35 -0
  61. package/dist/communication/encryption.js +90 -0
  62. package/dist/communication/handlers/context.d.ts +27 -0
  63. package/dist/communication/handlers/context.js +1 -0
  64. package/dist/communication/handlers/conversation.d.ts +24 -0
  65. package/dist/communication/handlers/conversation.js +113 -0
  66. package/dist/communication/handlers/file-upload.d.ts +17 -0
  67. package/dist/communication/handlers/file-upload.js +62 -0
  68. package/dist/communication/handlers/messaging.d.ts +30 -0
  69. package/dist/communication/handlers/messaging.js +237 -0
  70. package/dist/communication/handlers/presence.d.ts +15 -0
  71. package/dist/communication/handlers/presence.js +76 -0
  72. package/dist/communication/handlers.d.ts +5 -0
  73. package/dist/communication/handlers.js +5 -0
  74. package/dist/communication/index.d.ts +9 -0
  75. package/dist/communication/index.js +7 -0
  76. package/dist/communication/link-preview.d.ts +18 -0
  77. package/dist/communication/link-preview.js +115 -0
  78. package/dist/communication/schema.d.ts +10 -0
  79. package/dist/communication/schema.js +101 -0
  80. package/dist/communication/server.d.ts +86 -0
  81. package/dist/communication/server.js +212 -0
  82. package/dist/communication/signaling.d.ts +43 -0
  83. package/dist/communication/signaling.js +271 -0
  84. package/dist/communication/store.d.ts +71 -0
  85. package/dist/communication/store.js +289 -0
  86. package/dist/communication/types.d.ts +454 -0
  87. package/dist/communication/types.js +1 -0
  88. package/dist/create.d.ts +1 -0
  89. package/dist/create.js +55 -0
  90. package/dist/db/auto-migrate.d.ts +3 -0
  91. package/dist/db/auto-migrate.js +100 -0
  92. package/dist/db/client.d.ts +3 -0
  93. package/dist/db/client.js +18 -0
  94. package/dist/db/index.d.ts +17 -13
  95. package/dist/db/index.js +205 -26
  96. package/dist/db/seed.d.ts +12 -0
  97. package/dist/db/seed.js +88 -0
  98. package/dist/db/table.d.ts +10 -0
  99. package/dist/db/table.js +12 -0
  100. package/dist/dev-server/config.d.ts +11 -0
  101. package/dist/dev-server/config.js +40 -20
  102. package/dist/dev-server/index-html.d.ts +4 -0
  103. package/dist/dev-server/index-html.js +21 -6
  104. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  105. package/dist/dev-server/nuralyui-aliases.js +115 -94
  106. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  107. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  108. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  109. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  110. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  111. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  113. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  114. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  115. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  116. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  117. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  118. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  119. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  120. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  121. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  122. package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
  123. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  125. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  127. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  128. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  129. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
  130. package/dist/dev-server/server.js +242 -70
  131. package/dist/dev-server/ssr-render.d.ts +2 -1
  132. package/dist/dev-server/ssr-render.js +117 -50
  133. package/dist/editor/ai/backend.d.ts +20 -0
  134. package/dist/editor/ai/backend.js +113 -0
  135. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  136. package/dist/editor/ai/claude-code-client.js +145 -0
  137. package/dist/editor/ai/deepseek-client.d.ts +7 -0
  138. package/dist/editor/ai/deepseek-client.js +113 -0
  139. package/dist/editor/ai/opencode-client.d.ts +14 -0
  140. package/dist/editor/ai/opencode-client.js +99 -0
  141. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  142. package/dist/editor/ai/snapshot-store.js +35 -0
  143. package/dist/editor/ai/types.d.ts +30 -0
  144. package/dist/editor/ai/types.js +136 -0
  145. package/dist/editor/ai-chat-panel.d.ts +13 -0
  146. package/dist/editor/ai-chat-panel.js +613 -0
  147. package/dist/editor/ai-markdown.d.ts +10 -0
  148. package/dist/editor/ai-markdown.js +70 -0
  149. package/dist/editor/ai-project-panel.d.ts +11 -0
  150. package/dist/editor/ai-project-panel.js +332 -0
  151. package/dist/editor/ast-modification.d.ts +11 -0
  152. package/dist/editor/ast-modification.js +1 -0
  153. package/dist/editor/ast-service.d.ts +30 -0
  154. package/dist/editor/ast-service.js +180 -0
  155. package/dist/editor/css-rules.d.ts +54 -0
  156. package/dist/editor/css-rules.js +423 -0
  157. package/dist/editor/editor-api-client.d.ts +51 -0
  158. package/dist/editor/editor-api-client.js +162 -0
  159. package/dist/editor/editor-bridge.d.ts +1 -0
  160. package/dist/editor/editor-bridge.js +18 -8
  161. package/dist/editor/editor-toolbar.d.ts +14 -0
  162. package/dist/editor/editor-toolbar.js +115 -0
  163. package/dist/editor/file-editor.d.ts +9 -0
  164. package/dist/editor/file-editor.js +236 -0
  165. package/dist/editor/file-service.d.ts +16 -0
  166. package/dist/editor/file-service.js +52 -0
  167. package/dist/editor/i18n-key-gen.d.ts +1 -0
  168. package/dist/editor/i18n-key-gen.js +7 -0
  169. package/dist/editor/inline-text-edit.d.ts +5 -0
  170. package/dist/editor/inline-text-edit.js +173 -92
  171. package/dist/editor/overlay-events.d.ts +5 -0
  172. package/dist/editor/overlay-events.js +364 -0
  173. package/dist/editor/overlay-hmr.d.ts +2 -0
  174. package/dist/editor/overlay-hmr.js +76 -0
  175. package/dist/editor/overlay-selection.d.ts +29 -0
  176. package/dist/editor/overlay-selection.js +148 -0
  177. package/dist/editor/overlay-utils.d.ts +12 -0
  178. package/dist/editor/overlay-utils.js +59 -0
  179. package/dist/editor/properties-panel-persist.d.ts +14 -0
  180. package/dist/editor/properties-panel-persist.js +70 -0
  181. package/dist/editor/properties-panel-rows.d.ts +10 -0
  182. package/dist/editor/properties-panel-rows.js +349 -0
  183. package/dist/editor/properties-panel-styles.d.ts +4 -0
  184. package/dist/editor/properties-panel-styles.js +174 -0
  185. package/dist/editor/properties-panel.d.ts +4 -0
  186. package/dist/editor/properties-panel.js +148 -0
  187. package/dist/editor/property-registry.d.ts +16 -0
  188. package/dist/editor/property-registry.js +303 -0
  189. package/dist/editor/standalone-file-panel.d.ts +0 -0
  190. package/dist/editor/standalone-file-panel.js +1 -0
  191. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  192. package/dist/editor/standalone-overlay-dom.js +1 -0
  193. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  194. package/dist/editor/standalone-overlay-styles.js +1 -0
  195. package/dist/editor/standalone-overlay.d.ts +1 -0
  196. package/dist/editor/standalone-overlay.js +76 -0
  197. package/dist/editor/syntax-highlighter.d.ts +4 -0
  198. package/dist/editor/syntax-highlighter.js +81 -0
  199. package/dist/editor/text-toolbar.d.ts +11 -0
  200. package/dist/editor/text-toolbar.js +327 -0
  201. package/dist/editor/toolbar-styles.d.ts +4 -0
  202. package/dist/editor/toolbar-styles.js +198 -0
  203. package/dist/email/index.d.ts +32 -0
  204. package/dist/email/index.js +154 -0
  205. package/dist/email/providers/resend.d.ts +2 -0
  206. package/dist/email/providers/resend.js +24 -0
  207. package/dist/email/providers/sendgrid.d.ts +2 -0
  208. package/dist/email/providers/sendgrid.js +31 -0
  209. package/dist/email/providers/smtp.d.ts +13 -0
  210. package/dist/email/providers/smtp.js +125 -0
  211. package/dist/email/template-engine.d.ts +18 -0
  212. package/dist/email/template-engine.js +116 -0
  213. package/dist/email/templates/base.d.ts +9 -0
  214. package/dist/email/templates/base.js +65 -0
  215. package/dist/email/templates/password-reset.d.ts +5 -0
  216. package/dist/email/templates/password-reset.js +15 -0
  217. package/dist/email/templates/verify-email.d.ts +5 -0
  218. package/dist/email/templates/verify-email.js +15 -0
  219. package/dist/email/templates/welcome.d.ts +5 -0
  220. package/dist/email/templates/welcome.js +13 -0
  221. package/dist/email/types.d.ts +49 -0
  222. package/dist/email/types.js +1 -0
  223. package/dist/llms/generate.d.ts +46 -0
  224. package/dist/llms/generate.js +185 -0
  225. package/dist/permissions/guard.d.ts +28 -0
  226. package/dist/permissions/guard.js +30 -0
  227. package/dist/permissions/index.d.ts +6 -0
  228. package/dist/permissions/index.js +3 -0
  229. package/dist/permissions/service.d.ts +80 -0
  230. package/dist/permissions/service.js +210 -0
  231. package/dist/permissions/tables.d.ts +5 -0
  232. package/dist/permissions/tables.js +68 -0
  233. package/dist/permissions/types.d.ts +33 -0
  234. package/dist/permissions/types.js +1 -0
  235. package/dist/runtime/app-shell.d.ts +1 -1
  236. package/dist/runtime/app-shell.js +164 -0
  237. package/dist/runtime/auth.d.ts +10 -0
  238. package/dist/runtime/auth.js +30 -0
  239. package/dist/runtime/communication.d.ts +137 -0
  240. package/dist/runtime/communication.js +228 -0
  241. package/dist/runtime/error-boundary.d.ts +23 -0
  242. package/dist/runtime/error-boundary.js +120 -0
  243. package/dist/runtime/i18n.d.ts +6 -1
  244. package/dist/runtime/i18n.js +42 -21
  245. package/dist/runtime/island.d.ts +16 -0
  246. package/dist/runtime/island.js +80 -0
  247. package/dist/runtime/router-data.d.ts +3 -0
  248. package/dist/runtime/router-data.js +102 -17
  249. package/dist/runtime/router-hydration.js +34 -2
  250. package/dist/runtime/router.d.ts +19 -2
  251. package/dist/runtime/router.js +237 -43
  252. package/dist/runtime/socket-client.d.ts +2 -0
  253. package/dist/runtime/socket-client.js +30 -0
  254. package/dist/runtime/webrtc.d.ts +91 -0
  255. package/dist/runtime/webrtc.js +428 -0
  256. package/dist/shared/dom-shims.js +4 -2
  257. package/dist/shared/graceful-shutdown.d.ts +8 -0
  258. package/dist/shared/graceful-shutdown.js +36 -0
  259. package/dist/shared/health.d.ts +8 -0
  260. package/dist/shared/health.js +25 -0
  261. package/dist/shared/llms-txt.d.ts +31 -0
  262. package/dist/shared/llms-txt.js +85 -0
  263. package/dist/shared/logger.d.ts +32 -0
  264. package/dist/shared/logger.js +93 -0
  265. package/dist/shared/meta.d.ts +27 -0
  266. package/dist/shared/meta.js +71 -0
  267. package/dist/shared/middleware-runner.d.ts +9 -0
  268. package/dist/shared/middleware-runner.js +29 -0
  269. package/dist/shared/rate-limit.d.ts +18 -0
  270. package/dist/shared/rate-limit.js +71 -0
  271. package/dist/shared/request-id.d.ts +5 -0
  272. package/dist/shared/request-id.js +18 -0
  273. package/dist/shared/route-matching.js +16 -1
  274. package/dist/shared/security-headers.d.ts +18 -0
  275. package/dist/shared/security-headers.js +38 -0
  276. package/dist/shared/socket-io-setup.d.ts +11 -0
  277. package/dist/shared/socket-io-setup.js +51 -0
  278. package/dist/shared/types.d.ts +15 -0
  279. package/dist/shared/utils.d.ts +33 -7
  280. package/dist/shared/utils.js +164 -27
  281. package/dist/storage/adapters/local.d.ts +44 -0
  282. package/dist/storage/adapters/local.js +85 -0
  283. package/dist/storage/adapters/s3.d.ts +32 -0
  284. package/dist/storage/adapters/s3.js +119 -0
  285. package/dist/storage/adapters/types.d.ts +53 -0
  286. package/dist/storage/adapters/types.js +1 -0
  287. package/dist/storage/index.d.ts +76 -0
  288. package/dist/storage/index.js +83 -0
  289. package/package.json +45 -7
  290. package/templates/blog/api/posts.ts +4 -18
  291. package/templates/blog/data/migrations/001_init.sql +6 -5
  292. package/templates/blog/lumenjs.config.ts +3 -0
  293. package/templates/blog/package.json +14 -0
  294. package/templates/blog/pages/_layout.ts +25 -0
  295. package/templates/blog/pages/index.ts +48 -22
  296. package/templates/blog/pages/posts/[slug].ts +45 -20
  297. package/templates/blog/pages/tag/[tag].ts +44 -0
  298. package/templates/dashboard/api/stats.ts +8 -5
  299. package/templates/dashboard/lumenjs.config.ts +3 -0
  300. package/templates/dashboard/package.json +14 -0
  301. package/templates/dashboard/pages/_layout.ts +25 -0
  302. package/templates/dashboard/pages/index.ts +54 -23
  303. package/templates/dashboard/pages/settings/index.ts +29 -0
  304. package/templates/default/lumenjs.config.ts +3 -0
  305. package/templates/default/package.json +14 -0
  306. package/templates/default/pages/index.ts +24 -0
@@ -1,11 +1,12 @@
1
- import { build as viteBuild } from 'vite';
2
1
  import path from 'path';
3
2
  import fs from 'fs';
4
3
  import { getSharedViteConfig } from '../dev-server/server.js';
5
4
  import { readProjectConfig } from '../dev-server/config.js';
6
- import { generateIndexHtml } from '../dev-server/index-html.js';
7
5
  import { filePathToTagName } from '../shared/utils.js';
8
- import { scanPages, scanLayouts, scanApiRoutes, getLayoutDirsForPage } from './scan.js';
6
+ import { scanPages, scanLayouts, scanApiRoutes, scanMiddleware, getLayoutDirsForPage } from './scan.js';
7
+ import { buildClient } from './build-client.js';
8
+ import { buildServer } from './build-server.js';
9
+ import { prerenderPages } from './build-prerender.js';
9
10
  export async function buildProject(options) {
10
11
  const { projectDir } = options;
11
12
  const outDir = options.outDir || path.join(projectDir, '.lumenjs');
@@ -19,130 +20,44 @@ export async function buildProject(options) {
19
20
  fs.rmSync(outDir, { recursive: true });
20
21
  }
21
22
  fs.mkdirSync(outDir, { recursive: true });
22
- const { title, integrations, i18n: i18nConfig } = readProjectConfig(projectDir);
23
+ const { title, integrations, i18n: i18nConfig, prefetch: prefetchStrategy, prerender: globalPrerender } = readProjectConfig(projectDir);
23
24
  const shared = getSharedViteConfig(projectDir, { mode: 'production', integrations });
24
- // Scan pages, layouts, and API routes for the manifest
25
+ // Scan pages, layouts, API routes, and middleware for the manifest
25
26
  const pageEntries = scanPages(pagesDir);
26
27
  const layoutEntries = scanLayouts(pagesDir);
27
28
  const apiEntries = scanApiRoutes(apiDir);
28
- // --- Client build ---
29
- console.log('[LumenJS] Building client bundle...');
30
- // Generate index.html as build entry
31
- const indexHtml = generateIndexHtml({ title, editorMode: false, integrations });
32
- const tempIndexPath = path.join(projectDir, '__nk_build_index.html');
33
- fs.writeFileSync(tempIndexPath, indexHtml);
34
- try {
35
- await viteBuild({
36
- root: projectDir,
37
- publicDir: fs.existsSync(publicDir) ? publicDir : undefined,
38
- resolve: shared.resolve,
39
- plugins: shared.plugins,
40
- esbuild: shared.esbuild,
41
- build: {
42
- outDir: clientDir,
43
- emptyOutDir: true,
44
- rollupOptions: {
45
- input: tempIndexPath,
46
- },
47
- },
48
- logLevel: 'warn',
49
- });
50
- }
51
- finally {
52
- // Clean up temp file
53
- if (fs.existsSync(tempIndexPath)) {
54
- fs.unlinkSync(tempIndexPath);
29
+ const middlewareEntries = scanMiddleware(pagesDir);
30
+ // Check for auth config
31
+ const authConfigPath = path.join(projectDir, 'lumenjs.auth.ts');
32
+ const hasAuthConfig = fs.existsSync(authConfigPath);
33
+ // Apply global prerender flag from config
34
+ if (globalPrerender) {
35
+ for (const entry of pageEntries) {
36
+ entry.prerender = true;
55
37
  }
56
38
  }
57
- // Rename the built HTML file from __nk_build_index.html to index.html
58
- const builtHtmlPath = path.join(clientDir, '__nk_build_index.html');
59
- const finalHtmlPath = path.join(clientDir, 'index.html');
60
- if (fs.existsSync(builtHtmlPath)) {
61
- fs.renameSync(builtHtmlPath, finalHtmlPath);
62
- }
39
+ // --- Client build ---
40
+ await buildClient({
41
+ projectDir,
42
+ clientDir,
43
+ title,
44
+ integrations,
45
+ prefetchStrategy,
46
+ publicDir,
47
+ shared,
48
+ });
63
49
  // --- Server build ---
64
- console.log('[LumenJS] Building server bundle...');
65
- // Collect server entry points (pages with loaders + layouts with loaders + API routes)
66
- const serverEntries = {};
67
- for (const entry of pageEntries) {
68
- if (entry.hasLoader || entry.hasSubscribe) {
69
- serverEntries[`pages/${entry.name}`] = entry.filePath;
70
- }
71
- }
72
- for (const entry of layoutEntries) {
73
- if (entry.hasLoader || entry.hasSubscribe) {
74
- const entryName = entry.dir ? `layouts/${entry.dir}/_layout` : 'layouts/_layout';
75
- serverEntries[entryName] = entry.filePath;
76
- }
77
- }
78
- for (const entry of apiEntries) {
79
- serverEntries[`api/${entry.name}`] = entry.filePath;
80
- }
81
- // Create SSR runtime entry — bundles @lit-labs/ssr alongside Lit so all
82
- // server modules share one Lit instance (avoids _$EM mismatches).
83
- const ssrEntryPath = path.join(projectDir, '__nk_ssr_entry.js');
84
- const hasPageLoaders = pageEntries.some(e => e.hasLoader);
85
- const hasLayoutLoaders = layoutEntries.some(e => e.hasLoader);
86
- if (hasPageLoaders || hasLayoutLoaders) {
87
- fs.writeFileSync(ssrEntryPath, [
88
- "import '@lit-labs/ssr/lib/install-global-dom-shim.js';",
89
- "export { render } from '@lit-labs/ssr';",
90
- "export { html, unsafeStatic } from 'lit/static-html.js';",
91
- ].join('\n'));
92
- serverEntries['ssr-runtime'] = ssrEntryPath;
93
- }
94
- try {
95
- if (Object.keys(serverEntries).length > 0) {
96
- await viteBuild({
97
- root: projectDir,
98
- resolve: shared.resolve,
99
- plugins: shared.plugins,
100
- esbuild: shared.esbuild,
101
- build: {
102
- outDir: serverDir,
103
- emptyOutDir: true,
104
- ssr: true,
105
- rollupOptions: {
106
- input: serverEntries,
107
- output: {
108
- format: 'esm',
109
- entryFileNames: '[name].js',
110
- chunkFileNames: 'assets/[name]-[hash].js',
111
- manualChunks(id) {
112
- // Force all Lit packages into a single shared chunk so SSR runtime
113
- // and page modules use the exact same Lit class instances.
114
- if (id.includes('/node_modules/lit/') ||
115
- id.includes('/node_modules/lit-html/') ||
116
- id.includes('/node_modules/lit-element/') ||
117
- id.includes('/node_modules/@lit/reactive-element/')) {
118
- return 'lit-shared';
119
- }
120
- },
121
- },
122
- external: [
123
- /^node:/,
124
- 'os', 'fs', 'path', 'url', 'util', 'crypto', 'http', 'https', 'net',
125
- 'stream', 'zlib', 'events', 'buffer', 'querystring', 'child_process',
126
- 'worker_threads', 'cluster', 'dns', 'tls', 'assert', 'constants',
127
- 'better-sqlite3',
128
- ],
129
- },
130
- },
131
- logLevel: 'warn',
132
- ssr: {
133
- noExternal: true,
134
- },
135
- });
136
- }
137
- else {
138
- fs.mkdirSync(serverDir, { recursive: true });
139
- }
140
- }
141
- finally {
142
- if (fs.existsSync(ssrEntryPath)) {
143
- fs.unlinkSync(ssrEntryPath);
144
- }
145
- }
50
+ await buildServer({
51
+ projectDir,
52
+ serverDir,
53
+ pageEntries,
54
+ layoutEntries,
55
+ apiEntries,
56
+ middlewareEntries,
57
+ hasAuthConfig,
58
+ authConfigPath,
59
+ shared,
60
+ });
146
61
  // --- Copy locales ---
147
62
  if (i18nConfig) {
148
63
  const localesDir = path.join(projectDir, 'locales');
@@ -160,20 +75,25 @@ export async function buildProject(options) {
160
75
  // --- Write manifest ---
161
76
  const manifest = {
162
77
  routes: pageEntries.map(e => {
163
- const routeLayouts = getLayoutDirsForPage(e.filePath, pagesDir, layoutEntries);
78
+ const routeLayouts = e.hasStandalone ? [] : getLayoutDirsForPage(e.filePath, pagesDir, layoutEntries);
164
79
  const relPath = path.relative(pagesDir, e.filePath).replace(/\\/g, '/');
165
80
  return {
166
81
  path: e.routePath,
167
- module: (e.hasLoader || e.hasSubscribe) ? `pages/${e.name}.js` : '',
82
+ module: (e.hasLoader || e.hasSubscribe || e.hasSocket || e.prerender) ? `pages/${e.name.replace(/\[(\w+)\]/g, '_$1_')}.js` : '',
168
83
  hasLoader: e.hasLoader,
169
84
  hasSubscribe: e.hasSubscribe,
170
85
  tagName: filePathToTagName(relPath),
171
86
  ...(routeLayouts.length > 0 ? { layouts: routeLayouts } : {}),
87
+ ...(e.hasSocket ? { hasSocket: true } : {}),
88
+ ...(e.hasAuth ? { hasAuth: true } : {}),
89
+ ...(e.hasMeta ? { hasMeta: true } : {}),
90
+ ...(e.hasStandalone ? { hasStandalone: true } : {}),
91
+ ...(e.prerender ? { prerender: true } : {}),
172
92
  };
173
93
  }),
174
94
  apiRoutes: apiEntries.map(e => ({
175
95
  path: `/api/${e.routePath}`,
176
- module: `api/${e.name}.js`,
96
+ module: `api/${e.name.replace(/\[(\w+)\]/g, '_$1_')}.js`,
177
97
  hasLoader: false,
178
98
  hasSubscribe: false,
179
99
  })),
@@ -183,9 +103,26 @@ export async function buildProject(options) {
183
103
  hasLoader: e.hasLoader,
184
104
  hasSubscribe: e.hasSubscribe,
185
105
  })),
106
+ ...(middlewareEntries.length > 0 ? {
107
+ middlewares: middlewareEntries.map(e => ({
108
+ dir: e.dir,
109
+ module: e.dir ? `middleware/${e.dir}/_middleware.js` : 'middleware/_middleware.js',
110
+ })),
111
+ } : {}),
186
112
  ...(i18nConfig ? { i18n: i18nConfig } : {}),
113
+ ...(hasAuthConfig ? { auth: { configModule: 'auth-config.js' } } : {}),
114
+ prefetch: prefetchStrategy,
187
115
  };
188
116
  fs.writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
117
+ // --- Pre-render phase ---
118
+ await prerenderPages({
119
+ serverDir,
120
+ clientDir,
121
+ pagesDir,
122
+ pageEntries,
123
+ layoutEntries,
124
+ manifest,
125
+ });
189
126
  console.log('[LumenJS] Build complete.');
190
127
  console.log(` Output: ${outDir}`);
191
128
  console.log(` Client assets: ${clientDir}`);
@@ -4,6 +4,11 @@ export interface PageEntry {
4
4
  routePath: string;
5
5
  hasLoader: boolean;
6
6
  hasSubscribe: boolean;
7
+ hasSocket: boolean;
8
+ hasAuth: boolean;
9
+ hasMeta: boolean;
10
+ hasStandalone: boolean;
11
+ prerender: boolean;
7
12
  }
8
13
  export interface LayoutEntry {
9
14
  dir: string;
@@ -21,3 +26,16 @@ export declare function scanLayouts(pagesDir: string): LayoutEntry[];
21
26
  export declare function scanApiRoutes(apiDir: string): ApiEntry[];
22
27
  /** Get the layout directory chain for a given page file */
23
28
  export declare function getLayoutDirsForPage(pageFilePath: string, pagesDir: string, layouts: LayoutEntry[]): string[];
29
+ export interface MiddlewareEntry {
30
+ dir: string;
31
+ filePath: string;
32
+ }
33
+ /**
34
+ * Scan for _middleware.ts files in the pages directory tree.
35
+ */
36
+ export declare function scanMiddleware(pagesDir: string): MiddlewareEntry[];
37
+ /**
38
+ * Get middleware directories that match a given URL pathname.
39
+ * Returns matching middleware entries from root → deepest.
40
+ */
41
+ export declare function getMiddlewareDirsForPathname(pathname: string, entries: MiddlewareEntry[]): MiddlewareEntry[];
@@ -1,6 +1,33 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { fileHasLoader, fileHasSubscribe, filePathToRoute } from '../shared/utils.js';
3
+ import { filePathToRoute, fileHasLoader, fileHasSubscribe } from '../shared/utils.js';
4
+ /** Read a page file once and check all flags from the same content. */
5
+ function analyzePageFile(filePath) {
6
+ try {
7
+ const content = fs.readFileSync(filePath, 'utf-8');
8
+ const classStart = content.search(/export\s+class\s+\w+/);
9
+ const hasExportBefore = (regex) => {
10
+ const match = regex.exec(content);
11
+ if (!match)
12
+ return false;
13
+ if (classStart >= 0 && match.index > classStart)
14
+ return false;
15
+ return true;
16
+ };
17
+ return {
18
+ hasLoader: hasExportBefore(/export\s+(async\s+)?function\s+loader\s*\(/),
19
+ hasSubscribe: hasExportBefore(/export\s+(async\s+)?function\s+subscribe\s*\(/),
20
+ hasSocket: /export\s+(function|const)\s+socket[\s(=]/.test(content),
21
+ hasAuth: hasExportBefore(/export\s+const\s+auth\s*=/),
22
+ hasMeta: hasExportBefore(/export\s+(const\s+meta\s*=|(async\s+)?function\s+meta\s*\()/),
23
+ hasStandalone: hasExportBefore(/export\s+const\s+standalone\s*=/),
24
+ prerender: /export\s+const\s+prerender\s*=\s*true/.test(content),
25
+ };
26
+ }
27
+ catch {
28
+ return { hasLoader: false, hasSubscribe: false, hasSocket: false, hasAuth: false, hasMeta: false, hasStandalone: false, prerender: false };
29
+ }
30
+ }
4
31
  export function scanPages(pagesDir) {
5
32
  if (!fs.existsSync(pagesDir))
6
33
  return [];
@@ -44,18 +71,23 @@ export function getLayoutDirsForPage(pageFilePath, pagesDir, layouts) {
44
71
  function walkDir(baseDir, relativePath, entries, pagesDir) {
45
72
  const fullDir = path.join(baseDir, relativePath);
46
73
  const dirEntries = fs.readdirSync(fullDir, { withFileTypes: true });
74
+ // Check if this subdirectory contains an index file (folder route)
75
+ // Only applies to subdirectories, not the root pages directory
76
+ const hasIndex = relativePath !== '' && dirEntries.some(e => e.isFile() && /^index\.(ts|js)$/.test(e.name));
47
77
  for (const entry of dirEntries) {
48
78
  const entryRelative = path.join(relativePath, entry.name);
49
- if (entry.isDirectory()) {
79
+ if (entry.isDirectory() && !entry.name.startsWith('_')) {
50
80
  walkDir(baseDir, entryRelative, entries, pagesDir);
51
81
  }
52
82
  else if (entry.isFile() && /\.(ts|js)$/.test(entry.name) && !entry.name.startsWith('_')) {
83
+ // In a folder route (has index file), only register the index file and dynamic param files
84
+ if (hasIndex && !/^index\.(ts|js)$/.test(entry.name) && !entry.name.startsWith('['))
85
+ continue;
53
86
  const filePath = path.join(pagesDir, entryRelative);
54
87
  const name = entryRelative.replace(/\.(ts|js)$/, '').replace(/\\/g, '/');
55
88
  const routePath = filePathToRoute(entryRelative);
56
- const hasLoader = fileHasLoader(filePath);
57
- const hasSubscribe = fileHasSubscribe(filePath);
58
- entries.push({ name, filePath, routePath, hasLoader, hasSubscribe });
89
+ const flags = analyzePageFile(filePath);
90
+ entries.push({ name, filePath, routePath, ...flags });
59
91
  }
60
92
  }
61
93
  }
@@ -68,11 +100,50 @@ function walkForLayouts(baseDir, relativePath, entries) {
68
100
  const dir = relativePath.replace(/\\/g, '/');
69
101
  entries.push({ dir, filePath, hasLoader: fileHasLoader(filePath), hasSubscribe: fileHasSubscribe(filePath) });
70
102
  }
71
- if (entry.isDirectory()) {
103
+ if (entry.isDirectory() && !entry.name.startsWith('_')) {
72
104
  walkForLayouts(baseDir, path.join(relativePath, entry.name), entries);
73
105
  }
74
106
  }
75
107
  }
108
+ /**
109
+ * Scan for _middleware.ts files in the pages directory tree.
110
+ */
111
+ export function scanMiddleware(pagesDir) {
112
+ if (!fs.existsSync(pagesDir))
113
+ return [];
114
+ const entries = [];
115
+ walkForMiddleware(pagesDir, '', entries);
116
+ return entries;
117
+ }
118
+ /**
119
+ * Get middleware directories that match a given URL pathname.
120
+ * Returns matching middleware entries from root → deepest.
121
+ */
122
+ export function getMiddlewareDirsForPathname(pathname, entries) {
123
+ const urlSegments = pathname.replace(/^\//, '').split('/').filter(Boolean);
124
+ return entries.filter(entry => {
125
+ if (entry.dir === '')
126
+ return true; // Root middleware applies to all routes
127
+ const dirSegments = entry.dir.split('/').filter(Boolean);
128
+ if (dirSegments.length > urlSegments.length)
129
+ return false;
130
+ return dirSegments.every((seg, i) => seg === urlSegments[i]);
131
+ }).sort((a, b) => a.dir.split('/').length - b.dir.split('/').length);
132
+ }
133
+ function walkForMiddleware(baseDir, relativePath, entries) {
134
+ const fullDir = path.join(baseDir, relativePath);
135
+ const dirEntries = fs.readdirSync(fullDir, { withFileTypes: true });
136
+ for (const entry of dirEntries) {
137
+ if (entry.isFile() && /^_middleware\.(ts|js)$/.test(entry.name)) {
138
+ const filePath = path.join(fullDir, entry.name);
139
+ const dir = relativePath.replace(/\\/g, '/');
140
+ entries.push({ dir, filePath });
141
+ }
142
+ if (entry.isDirectory() && !entry.name.startsWith('_')) {
143
+ walkForMiddleware(baseDir, path.join(relativePath, entry.name), entries);
144
+ }
145
+ }
146
+ }
76
147
  function walkApiDir(baseDir, relativePath, entries, apiDir) {
77
148
  const fullDir = path.join(baseDir, relativePath);
78
149
  const dirEntries = fs.readdirSync(fullDir, { withFileTypes: true });
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { readBody } from '../shared/utils.js';
4
4
  import { matchRoute } from '../shared/route-matching.js';
5
+ import { useStorage } from '../storage/index.js';
5
6
  export async function handleApiRoute(manifest, serverDir, pathname, queryString, method, req, res) {
6
7
  const matched = matchRoute(manifest.apiRoutes, pathname);
7
8
  if (!matched) {
@@ -9,7 +10,9 @@ export async function handleApiRoute(manifest, serverDir, pathname, queryString,
9
10
  res.end(JSON.stringify({ error: 'API route not found' }));
10
11
  return;
11
12
  }
12
- const modulePath = path.join(serverDir, matched.route.module);
13
+ // Vite replaces [param] with _param_ in output filenames
14
+ const resolvedModule = matched.route.module.replace(/\[([^\]]+)\]/g, '_$1_');
15
+ const modulePath = path.join(serverDir, resolvedModule);
13
16
  if (!fs.existsSync(modulePath)) {
14
17
  res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
15
18
  res.end(JSON.stringify({ error: 'API route module not found' }));
@@ -43,13 +46,16 @@ export async function handleApiRoute(manifest, serverDir, pathname, queryString,
43
46
  params: matched.params,
44
47
  body,
45
48
  headers: req.headers,
49
+ storage: useStorage(),
50
+ nkAuth: req.nkAuth,
46
51
  });
47
52
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
48
53
  res.end(JSON.stringify(result));
49
54
  }
50
55
  catch (err) {
51
56
  const status = err?.status || 500;
52
- const message = err?.message || 'Internal server error';
57
+ // Only expose error messages for client errors (4xx); hide internals for 5xx
58
+ const message = status < 500 ? (err?.message || 'Request error') : 'Internal server error';
53
59
  res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
54
60
  res.end(JSON.stringify({ error: message }));
55
61
  }
@@ -1,6 +1,6 @@
1
1
  import http from 'http';
2
2
  import type { BuildManifest } from '../shared/types.js';
3
- export declare function handleLayoutLoaderRequest(manifest: BuildManifest, serverDir: string, queryString: string | undefined, headers: http.IncomingHttpHeaders, res: http.ServerResponse): Promise<void>;
4
- export declare function handleLayoutSubscribeRequest(manifest: BuildManifest, serverDir: string, queryString: string | undefined, headers: http.IncomingHttpHeaders, res: http.ServerResponse): Promise<void>;
5
- export declare function handleSubscribeRequest(manifest: BuildManifest, serverDir: string, pagesDir: string, pathname: string, queryString: string | undefined, headers: http.IncomingHttpHeaders, res: http.ServerResponse): Promise<void>;
6
- export declare function handleLoaderRequest(manifest: BuildManifest, serverDir: string, pagesDir: string, pathname: string, queryString: string | undefined, headers: http.IncomingHttpHeaders, res: http.ServerResponse): Promise<void>;
3
+ export declare function handleLayoutLoaderRequest(manifest: BuildManifest, serverDir: string, queryString: string | undefined, headers: http.IncomingHttpHeaders, res: http.ServerResponse, user?: any): Promise<void>;
4
+ export declare function handleLayoutSubscribeRequest(manifest: BuildManifest, serverDir: string, queryString: string | undefined, headers: http.IncomingHttpHeaders, res: http.ServerResponse, user?: any): Promise<void>;
5
+ export declare function handleSubscribeRequest(manifest: BuildManifest, serverDir: string, pagesDir: string, pathname: string, queryString: string | undefined, headers: http.IncomingHttpHeaders, res: http.ServerResponse, user?: any): Promise<void>;
6
+ export declare function handleLoaderRequest(manifest: BuildManifest, serverDir: string, pagesDir: string, pathname: string, queryString: string | undefined, headers: http.IncomingHttpHeaders, res: http.ServerResponse, user?: any): Promise<void>;
@@ -2,7 +2,15 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { isRedirectResponse } from '../shared/utils.js';
4
4
  import { matchRoute } from '../shared/route-matching.js';
5
- export async function handleLayoutLoaderRequest(manifest, serverDir, queryString, headers, res) {
5
+ import { logger } from '../shared/logger.js';
6
+ /** Resolve a module path, falling back to bracket-sanitized filename (Rollup replaces [] with _) */
7
+ function resolveModulePath(serverDir, moduleName) {
8
+ const p = path.join(serverDir, moduleName);
9
+ if (fs.existsSync(p))
10
+ return p;
11
+ return path.join(serverDir, moduleName.replace(/\[/g, '_').replace(/\]/g, '_'));
12
+ }
13
+ export async function handleLayoutLoaderRequest(manifest, serverDir, queryString, headers, res, user) {
6
14
  const query = {};
7
15
  if (queryString) {
8
16
  for (const pair of queryString.split('&')) {
@@ -18,7 +26,7 @@ export async function handleLayoutLoaderRequest(manifest, serverDir, queryString
18
26
  res.end(JSON.stringify({ __nk_no_loader: true }));
19
27
  return;
20
28
  }
21
- const modulePath = path.join(serverDir, layout.module);
29
+ const modulePath = resolveModulePath(serverDir, layout.module);
22
30
  if (!fs.existsSync(modulePath)) {
23
31
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
24
32
  res.end(JSON.stringify({ __nk_no_loader: true }));
@@ -34,7 +42,7 @@ export async function handleLayoutLoaderRequest(manifest, serverDir, queryString
34
42
  // Parse locale from query for layout loader
35
43
  const locale = query.__locale;
36
44
  delete query.__locale;
37
- const result = await mod.loader({ params: {}, query: {}, url: `/__layout/${dir}`, headers, locale });
45
+ const result = await mod.loader({ params: {}, query: {}, url: `/__layout/${dir}`, headers, locale, user: user ?? null });
38
46
  if (isRedirectResponse(result)) {
39
47
  res.writeHead(result.status || 302, { Location: result.location });
40
48
  res.end();
@@ -49,14 +57,14 @@ export async function handleLayoutLoaderRequest(manifest, serverDir, queryString
49
57
  res.end();
50
58
  return;
51
59
  }
52
- console.error(`[LumenJS] Layout loader error for dir=${dir}:`, err);
60
+ logger.error(`Layout loader error`, { dir, error: err?.message });
53
61
  const status = err?.status || 500;
54
- const message = err?.message || 'Layout loader failed';
62
+ const message = status < 500 ? (err?.message || 'Layout loader failed') : 'Internal server error';
55
63
  res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
56
64
  res.end(JSON.stringify({ error: message }));
57
65
  }
58
66
  }
59
- export async function handleLayoutSubscribeRequest(manifest, serverDir, queryString, headers, res) {
67
+ export async function handleLayoutSubscribeRequest(manifest, serverDir, queryString, headers, res, user) {
60
68
  const query = {};
61
69
  if (queryString) {
62
70
  for (const pair of queryString.split('&')) {
@@ -71,7 +79,7 @@ export async function handleLayoutSubscribeRequest(manifest, serverDir, queryStr
71
79
  res.end();
72
80
  return;
73
81
  }
74
- const modulePath = path.join(serverDir, layout.module);
82
+ const modulePath = resolveModulePath(serverDir, layout.module);
75
83
  if (!fs.existsSync(modulePath)) {
76
84
  res.writeHead(204);
77
85
  res.end();
@@ -93,21 +101,21 @@ export async function handleLayoutSubscribeRequest(manifest, serverDir, queryStr
93
101
  const push = (data) => {
94
102
  res.write(`data: ${JSON.stringify(data)}\n\n`);
95
103
  };
96
- const cleanup = mod.subscribe({ params: {}, push, headers, locale });
104
+ const cleanup = mod.subscribe({ params: {}, push, headers, locale, user: user ?? null });
97
105
  res.on('close', () => {
98
106
  if (typeof cleanup === 'function')
99
107
  cleanup();
100
108
  });
101
109
  }
102
110
  catch (err) {
103
- console.error(`[LumenJS] Layout subscribe error for dir=${dir}:`, err);
111
+ logger.error(`Layout subscribe error`, { dir, error: err?.message });
104
112
  if (!res.headersSent) {
105
113
  res.writeHead(500);
106
114
  res.end();
107
115
  }
108
116
  }
109
117
  }
110
- export async function handleSubscribeRequest(manifest, serverDir, pagesDir, pathname, queryString, headers, res) {
118
+ export async function handleSubscribeRequest(manifest, serverDir, pagesDir, pathname, queryString, headers, res, user) {
111
119
  const pagePath = pathname.replace('/__nk_subscribe', '') || '/';
112
120
  const query = {};
113
121
  if (queryString) {
@@ -130,7 +138,7 @@ export async function handleSubscribeRequest(manifest, serverDir, pagesDir, path
130
138
  res.end();
131
139
  return;
132
140
  }
133
- const modulePath = path.join(serverDir, matched.route.module);
141
+ const modulePath = resolveModulePath(serverDir, matched.route.module);
134
142
  if (!fs.existsSync(modulePath)) {
135
143
  res.writeHead(204);
136
144
  res.end();
@@ -152,21 +160,21 @@ export async function handleSubscribeRequest(manifest, serverDir, pagesDir, path
152
160
  const push = (data) => {
153
161
  res.write(`data: ${JSON.stringify(data)}\n\n`);
154
162
  };
155
- const cleanup = mod.subscribe({ params: matched.params, push, headers, locale });
163
+ const cleanup = mod.subscribe({ params: matched.params, push, headers, locale, user: user ?? null });
156
164
  res.on('close', () => {
157
165
  if (typeof cleanup === 'function')
158
166
  cleanup();
159
167
  });
160
168
  }
161
169
  catch (err) {
162
- console.error(`[LumenJS] Subscribe error for ${pagePath}:`, err);
170
+ logger.error(`Subscribe error`, { pagePath, error: err?.message });
163
171
  if (!res.headersSent) {
164
172
  res.writeHead(500);
165
173
  res.end();
166
174
  }
167
175
  }
168
176
  }
169
- export async function handleLoaderRequest(manifest, serverDir, pagesDir, pathname, queryString, headers, res) {
177
+ export async function handleLoaderRequest(manifest, serverDir, pagesDir, pathname, queryString, headers, res, user) {
170
178
  const pagePath = pathname.replace('/__nk_loader', '') || '/';
171
179
  // Parse query params
172
180
  const query = {};
@@ -191,7 +199,7 @@ export async function handleLoaderRequest(manifest, serverDir, pagesDir, pathnam
191
199
  res.end(JSON.stringify({ __nk_no_loader: true }));
192
200
  return;
193
201
  }
194
- const modulePath = path.join(serverDir, matched.route.module);
202
+ const modulePath = resolveModulePath(serverDir, matched.route.module);
195
203
  if (!fs.existsSync(modulePath)) {
196
204
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
197
205
  res.end(JSON.stringify({ __nk_no_loader: true }));
@@ -206,7 +214,7 @@ export async function handleLoaderRequest(manifest, serverDir, pagesDir, pathnam
206
214
  }
207
215
  const locale = query.__locale;
208
216
  delete query.__locale;
209
- const result = await mod.loader({ params: matched.params, query, url: pagePath, headers, locale });
217
+ const result = await mod.loader({ params: matched.params, query, url: pagePath, headers, locale, user: user ?? null });
210
218
  if (isRedirectResponse(result)) {
211
219
  res.writeHead(result.status || 302, { Location: result.location });
212
220
  res.end();
@@ -221,9 +229,9 @@ export async function handleLoaderRequest(manifest, serverDir, pagesDir, pathnam
221
229
  res.end();
222
230
  return;
223
231
  }
224
- console.error(`[LumenJS] Loader error for ${pagePath}:`, err);
232
+ logger.error(`Loader error`, { pagePath, error: err?.message });
225
233
  const status = err?.status || 500;
226
- const message = err?.message || 'Loader failed';
234
+ const message = status < 500 ? (err?.message || 'Loader failed') : 'Internal server error';
227
235
  res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
228
236
  res.end(JSON.stringify({ error: message }));
229
237
  }