@nuraly/lumenjs 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.
Files changed (333) 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 +73 -0
  11. package/dist/auth/native-auth.js +293 -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 +98 -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/utils.d.ts +7 -0
  31. package/dist/auth/routes/utils.js +35 -0
  32. package/dist/auth/routes/verify.d.ts +3 -0
  33. package/dist/auth/routes/verify.js +26 -0
  34. package/dist/auth/routes.d.ts +8 -0
  35. package/dist/auth/routes.js +110 -0
  36. package/dist/auth/session.d.ts +8 -0
  37. package/dist/auth/session.js +54 -0
  38. package/dist/auth/token.d.ts +33 -0
  39. package/dist/auth/token.js +90 -0
  40. package/dist/auth/types.d.ts +156 -0
  41. package/dist/auth/types.js +2 -0
  42. package/dist/build/build-client.d.ts +15 -0
  43. package/dist/build/build-client.js +45 -0
  44. package/dist/build/build-prerender.d.ts +11 -0
  45. package/dist/build/build-prerender.js +159 -0
  46. package/dist/build/build-server.d.ts +17 -0
  47. package/dist/build/build-server.js +98 -0
  48. package/dist/build/build.js +48 -120
  49. package/dist/build/scan.d.ts +17 -0
  50. package/dist/build/scan.js +76 -6
  51. package/dist/build/serve-api.js +8 -2
  52. package/dist/build/serve-loaders.d.ts +4 -4
  53. package/dist/build/serve-loaders.js +26 -18
  54. package/dist/build/serve-ssr.js +38 -11
  55. package/dist/build/serve-static.js +3 -3
  56. package/dist/build/serve.js +218 -15
  57. package/dist/cli.js +37 -6
  58. package/dist/communication/encryption.d.ts +35 -0
  59. package/dist/communication/encryption.js +90 -0
  60. package/dist/communication/handlers/context.d.ts +27 -0
  61. package/dist/communication/handlers/context.js +1 -0
  62. package/dist/communication/handlers/conversation.d.ts +24 -0
  63. package/dist/communication/handlers/conversation.js +113 -0
  64. package/dist/communication/handlers/file-upload.d.ts +17 -0
  65. package/dist/communication/handlers/file-upload.js +62 -0
  66. package/dist/communication/handlers/messaging.d.ts +30 -0
  67. package/dist/communication/handlers/messaging.js +237 -0
  68. package/dist/communication/handlers/presence.d.ts +15 -0
  69. package/dist/communication/handlers/presence.js +76 -0
  70. package/dist/communication/handlers.d.ts +5 -0
  71. package/dist/communication/handlers.js +5 -0
  72. package/dist/communication/index.d.ts +9 -0
  73. package/dist/communication/index.js +7 -0
  74. package/dist/communication/link-preview.d.ts +18 -0
  75. package/dist/communication/link-preview.js +115 -0
  76. package/dist/communication/schema.d.ts +10 -0
  77. package/dist/communication/schema.js +101 -0
  78. package/dist/communication/server.d.ts +86 -0
  79. package/dist/communication/server.js +212 -0
  80. package/dist/communication/signaling.d.ts +43 -0
  81. package/dist/communication/signaling.js +271 -0
  82. package/dist/communication/store.d.ts +71 -0
  83. package/dist/communication/store.js +289 -0
  84. package/dist/communication/types.d.ts +454 -0
  85. package/dist/communication/types.js +1 -0
  86. package/dist/create.d.ts +1 -0
  87. package/dist/create.js +55 -0
  88. package/dist/db/auto-migrate.d.ts +3 -0
  89. package/dist/db/auto-migrate.js +100 -0
  90. package/dist/db/client.d.ts +3 -0
  91. package/dist/db/client.js +18 -0
  92. package/dist/db/index.d.ts +17 -13
  93. package/dist/db/index.js +205 -26
  94. package/dist/db/seed.d.ts +12 -0
  95. package/dist/db/seed.js +88 -0
  96. package/dist/db/table.d.ts +10 -0
  97. package/dist/db/table.js +12 -0
  98. package/dist/dev-server/config.d.ts +11 -0
  99. package/dist/dev-server/config.js +23 -20
  100. package/dist/dev-server/index-html.d.ts +3 -0
  101. package/dist/dev-server/index-html.js +18 -6
  102. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  103. package/dist/dev-server/nuralyui-aliases.js +115 -94
  104. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  105. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  106. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  107. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  108. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  109. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  110. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  111. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  113. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  114. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  115. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  116. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  117. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  118. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  119. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  120. package/dist/dev-server/plugins/vite-plugin-routes.js +15 -5
  121. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  122. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  123. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  125. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  126. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  127. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +111 -2
  128. package/dist/dev-server/server.js +127 -13
  129. package/dist/dev-server/ssr-render.d.ts +2 -1
  130. package/dist/dev-server/ssr-render.js +107 -48
  131. package/dist/editor/ai/backend.d.ts +20 -0
  132. package/dist/editor/ai/backend.js +104 -0
  133. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  134. package/dist/editor/ai/claude-code-client.js +145 -0
  135. package/dist/editor/ai/opencode-client.d.ts +14 -0
  136. package/dist/editor/ai/opencode-client.js +125 -0
  137. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  138. package/dist/editor/ai/snapshot-store.js +35 -0
  139. package/dist/editor/ai/types.d.ts +30 -0
  140. package/dist/editor/ai/types.js +136 -0
  141. package/dist/editor/ai-chat-panel.d.ts +13 -0
  142. package/dist/editor/ai-chat-panel.js +587 -0
  143. package/dist/editor/ai-markdown.d.ts +10 -0
  144. package/dist/editor/ai-markdown.js +70 -0
  145. package/dist/editor/ai-project-panel.d.ts +11 -0
  146. package/dist/editor/ai-project-panel.js +332 -0
  147. package/dist/editor/ast-modification.d.ts +11 -0
  148. package/dist/editor/ast-modification.js +1 -0
  149. package/dist/editor/ast-service.d.ts +30 -0
  150. package/dist/editor/ast-service.js +180 -0
  151. package/dist/editor/css-rules.d.ts +54 -0
  152. package/dist/editor/css-rules.js +423 -0
  153. package/dist/editor/editor-api-client.d.ts +51 -0
  154. package/dist/editor/editor-api-client.js +162 -0
  155. package/dist/editor/editor-bridge.d.ts +1 -0
  156. package/dist/editor/editor-bridge.js +17 -8
  157. package/dist/editor/editor-toolbar.d.ts +14 -0
  158. package/dist/editor/editor-toolbar.js +115 -0
  159. package/dist/editor/file-editor.d.ts +9 -0
  160. package/dist/editor/file-editor.js +236 -0
  161. package/dist/editor/file-service.d.ts +16 -0
  162. package/dist/editor/file-service.js +52 -0
  163. package/dist/editor/i18n-key-gen.d.ts +1 -0
  164. package/dist/editor/i18n-key-gen.js +7 -0
  165. package/dist/editor/inline-text-edit.d.ts +5 -0
  166. package/dist/editor/inline-text-edit.js +173 -92
  167. package/dist/editor/overlay-events.d.ts +5 -0
  168. package/dist/editor/overlay-events.js +364 -0
  169. package/dist/editor/overlay-hmr.d.ts +2 -0
  170. package/dist/editor/overlay-hmr.js +75 -0
  171. package/dist/editor/overlay-selection.d.ts +29 -0
  172. package/dist/editor/overlay-selection.js +148 -0
  173. package/dist/editor/overlay-utils.d.ts +12 -0
  174. package/dist/editor/overlay-utils.js +59 -0
  175. package/dist/editor/properties-panel-persist.d.ts +14 -0
  176. package/dist/editor/properties-panel-persist.js +70 -0
  177. package/dist/editor/properties-panel-rows.d.ts +10 -0
  178. package/dist/editor/properties-panel-rows.js +349 -0
  179. package/dist/editor/properties-panel-styles.d.ts +4 -0
  180. package/dist/editor/properties-panel-styles.js +174 -0
  181. package/dist/editor/properties-panel.d.ts +4 -0
  182. package/dist/editor/properties-panel.js +148 -0
  183. package/dist/editor/property-registry.d.ts +16 -0
  184. package/dist/editor/property-registry.js +303 -0
  185. package/dist/editor/standalone-file-panel.d.ts +0 -0
  186. package/dist/editor/standalone-file-panel.js +1 -0
  187. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  188. package/dist/editor/standalone-overlay-dom.js +1 -0
  189. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  190. package/dist/editor/standalone-overlay-styles.js +1 -0
  191. package/dist/editor/standalone-overlay.d.ts +1 -0
  192. package/dist/editor/standalone-overlay.js +76 -0
  193. package/dist/editor/syntax-highlighter.d.ts +4 -0
  194. package/dist/editor/syntax-highlighter.js +81 -0
  195. package/dist/editor/text-toolbar.d.ts +11 -0
  196. package/dist/editor/text-toolbar.js +327 -0
  197. package/dist/editor/toolbar-styles.d.ts +4 -0
  198. package/dist/editor/toolbar-styles.js +198 -0
  199. package/dist/email/index.d.ts +32 -0
  200. package/dist/email/index.js +154 -0
  201. package/dist/email/providers/resend.d.ts +2 -0
  202. package/dist/email/providers/resend.js +24 -0
  203. package/dist/email/providers/sendgrid.d.ts +2 -0
  204. package/dist/email/providers/sendgrid.js +31 -0
  205. package/dist/email/providers/smtp.d.ts +13 -0
  206. package/dist/email/providers/smtp.js +125 -0
  207. package/dist/email/template-engine.d.ts +18 -0
  208. package/dist/email/template-engine.js +116 -0
  209. package/dist/email/templates/base.d.ts +9 -0
  210. package/dist/email/templates/base.js +65 -0
  211. package/dist/email/templates/password-reset.d.ts +5 -0
  212. package/dist/email/templates/password-reset.js +15 -0
  213. package/dist/email/templates/verify-email.d.ts +5 -0
  214. package/dist/email/templates/verify-email.js +15 -0
  215. package/dist/email/templates/welcome.d.ts +5 -0
  216. package/dist/email/templates/welcome.js +13 -0
  217. package/dist/email/types.d.ts +49 -0
  218. package/dist/email/types.js +1 -0
  219. package/dist/llms/generate.d.ts +46 -0
  220. package/dist/llms/generate.js +185 -0
  221. package/dist/permissions/guard.d.ts +28 -0
  222. package/dist/permissions/guard.js +30 -0
  223. package/dist/permissions/index.d.ts +6 -0
  224. package/dist/permissions/index.js +3 -0
  225. package/dist/permissions/service.d.ts +80 -0
  226. package/dist/permissions/service.js +210 -0
  227. package/dist/permissions/tables.d.ts +5 -0
  228. package/dist/permissions/tables.js +68 -0
  229. package/dist/permissions/types.d.ts +33 -0
  230. package/dist/permissions/types.js +1 -0
  231. package/dist/runtime/app-shell.js +163 -0
  232. package/dist/runtime/auth.d.ts +10 -0
  233. package/dist/runtime/auth.js +30 -0
  234. package/dist/runtime/communication.d.ts +137 -0
  235. package/dist/runtime/communication.js +228 -0
  236. package/dist/runtime/error-boundary.d.ts +23 -0
  237. package/dist/runtime/error-boundary.js +120 -0
  238. package/dist/runtime/i18n.d.ts +6 -1
  239. package/dist/runtime/i18n.js +42 -21
  240. package/dist/runtime/router-data.d.ts +3 -0
  241. package/dist/runtime/router-data.js +102 -17
  242. package/dist/runtime/router-hydration.js +25 -0
  243. package/dist/runtime/router.d.ts +16 -1
  244. package/dist/runtime/router.js +188 -42
  245. package/dist/runtime/socket-client.d.ts +2 -0
  246. package/dist/runtime/socket-client.js +30 -0
  247. package/dist/runtime/webrtc.d.ts +47 -0
  248. package/dist/runtime/webrtc.js +178 -0
  249. package/dist/shared/graceful-shutdown.d.ts +8 -0
  250. package/dist/shared/graceful-shutdown.js +36 -0
  251. package/dist/shared/health.d.ts +8 -0
  252. package/dist/shared/health.js +25 -0
  253. package/dist/shared/llms-txt.d.ts +31 -0
  254. package/dist/shared/llms-txt.js +85 -0
  255. package/dist/shared/logger.d.ts +32 -0
  256. package/dist/shared/logger.js +93 -0
  257. package/dist/shared/meta.d.ts +27 -0
  258. package/dist/shared/meta.js +71 -0
  259. package/dist/shared/middleware-runner.d.ts +9 -0
  260. package/dist/shared/middleware-runner.js +29 -0
  261. package/dist/shared/rate-limit.d.ts +18 -0
  262. package/dist/shared/rate-limit.js +71 -0
  263. package/dist/shared/request-id.d.ts +5 -0
  264. package/dist/shared/request-id.js +18 -0
  265. package/dist/shared/route-matching.js +16 -1
  266. package/dist/shared/security-headers.d.ts +18 -0
  267. package/dist/shared/security-headers.js +38 -0
  268. package/dist/shared/socket-io-setup.d.ts +11 -0
  269. package/dist/shared/socket-io-setup.js +51 -0
  270. package/dist/shared/types.d.ts +14 -0
  271. package/dist/shared/utils.d.ts +33 -7
  272. package/dist/shared/utils.js +164 -27
  273. package/dist/storage/adapters/local.d.ts +44 -0
  274. package/dist/storage/adapters/local.js +85 -0
  275. package/dist/storage/adapters/s3.d.ts +32 -0
  276. package/dist/storage/adapters/s3.js +116 -0
  277. package/dist/storage/adapters/types.d.ts +53 -0
  278. package/dist/storage/adapters/types.js +1 -0
  279. package/dist/storage/index.d.ts +76 -0
  280. package/dist/storage/index.js +83 -0
  281. package/package.json +19 -7
  282. package/templates/blog/api/posts.ts +4 -18
  283. package/templates/blog/data/migrations/001_init.sql +6 -5
  284. package/templates/blog/lumenjs.config.ts +3 -0
  285. package/templates/blog/package.json +14 -0
  286. package/templates/blog/pages/_layout.ts +25 -0
  287. package/templates/blog/pages/index.ts +48 -22
  288. package/templates/blog/pages/posts/[slug].ts +45 -20
  289. package/templates/blog/pages/tag/[tag].ts +44 -0
  290. package/templates/dashboard/api/stats.ts +8 -5
  291. package/templates/dashboard/lumenjs.config.ts +3 -0
  292. package/templates/dashboard/package.json +14 -0
  293. package/templates/dashboard/pages/_layout.ts +25 -0
  294. package/templates/dashboard/pages/index.ts +54 -23
  295. package/templates/dashboard/pages/settings/index.ts +29 -0
  296. package/templates/default/lumenjs.config.ts +3 -0
  297. package/templates/default/package.json +14 -0
  298. package/templates/default/pages/index.ts +24 -0
  299. package/templates/social/api/posts/[id].ts +14 -0
  300. package/templates/social/api/posts.ts +11 -0
  301. package/templates/social/api/profile/[username].ts +10 -0
  302. package/templates/social/api/upload.ts +19 -0
  303. package/templates/social/data/migrations/001_init.sql +78 -0
  304. package/templates/social/data/migrations/002_add_image_url.sql +1 -0
  305. package/templates/social/data/migrations/003_auth.sql +7 -0
  306. package/templates/social/docs/architecture.md +76 -0
  307. package/templates/social/docs/components.md +100 -0
  308. package/templates/social/docs/data.md +89 -0
  309. package/templates/social/docs/pages.md +96 -0
  310. package/templates/social/docs/theming.md +52 -0
  311. package/templates/social/lib/media.ts +130 -0
  312. package/templates/social/lumenjs.auth.ts +21 -0
  313. package/templates/social/lumenjs.config.ts +3 -0
  314. package/templates/social/package.json +5 -0
  315. package/templates/social/pages/_layout.ts +239 -0
  316. package/templates/social/pages/apps/[id].ts +173 -0
  317. package/templates/social/pages/apps/index.ts +116 -0
  318. package/templates/social/pages/auth/login.ts +92 -0
  319. package/templates/social/pages/bookmarks.ts +57 -0
  320. package/templates/social/pages/explore.ts +73 -0
  321. package/templates/social/pages/index.ts +351 -0
  322. package/templates/social/pages/messages.ts +298 -0
  323. package/templates/social/pages/new.ts +77 -0
  324. package/templates/social/pages/notifications.ts +73 -0
  325. package/templates/social/pages/post/[id].ts +124 -0
  326. package/templates/social/pages/profile/[username].ts +100 -0
  327. package/templates/social/pages/settings/accessibility.ts +153 -0
  328. package/templates/social/pages/settings/account.ts +260 -0
  329. package/templates/social/pages/settings/help.ts +141 -0
  330. package/templates/social/pages/settings/language.ts +103 -0
  331. package/templates/social/pages/settings/privacy.ts +183 -0
  332. package/templates/social/pages/settings/security.ts +133 -0
  333. package/templates/social/pages/settings.ts +185 -0
@@ -0,0 +1,31 @@
1
+ interface LlmsTxtPage {
2
+ path: string;
3
+ hasLoader: boolean;
4
+ hasSubscribe: boolean;
5
+ hasSocket: boolean;
6
+ }
7
+ interface LlmsTxtApiRoute {
8
+ path: string;
9
+ methods: string[];
10
+ }
11
+ interface LlmsTxtConfig {
12
+ title: string;
13
+ integrations: string[];
14
+ i18n?: {
15
+ locales: string[];
16
+ defaultLocale: string;
17
+ };
18
+ }
19
+ /**
20
+ * Generate the /llms.txt content for a LumenJS project.
21
+ */
22
+ export declare function generateLlmsTxt(options: {
23
+ pages: LlmsTxtPage[];
24
+ apiRoutes: LlmsTxtApiRoute[];
25
+ config: LlmsTxtConfig;
26
+ }): string;
27
+ /**
28
+ * Detect exported HTTP methods from an API route file.
29
+ */
30
+ export declare function detectApiMethods(filePath: string): string[];
31
+ export {};
@@ -0,0 +1,85 @@
1
+ import fs from 'fs';
2
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
3
+ /**
4
+ * Generate the /llms.txt content for a LumenJS project.
5
+ */
6
+ export function generateLlmsTxt(options) {
7
+ const { pages, apiRoutes, config } = options;
8
+ const lines = [];
9
+ lines.push(`# ${config.title}`);
10
+ lines.push('');
11
+ lines.push('> Built with LumenJS');
12
+ lines.push('');
13
+ // Pages section
14
+ if (pages.length > 0) {
15
+ lines.push('## Pages');
16
+ lines.push('');
17
+ for (const page of pages) {
18
+ lines.push(`### ${page.path}`);
19
+ const traits = [];
20
+ if (page.hasLoader)
21
+ traits.push('server loader');
22
+ if (page.hasSubscribe)
23
+ traits.push('live data (SSE)');
24
+ if (page.hasSocket)
25
+ traits.push('socket (bidirectional)');
26
+ if (page.path.includes(':'))
27
+ traits.push('dynamic route');
28
+ if (traits.length > 0) {
29
+ lines.push(`- ${traits.join(', ')}`);
30
+ }
31
+ lines.push('');
32
+ }
33
+ }
34
+ // API routes section
35
+ if (apiRoutes.length > 0) {
36
+ lines.push('## API Routes');
37
+ lines.push('');
38
+ for (const route of apiRoutes) {
39
+ for (const method of route.methods) {
40
+ const fullPath = route.path.startsWith('/api') ? route.path : `/api${route.path}`;
41
+ lines.push(`- ${method} ${fullPath}`);
42
+ }
43
+ }
44
+ lines.push('');
45
+ }
46
+ // Features section
47
+ const features = [];
48
+ if (config.i18n) {
49
+ features.push(`Internationalization (${config.i18n.locales.join(', ')})`);
50
+ }
51
+ if (config.integrations.includes('tailwind'))
52
+ features.push('Tailwind CSS');
53
+ if (config.integrations.includes('nuralyui'))
54
+ features.push('NuralyUI Components');
55
+ if (config.integrations.includes('socketio'))
56
+ features.push('Socket.IO');
57
+ if (features.length > 0) {
58
+ lines.push('## Features');
59
+ lines.push('');
60
+ for (const f of features) {
61
+ lines.push(`- ${f}`);
62
+ }
63
+ lines.push('');
64
+ }
65
+ return lines.join('\n');
66
+ }
67
+ /**
68
+ * Detect exported HTTP methods from an API route file.
69
+ */
70
+ export function detectApiMethods(filePath) {
71
+ try {
72
+ const content = fs.readFileSync(filePath, 'utf-8');
73
+ const methods = [];
74
+ for (const method of HTTP_METHODS) {
75
+ const regex = new RegExp(`export\\s+(async\\s+)?function\\s+${method}\\s*\\(`);
76
+ if (regex.test(content)) {
77
+ methods.push(method);
78
+ }
79
+ }
80
+ return methods;
81
+ }
82
+ catch {
83
+ return [];
84
+ }
85
+ }
@@ -0,0 +1,32 @@
1
+ import { IncomingMessage } from 'http';
2
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
3
+ export interface LogEntry {
4
+ level: LogLevel;
5
+ msg: string;
6
+ timestamp: string;
7
+ [key: string]: unknown;
8
+ }
9
+ export interface LoggerOptions {
10
+ level?: LogLevel;
11
+ json?: boolean;
12
+ }
13
+ export declare function configureLogger(opts: LoggerOptions): void;
14
+ export declare const logger: {
15
+ debug: (msg: string, extra?: Record<string, unknown>) => void;
16
+ info: (msg: string, extra?: Record<string, unknown>) => void;
17
+ warn: (msg: string, extra?: Record<string, unknown>) => void;
18
+ error: (msg: string, extra?: Record<string, unknown>) => void;
19
+ fatal: (msg: string, extra?: Record<string, unknown>) => void;
20
+ /** Create a child logger with preset extra fields (e.g. requestId). */
21
+ child(fields: Record<string, unknown>): {
22
+ debug: (msg: string, extra?: Record<string, unknown>) => void;
23
+ info: (msg: string, extra?: Record<string, unknown>) => void;
24
+ warn: (msg: string, extra?: Record<string, unknown>) => void;
25
+ error: (msg: string, extra?: Record<string, unknown>) => void;
26
+ fatal: (msg: string, extra?: Record<string, unknown>) => void;
27
+ };
28
+ /** Log an HTTP request completion. */
29
+ request(req: IncomingMessage, statusCode: number, durationMs: number, extra?: Record<string, unknown>): void;
30
+ };
31
+ /** Auto-configure from environment. Call once at startup. */
32
+ export declare function initLogger(): void;
@@ -0,0 +1,93 @@
1
+ const LEVEL_VALUES = {
2
+ debug: 0,
3
+ info: 1,
4
+ warn: 2,
5
+ error: 3,
6
+ fatal: 4,
7
+ };
8
+ const LEVEL_COLORS = {
9
+ debug: '\x1b[36m', // cyan
10
+ info: '\x1b[32m', // green
11
+ warn: '\x1b[33m', // yellow
12
+ error: '\x1b[31m', // red
13
+ fatal: '\x1b[35m', // magenta
14
+ };
15
+ const RESET = '\x1b[0m';
16
+ const DIM = '\x1b[2m';
17
+ let globalLevel = 'info';
18
+ let globalJson = false;
19
+ export function configureLogger(opts) {
20
+ if (opts.level)
21
+ globalLevel = opts.level;
22
+ if (opts.json !== undefined)
23
+ globalJson = opts.json;
24
+ }
25
+ function shouldLog(level) {
26
+ return LEVEL_VALUES[level] >= LEVEL_VALUES[globalLevel];
27
+ }
28
+ function formatJson(entry) {
29
+ return JSON.stringify(entry);
30
+ }
31
+ function formatPretty(entry) {
32
+ const { level, msg, timestamp, ...extra } = entry;
33
+ const color = LEVEL_COLORS[level];
34
+ const ts = `${DIM}${timestamp}${RESET}`;
35
+ const lvl = `${color}${level.toUpperCase().padEnd(5)}${RESET}`;
36
+ const extraStr = Object.keys(extra).length > 0
37
+ ? ` ${DIM}${JSON.stringify(extra)}${RESET}`
38
+ : '';
39
+ return `${ts} ${lvl} ${msg}${extraStr}`;
40
+ }
41
+ function log(level, msg, extra) {
42
+ if (!shouldLog(level))
43
+ return;
44
+ const entry = {
45
+ level,
46
+ msg,
47
+ timestamp: new Date().toISOString(),
48
+ ...extra,
49
+ };
50
+ const formatted = globalJson ? formatJson(entry) : formatPretty(entry);
51
+ if (level === 'error' || level === 'fatal') {
52
+ process.stderr.write(formatted + '\n');
53
+ }
54
+ else {
55
+ process.stdout.write(formatted + '\n');
56
+ }
57
+ }
58
+ export const logger = {
59
+ debug: (msg, extra) => log('debug', msg, extra),
60
+ info: (msg, extra) => log('info', msg, extra),
61
+ warn: (msg, extra) => log('warn', msg, extra),
62
+ error: (msg, extra) => log('error', msg, extra),
63
+ fatal: (msg, extra) => log('fatal', msg, extra),
64
+ /** Create a child logger with preset extra fields (e.g. requestId). */
65
+ child(fields) {
66
+ return {
67
+ debug: (msg, extra) => log('debug', msg, { ...fields, ...extra }),
68
+ info: (msg, extra) => log('info', msg, { ...fields, ...extra }),
69
+ warn: (msg, extra) => log('warn', msg, { ...fields, ...extra }),
70
+ error: (msg, extra) => log('error', msg, { ...fields, ...extra }),
71
+ fatal: (msg, extra) => log('fatal', msg, { ...fields, ...extra }),
72
+ };
73
+ },
74
+ /** Log an HTTP request completion. */
75
+ request(req, statusCode, durationMs, extra) {
76
+ const level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
77
+ log(level, `${req.method} ${req.url} ${statusCode} ${durationMs}ms`, {
78
+ method: req.method,
79
+ url: req.url,
80
+ status: statusCode,
81
+ duration: durationMs,
82
+ ...extra,
83
+ });
84
+ },
85
+ };
86
+ /** Auto-configure from environment. Call once at startup. */
87
+ export function initLogger() {
88
+ const isProd = process.env.NODE_ENV === 'production';
89
+ configureLogger({
90
+ level: process.env.LOG_LEVEL || (isProd ? 'info' : 'debug'),
91
+ json: process.env.LOG_FORMAT === 'json' || isProd,
92
+ });
93
+ }
@@ -0,0 +1,27 @@
1
+ export interface PageMeta {
2
+ title?: string;
3
+ description?: string;
4
+ image?: string;
5
+ canonical?: string;
6
+ robots?: string;
7
+ type?: string;
8
+ }
9
+ export interface MetaTagOptions {
10
+ siteTitle?: string;
11
+ url?: string;
12
+ locale?: string;
13
+ i18nConfig?: {
14
+ locales: string[];
15
+ defaultLocale: string;
16
+ prefixDefault: boolean;
17
+ };
18
+ }
19
+ /**
20
+ * Generate HTML meta tags from a PageMeta object.
21
+ * Returns a string of HTML tags to inject into <head>.
22
+ */
23
+ export declare function generateMetaTags(meta: PageMeta, options?: MetaTagOptions): string;
24
+ /**
25
+ * Compute the full page title with optional site title suffix.
26
+ */
27
+ export declare function computeTitle(meta: PageMeta | undefined, siteTitle: string): string;
@@ -0,0 +1,71 @@
1
+ import { escapeHtml } from './utils.js';
2
+ /**
3
+ * Generate HTML meta tags from a PageMeta object.
4
+ * Returns a string of HTML tags to inject into <head>.
5
+ */
6
+ export function generateMetaTags(meta, options) {
7
+ const tags = [];
8
+ const ogType = meta.type || 'website';
9
+ // og:type is always emitted
10
+ tags.push(`<meta property="og:type" content="${escapeHtml(ogType)}">`);
11
+ if (meta.description) {
12
+ tags.push(`<meta name="description" content="${escapeHtml(meta.description)}">`);
13
+ tags.push(`<meta property="og:description" content="${escapeHtml(meta.description)}">`);
14
+ }
15
+ if (meta.title) {
16
+ const fullTitle = options?.siteTitle ? `${meta.title} | ${options.siteTitle}` : meta.title;
17
+ tags.push(`<meta property="og:title" content="${escapeHtml(fullTitle)}">`);
18
+ }
19
+ if (meta.image) {
20
+ tags.push(`<meta property="og:image" content="${escapeHtml(meta.image)}">`);
21
+ tags.push(`<meta name="twitter:card" content="summary_large_image">`);
22
+ tags.push(`<meta name="twitter:image" content="${escapeHtml(meta.image)}">`);
23
+ }
24
+ if (meta.robots) {
25
+ tags.push(`<meta name="robots" content="${escapeHtml(meta.robots)}">`);
26
+ }
27
+ if (options?.url) {
28
+ tags.push(`<meta property="og:url" content="${escapeHtml(options.url)}">`);
29
+ }
30
+ if (options?.locale) {
31
+ tags.push(`<meta property="og:locale" content="${escapeHtml(options.locale)}">`);
32
+ }
33
+ // Canonical URL
34
+ const canonicalUrl = meta.canonical || options?.url;
35
+ if (canonicalUrl) {
36
+ tags.push(`<link rel="canonical" href="${escapeHtml(canonicalUrl)}">`);
37
+ }
38
+ // hreflang tags for i18n
39
+ if (options?.i18nConfig && options.url) {
40
+ const { locales, defaultLocale, prefixDefault } = options.i18nConfig;
41
+ // Strip any existing locale prefix to get the base path
42
+ let basePath = options.url;
43
+ for (const loc of locales) {
44
+ if (basePath.startsWith(`/${loc}/`) || basePath === `/${loc}`) {
45
+ basePath = basePath.slice(loc.length + 1) || '/';
46
+ break;
47
+ }
48
+ }
49
+ for (const loc of locales) {
50
+ const href = (loc === defaultLocale && !prefixDefault)
51
+ ? basePath
52
+ : `/${loc}${basePath === '/' ? '' : basePath}`;
53
+ tags.push(`<link rel="alternate" hreflang="${escapeHtml(loc)}" href="${escapeHtml(href)}">`);
54
+ }
55
+ // x-default points to the default locale URL
56
+ const xDefaultHref = prefixDefault
57
+ ? `/${defaultLocale}${basePath === '/' ? '' : basePath}`
58
+ : basePath;
59
+ tags.push(`<link rel="alternate" hreflang="x-default" href="${escapeHtml(xDefaultHref)}">`);
60
+ }
61
+ return tags.join('\n ');
62
+ }
63
+ /**
64
+ * Compute the full page title with optional site title suffix.
65
+ */
66
+ export function computeTitle(meta, siteTitle) {
67
+ if (meta?.title) {
68
+ return `${meta.title} | ${siteTitle}`;
69
+ }
70
+ return siteTitle;
71
+ }
@@ -0,0 +1,9 @@
1
+ export type ConnectMiddleware = (req: any, res: any, next: (err?: any) => void) => void;
2
+ /**
3
+ * Chain Connect-style (req, res, next) middleware functions sequentially.
4
+ */
5
+ export declare function runMiddlewareChain(middlewares: ConnectMiddleware[], req: any, res: any, done: (err?: any) => void): void;
6
+ /**
7
+ * Validate and extract middleware array from a module's default export.
8
+ */
9
+ export declare function extractMiddleware(mod: any): ConnectMiddleware[];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Chain Connect-style (req, res, next) middleware functions sequentially.
3
+ */
4
+ export function runMiddlewareChain(middlewares, req, res, done) {
5
+ let index = 0;
6
+ function next(err) {
7
+ if (err)
8
+ return done(err);
9
+ if (index >= middlewares.length)
10
+ return done();
11
+ const mw = middlewares[index++];
12
+ try {
13
+ mw(req, res, next);
14
+ }
15
+ catch (e) {
16
+ done(e);
17
+ }
18
+ }
19
+ next();
20
+ }
21
+ /**
22
+ * Validate and extract middleware array from a module's default export.
23
+ */
24
+ export function extractMiddleware(mod) {
25
+ const arr = mod?.default ?? mod;
26
+ if (!Array.isArray(arr))
27
+ return [];
28
+ return arr.filter((fn) => typeof fn === 'function');
29
+ }
@@ -0,0 +1,18 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ export interface RateLimitConfig {
3
+ /** Time window in milliseconds. Default: 60000 (1 minute). */
4
+ windowMs?: number;
5
+ /** Max requests per window. Default: 100. */
6
+ max?: number;
7
+ /** Custom key generator. Default: IP address. */
8
+ keyGenerator?: (req: IncomingMessage) => string;
9
+ /** Paths to skip rate limiting. Default: static assets. */
10
+ skip?: (req: IncomingMessage) => boolean;
11
+ /** HTTP status code when rate limited. Default: 429. */
12
+ statusCode?: number;
13
+ /** Message sent when rate limited. */
14
+ message?: string;
15
+ }
16
+ export declare function createRateLimiter(config?: RateLimitConfig): (req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => void;
17
+ /** Stricter rate limiter preset for auth endpoints (20 req/min). */
18
+ export declare function createAuthRateLimiter(overrides?: Partial<RateLimitConfig>): (req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => void;
@@ -0,0 +1,71 @@
1
+ const DEFAULT_CONFIG = {
2
+ windowMs: 60_000,
3
+ max: 2000,
4
+ keyGenerator: (req) => {
5
+ // Uses X-Forwarded-For when behind a trusted reverse proxy.
6
+ // If directly internet-facing, override keyGenerator to use req.socket.remoteAddress only.
7
+ return req.headers['x-forwarded-for']?.split(',')[0]?.trim()
8
+ || req.socket.remoteAddress
9
+ || 'unknown';
10
+ },
11
+ skip: (req) => {
12
+ const url = req.url || '';
13
+ return url.includes('.') && !url.startsWith('/api/');
14
+ },
15
+ statusCode: 429,
16
+ message: 'Too many requests, please try again later.',
17
+ };
18
+ export function createRateLimiter(config) {
19
+ const opts = { ...DEFAULT_CONFIG, ...config };
20
+ const buckets = new Map();
21
+ const refillRate = opts.max / opts.windowMs; // tokens per ms
22
+ // Cleanup stale entries every 5 minutes
23
+ const cleanupInterval = setInterval(() => {
24
+ const now = Date.now();
25
+ for (const [key, bucket] of buckets) {
26
+ if (now - bucket.lastRefill > opts.windowMs * 2) {
27
+ buckets.delete(key);
28
+ }
29
+ }
30
+ }, 5 * 60_000);
31
+ cleanupInterval.unref();
32
+ return (req, res, next) => {
33
+ if (opts.skip(req))
34
+ return next();
35
+ const key = opts.keyGenerator(req);
36
+ const now = Date.now();
37
+ let bucket = buckets.get(key);
38
+ if (!bucket) {
39
+ bucket = { tokens: opts.max, lastRefill: now };
40
+ buckets.set(key, bucket);
41
+ }
42
+ // Refill tokens based on elapsed time
43
+ const elapsed = now - bucket.lastRefill;
44
+ bucket.tokens = Math.min(opts.max, bucket.tokens + elapsed * refillRate);
45
+ bucket.lastRefill = now;
46
+ if (bucket.tokens < 1) {
47
+ const retryAfter = Math.ceil((1 - bucket.tokens) / refillRate / 1000);
48
+ res.writeHead(opts.statusCode, {
49
+ 'Content-Type': 'application/json',
50
+ 'Retry-After': String(retryAfter),
51
+ 'X-RateLimit-Limit': String(opts.max),
52
+ 'X-RateLimit-Remaining': '0',
53
+ });
54
+ res.end(JSON.stringify({ error: opts.message }));
55
+ return;
56
+ }
57
+ bucket.tokens -= 1;
58
+ res.setHeader('X-RateLimit-Limit', String(opts.max));
59
+ res.setHeader('X-RateLimit-Remaining', String(Math.floor(bucket.tokens)));
60
+ next();
61
+ };
62
+ }
63
+ /** Stricter rate limiter preset for auth endpoints (20 req/min). */
64
+ export function createAuthRateLimiter(overrides) {
65
+ return createRateLimiter({
66
+ windowMs: 60_000,
67
+ max: 20,
68
+ skip: () => false,
69
+ ...overrides,
70
+ });
71
+ }
@@ -0,0 +1,5 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ /** Attach a unique request ID to each request. Reuses incoming X-Request-ID if present. */
3
+ export declare function createRequestIdMiddleware(): (req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => void;
4
+ /** Get the request ID from a request object. */
5
+ export declare function getRequestId(req: IncomingMessage): string | undefined;
@@ -0,0 +1,18 @@
1
+ import { randomUUID } from 'crypto';
2
+ const REQUEST_ID_HEADER = 'x-request-id';
3
+ /** Attach a unique request ID to each request. Reuses incoming X-Request-ID if present. */
4
+ export function createRequestIdMiddleware() {
5
+ return (req, res, next) => {
6
+ const existing = req.headers[REQUEST_ID_HEADER];
7
+ const requestId = (typeof existing === 'string' && existing) || randomUUID();
8
+ // Attach to request for downstream use
9
+ req.id = requestId;
10
+ // Echo back in response
11
+ res.setHeader('X-Request-ID', requestId);
12
+ next();
13
+ };
14
+ }
15
+ /** Get the request ID from a request object. */
16
+ export function getRequestId(req) {
17
+ return req.id;
18
+ }
@@ -1,11 +1,26 @@
1
+ function routeSpecificity(route) {
2
+ // Higher score = more specific = checked first.
3
+ // Each static segment adds 2, each dynamic segment adds 1, catch-all adds 0.
4
+ return route.path.replace(/^\//, '').split('/').filter(Boolean).reduce((score, seg) => {
5
+ if (seg.startsWith(':...'))
6
+ return score + 0;
7
+ if (seg.startsWith(':'))
8
+ return score + 1;
9
+ return score + 2;
10
+ }, 0);
11
+ }
1
12
  export function matchRoute(routes, pathname) {
2
13
  const urlSegments = pathname.replace(/^\//, '').split('/').filter(Boolean);
3
- for (const route of routes) {
14
+ const sorted = [...routes].sort((a, b) => routeSpecificity(b) - routeSpecificity(a));
15
+ for (const route of sorted) {
4
16
  const routeSegments = route.path.replace(/^\//, '').split('/').filter(Boolean);
5
17
  // Handle root route
6
18
  if (route.path === '/' && (pathname === '/' || pathname === '')) {
7
19
  return { route, params: {} };
8
20
  }
21
+ // Non-root routes with no segments can't match a non-empty pathname
22
+ if (routeSegments.length === 0 && urlSegments.length > 0)
23
+ continue;
9
24
  const params = {};
10
25
  let match = true;
11
26
  for (let i = 0; i < routeSegments.length; i++) {
@@ -0,0 +1,18 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ export interface SecurityHeadersConfig {
3
+ /** Content-Security-Policy. Set to false to disable. */
4
+ contentSecurityPolicy?: string | false;
5
+ /** X-Frame-Options. Default: 'DENY'. Set to false to disable. */
6
+ frameOptions?: 'DENY' | 'SAMEORIGIN' | false;
7
+ /** X-Content-Type-Options: nosniff. Default: true. */
8
+ noSniff?: boolean;
9
+ /** Strict-Transport-Security. Default: 'max-age=31536000; includeSubDomains'. Set to false to disable. */
10
+ hsts?: string | false;
11
+ /** Referrer-Policy. Default: 'strict-origin-when-cross-origin'. Set to false to disable. */
12
+ referrerPolicy?: string | false;
13
+ /** Permissions-Policy. Default restricts camera, microphone, geolocation. Set to false to disable. */
14
+ permissionsPolicy?: string | false;
15
+ /** Cross-Origin-Opener-Policy. Default: 'same-origin'. Set to false to disable. */
16
+ crossOriginOpenerPolicy?: string | false;
17
+ }
18
+ export declare function createSecurityHeadersMiddleware(config?: SecurityHeadersConfig): (_req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => void;
@@ -0,0 +1,38 @@
1
+ const DEFAULTS = {
2
+ // Note: 'unsafe-inline' for script-src is required for Lit's template system.
3
+ // Override via securityHeaders config in lumenjs.config.ts for stricter policies.
4
+ contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: blob: https:; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self' ws: wss:; frame-ancestors 'none'",
5
+ frameOptions: 'DENY',
6
+ noSniff: true,
7
+ hsts: 'max-age=31536000; includeSubDomains',
8
+ referrerPolicy: 'strict-origin-when-cross-origin',
9
+ permissionsPolicy: 'camera=(), microphone=(), geolocation=()',
10
+ crossOriginOpenerPolicy: 'same-origin',
11
+ };
12
+ export function createSecurityHeadersMiddleware(config) {
13
+ const opts = { ...DEFAULTS, ...config };
14
+ return (_req, res, next) => {
15
+ if (opts.contentSecurityPolicy !== false) {
16
+ res.setHeader('Content-Security-Policy', opts.contentSecurityPolicy);
17
+ }
18
+ if (opts.frameOptions !== false) {
19
+ res.setHeader('X-Frame-Options', opts.frameOptions);
20
+ }
21
+ if (opts.noSniff) {
22
+ res.setHeader('X-Content-Type-Options', 'nosniff');
23
+ }
24
+ if (opts.hsts !== false) {
25
+ res.setHeader('Strict-Transport-Security', opts.hsts);
26
+ }
27
+ if (opts.referrerPolicy !== false) {
28
+ res.setHeader('Referrer-Policy', opts.referrerPolicy);
29
+ }
30
+ if (opts.permissionsPolicy !== false) {
31
+ res.setHeader('Permissions-Policy', opts.permissionsPolicy);
32
+ }
33
+ if (opts.crossOriginOpenerPolicy !== false) {
34
+ res.setHeader('Cross-Origin-Opener-Policy', opts.crossOriginOpenerPolicy);
35
+ }
36
+ next();
37
+ };
38
+ }
@@ -0,0 +1,11 @@
1
+ export interface SocketRoute {
2
+ path: string;
3
+ hasSocket: boolean;
4
+ filePath: string;
5
+ }
6
+ export declare function setupSocketIO(options: {
7
+ httpServer: any;
8
+ loadModule: (filePath: string) => Promise<any>;
9
+ routes: SocketRoute[];
10
+ projectDir?: string;
11
+ }): Promise<any>;
@@ -0,0 +1,51 @@
1
+ import { createRequire } from 'module';
2
+ import { pathToFileURL } from 'url';
3
+ export async function setupSocketIO(options) {
4
+ // Resolve socket.io from the project's node_modules (not from lumenjs's own node_modules)
5
+ let SocketIOServer;
6
+ if (options.projectDir) {
7
+ const require = createRequire(pathToFileURL(options.projectDir + '/package.json').href);
8
+ const socketIo = require('socket.io');
9
+ SocketIOServer = socketIo.Server || socketIo.default?.Server || socketIo;
10
+ }
11
+ else {
12
+ const socketIo = await import('socket.io');
13
+ SocketIOServer = socketIo.Server;
14
+ }
15
+ const io = new SocketIOServer(options.httpServer, {
16
+ path: '/__nk_socketio/',
17
+ cors: { origin: process.env.NODE_ENV === 'production' ? false : '*' },
18
+ });
19
+ for (const route of options.routes) {
20
+ if (!route.hasSocket)
21
+ continue;
22
+ const ns = `/nk${route.path === '/' ? '/index' : route.path}`;
23
+ io.of(ns).on('connection', async (socket) => {
24
+ try {
25
+ const mod = await options.loadModule(route.filePath);
26
+ if (!mod.socket)
27
+ return;
28
+ const push = (data) => socket.emit('nk:data', data);
29
+ const on = (event, handler) => {
30
+ socket.on(`nk:${event}`, handler);
31
+ };
32
+ const room = {
33
+ join: (name) => socket.join(name),
34
+ leave: (name) => socket.leave(name),
35
+ broadcast: (name, data) => socket.to(name).emit('nk:data', data),
36
+ broadcastAll: (name, data) => io.of(ns).to(name).emit('nk:data', data),
37
+ };
38
+ const params = socket.handshake.query.__params
39
+ ? JSON.parse(socket.handshake.query.__params) : {};
40
+ const locale = socket.handshake.query.__locale;
41
+ const cleanup = mod.socket({ on, push, room, params, headers: socket.handshake.headers, locale, socket });
42
+ socket.on('disconnect', () => { if (typeof cleanup === 'function')
43
+ cleanup(); });
44
+ }
45
+ catch (err) {
46
+ console.error(`[LumenJS] Socket handler error for ${route.path}:`, err);
47
+ }
48
+ });
49
+ }
50
+ return io;
51
+ }