@nuraly/lumenjs 0.1.2 → 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 (337) hide show
  1. package/README.md +76 -235
  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 +52 -120
  49. package/dist/build/scan.d.ts +19 -0
  50. package/dist/build/scan.js +77 -6
  51. package/dist/build/serve-api.js +8 -2
  52. package/dist/build/serve-loaders.d.ts +4 -2
  53. package/dist/build/serve-loaders.js +128 -10
  54. package/dist/build/serve-ssr.js +38 -11
  55. package/dist/build/serve-static.js +3 -3
  56. package/dist/build/serve.js +229 -14
  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/context.d.ts +2 -0
  93. package/dist/db/context.js +9 -0
  94. package/dist/db/index.d.ts +23 -0
  95. package/dist/db/index.js +258 -0
  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 +14 -0
  101. package/dist/dev-server/config.js +26 -9
  102. package/dist/dev-server/index-html.d.ts +3 -0
  103. package/dist/dev-server/index-html.js +18 -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.d.ts +0 -1
  122. package/dist/dev-server/plugins/vite-plugin-loaders.js +311 -42
  123. package/dist/dev-server/plugins/vite-plugin-routes.js +18 -6
  124. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  125. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  127. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  128. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  129. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  130. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +111 -2
  131. package/dist/dev-server/server.js +128 -12
  132. package/dist/dev-server/ssr-render.d.ts +2 -1
  133. package/dist/dev-server/ssr-render.js +107 -48
  134. package/dist/editor/ai/backend.d.ts +20 -0
  135. package/dist/editor/ai/backend.js +104 -0
  136. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  137. package/dist/editor/ai/claude-code-client.js +145 -0
  138. package/dist/editor/ai/opencode-client.d.ts +14 -0
  139. package/dist/editor/ai/opencode-client.js +125 -0
  140. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  141. package/dist/editor/ai/snapshot-store.js +35 -0
  142. package/dist/editor/ai/types.d.ts +30 -0
  143. package/dist/editor/ai/types.js +136 -0
  144. package/dist/editor/ai-chat-panel.d.ts +13 -0
  145. package/dist/editor/ai-chat-panel.js +587 -0
  146. package/dist/editor/ai-markdown.d.ts +10 -0
  147. package/dist/editor/ai-markdown.js +70 -0
  148. package/dist/editor/ai-project-panel.d.ts +11 -0
  149. package/dist/editor/ai-project-panel.js +332 -0
  150. package/dist/editor/ast-modification.d.ts +11 -0
  151. package/dist/editor/ast-modification.js +1 -0
  152. package/dist/editor/ast-service.d.ts +30 -0
  153. package/dist/editor/ast-service.js +180 -0
  154. package/dist/editor/css-rules.d.ts +54 -0
  155. package/dist/editor/css-rules.js +423 -0
  156. package/dist/editor/editor-api-client.d.ts +51 -0
  157. package/dist/editor/editor-api-client.js +162 -0
  158. package/dist/editor/editor-bridge.d.ts +1 -0
  159. package/dist/editor/editor-bridge.js +17 -8
  160. package/dist/editor/editor-toolbar.d.ts +14 -0
  161. package/dist/editor/editor-toolbar.js +115 -0
  162. package/dist/editor/file-editor.d.ts +9 -0
  163. package/dist/editor/file-editor.js +236 -0
  164. package/dist/editor/file-service.d.ts +16 -0
  165. package/dist/editor/file-service.js +52 -0
  166. package/dist/editor/i18n-key-gen.d.ts +1 -0
  167. package/dist/editor/i18n-key-gen.js +7 -0
  168. package/dist/editor/inline-text-edit.d.ts +5 -0
  169. package/dist/editor/inline-text-edit.js +173 -92
  170. package/dist/editor/overlay-events.d.ts +5 -0
  171. package/dist/editor/overlay-events.js +364 -0
  172. package/dist/editor/overlay-hmr.d.ts +2 -0
  173. package/dist/editor/overlay-hmr.js +75 -0
  174. package/dist/editor/overlay-selection.d.ts +29 -0
  175. package/dist/editor/overlay-selection.js +148 -0
  176. package/dist/editor/overlay-utils.d.ts +12 -0
  177. package/dist/editor/overlay-utils.js +59 -0
  178. package/dist/editor/properties-panel-persist.d.ts +14 -0
  179. package/dist/editor/properties-panel-persist.js +70 -0
  180. package/dist/editor/properties-panel-rows.d.ts +10 -0
  181. package/dist/editor/properties-panel-rows.js +349 -0
  182. package/dist/editor/properties-panel-styles.d.ts +4 -0
  183. package/dist/editor/properties-panel-styles.js +174 -0
  184. package/dist/editor/properties-panel.d.ts +4 -0
  185. package/dist/editor/properties-panel.js +148 -0
  186. package/dist/editor/property-registry.d.ts +16 -0
  187. package/dist/editor/property-registry.js +303 -0
  188. package/dist/editor/standalone-file-panel.d.ts +0 -0
  189. package/dist/editor/standalone-file-panel.js +1 -0
  190. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  191. package/dist/editor/standalone-overlay-dom.js +1 -0
  192. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  193. package/dist/editor/standalone-overlay-styles.js +1 -0
  194. package/dist/editor/standalone-overlay.d.ts +1 -0
  195. package/dist/editor/standalone-overlay.js +76 -0
  196. package/dist/editor/syntax-highlighter.d.ts +4 -0
  197. package/dist/editor/syntax-highlighter.js +81 -0
  198. package/dist/editor/text-toolbar.d.ts +11 -0
  199. package/dist/editor/text-toolbar.js +327 -0
  200. package/dist/editor/toolbar-styles.d.ts +4 -0
  201. package/dist/editor/toolbar-styles.js +198 -0
  202. package/dist/email/index.d.ts +32 -0
  203. package/dist/email/index.js +154 -0
  204. package/dist/email/providers/resend.d.ts +2 -0
  205. package/dist/email/providers/resend.js +24 -0
  206. package/dist/email/providers/sendgrid.d.ts +2 -0
  207. package/dist/email/providers/sendgrid.js +31 -0
  208. package/dist/email/providers/smtp.d.ts +13 -0
  209. package/dist/email/providers/smtp.js +125 -0
  210. package/dist/email/template-engine.d.ts +18 -0
  211. package/dist/email/template-engine.js +116 -0
  212. package/dist/email/templates/base.d.ts +9 -0
  213. package/dist/email/templates/base.js +65 -0
  214. package/dist/email/templates/password-reset.d.ts +5 -0
  215. package/dist/email/templates/password-reset.js +15 -0
  216. package/dist/email/templates/verify-email.d.ts +5 -0
  217. package/dist/email/templates/verify-email.js +15 -0
  218. package/dist/email/templates/welcome.d.ts +5 -0
  219. package/dist/email/templates/welcome.js +13 -0
  220. package/dist/email/types.d.ts +49 -0
  221. package/dist/email/types.js +1 -0
  222. package/dist/llms/generate.d.ts +46 -0
  223. package/dist/llms/generate.js +185 -0
  224. package/dist/permissions/guard.d.ts +28 -0
  225. package/dist/permissions/guard.js +30 -0
  226. package/dist/permissions/index.d.ts +6 -0
  227. package/dist/permissions/index.js +3 -0
  228. package/dist/permissions/service.d.ts +80 -0
  229. package/dist/permissions/service.js +210 -0
  230. package/dist/permissions/tables.d.ts +5 -0
  231. package/dist/permissions/tables.js +68 -0
  232. package/dist/permissions/types.d.ts +33 -0
  233. package/dist/permissions/types.js +1 -0
  234. package/dist/runtime/app-shell.js +163 -0
  235. package/dist/runtime/auth.d.ts +10 -0
  236. package/dist/runtime/auth.js +30 -0
  237. package/dist/runtime/communication.d.ts +137 -0
  238. package/dist/runtime/communication.js +228 -0
  239. package/dist/runtime/error-boundary.d.ts +23 -0
  240. package/dist/runtime/error-boundary.js +120 -0
  241. package/dist/runtime/i18n.d.ts +6 -1
  242. package/dist/runtime/i18n.js +42 -21
  243. package/dist/runtime/router-data.d.ts +5 -0
  244. package/dist/runtime/router-data.js +121 -16
  245. package/dist/runtime/router-hydration.js +25 -0
  246. package/dist/runtime/router.d.ts +21 -1
  247. package/dist/runtime/router.js +221 -39
  248. package/dist/runtime/socket-client.d.ts +2 -0
  249. package/dist/runtime/socket-client.js +30 -0
  250. package/dist/runtime/webrtc.d.ts +47 -0
  251. package/dist/runtime/webrtc.js +178 -0
  252. package/dist/shared/graceful-shutdown.d.ts +8 -0
  253. package/dist/shared/graceful-shutdown.js +36 -0
  254. package/dist/shared/health.d.ts +8 -0
  255. package/dist/shared/health.js +25 -0
  256. package/dist/shared/llms-txt.d.ts +31 -0
  257. package/dist/shared/llms-txt.js +85 -0
  258. package/dist/shared/logger.d.ts +32 -0
  259. package/dist/shared/logger.js +93 -0
  260. package/dist/shared/meta.d.ts +27 -0
  261. package/dist/shared/meta.js +71 -0
  262. package/dist/shared/middleware-runner.d.ts +9 -0
  263. package/dist/shared/middleware-runner.js +29 -0
  264. package/dist/shared/rate-limit.d.ts +18 -0
  265. package/dist/shared/rate-limit.js +71 -0
  266. package/dist/shared/request-id.d.ts +5 -0
  267. package/dist/shared/request-id.js +18 -0
  268. package/dist/shared/route-matching.js +16 -1
  269. package/dist/shared/security-headers.d.ts +18 -0
  270. package/dist/shared/security-headers.js +38 -0
  271. package/dist/shared/socket-io-setup.d.ts +11 -0
  272. package/dist/shared/socket-io-setup.js +51 -0
  273. package/dist/shared/types.d.ts +16 -0
  274. package/dist/shared/utils.d.ts +37 -7
  275. package/dist/shared/utils.js +175 -26
  276. package/dist/storage/adapters/local.d.ts +44 -0
  277. package/dist/storage/adapters/local.js +85 -0
  278. package/dist/storage/adapters/s3.d.ts +32 -0
  279. package/dist/storage/adapters/s3.js +116 -0
  280. package/dist/storage/adapters/types.d.ts +53 -0
  281. package/dist/storage/adapters/types.js +1 -0
  282. package/dist/storage/index.d.ts +76 -0
  283. package/dist/storage/index.js +83 -0
  284. package/package.json +20 -1
  285. package/templates/blog/api/posts.ts +6 -0
  286. package/templates/blog/data/migrations/001_init.sql +13 -0
  287. package/templates/blog/lumenjs.config.ts +3 -0
  288. package/templates/blog/package.json +14 -0
  289. package/templates/blog/pages/_layout.ts +25 -0
  290. package/templates/blog/pages/index.ts +65 -0
  291. package/templates/blog/pages/posts/[slug].ts +60 -0
  292. package/templates/blog/pages/tag/[tag].ts +44 -0
  293. package/templates/dashboard/api/stats.ts +10 -0
  294. package/templates/dashboard/data/migrations/001_init.sql +13 -0
  295. package/templates/dashboard/lumenjs.config.ts +3 -0
  296. package/templates/dashboard/package.json +14 -0
  297. package/templates/dashboard/pages/_layout.ts +25 -0
  298. package/templates/dashboard/pages/index.ts +72 -0
  299. package/templates/dashboard/pages/settings/index.ts +29 -0
  300. package/templates/default/lumenjs.config.ts +3 -0
  301. package/templates/default/package.json +14 -0
  302. package/templates/default/pages/index.ts +24 -0
  303. package/templates/social/api/posts/[id].ts +14 -0
  304. package/templates/social/api/posts.ts +11 -0
  305. package/templates/social/api/profile/[username].ts +10 -0
  306. package/templates/social/api/upload.ts +19 -0
  307. package/templates/social/data/migrations/001_init.sql +78 -0
  308. package/templates/social/data/migrations/002_add_image_url.sql +1 -0
  309. package/templates/social/data/migrations/003_auth.sql +7 -0
  310. package/templates/social/docs/architecture.md +76 -0
  311. package/templates/social/docs/components.md +100 -0
  312. package/templates/social/docs/data.md +89 -0
  313. package/templates/social/docs/pages.md +96 -0
  314. package/templates/social/docs/theming.md +52 -0
  315. package/templates/social/lib/media.ts +130 -0
  316. package/templates/social/lumenjs.auth.ts +21 -0
  317. package/templates/social/lumenjs.config.ts +3 -0
  318. package/templates/social/package.json +5 -0
  319. package/templates/social/pages/_layout.ts +239 -0
  320. package/templates/social/pages/apps/[id].ts +173 -0
  321. package/templates/social/pages/apps/index.ts +116 -0
  322. package/templates/social/pages/auth/login.ts +92 -0
  323. package/templates/social/pages/bookmarks.ts +57 -0
  324. package/templates/social/pages/explore.ts +73 -0
  325. package/templates/social/pages/index.ts +351 -0
  326. package/templates/social/pages/messages.ts +298 -0
  327. package/templates/social/pages/new.ts +77 -0
  328. package/templates/social/pages/notifications.ts +73 -0
  329. package/templates/social/pages/post/[id].ts +124 -0
  330. package/templates/social/pages/profile/[username].ts +100 -0
  331. package/templates/social/pages/settings/accessibility.ts +153 -0
  332. package/templates/social/pages/settings/account.ts +260 -0
  333. package/templates/social/pages/settings/help.ts +141 -0
  334. package/templates/social/pages/settings/language.ts +103 -0
  335. package/templates/social/pages/settings/privacy.ts +183 -0
  336. package/templates/social/pages/settings/security.ts +133 -0
  337. package/templates/social/pages/settings.ts +185 -0
@@ -1,6 +1,6 @@
1
- import { fetchLoaderData, fetchLayoutLoaderData, render404 } from './router-data.js';
1
+ import { fetchLoaderData, fetchLayoutLoaderData, prefetchLoaderData, prefetchLayoutLoaderData, connectSubscribe, connectLayoutSubscribe, render404 } from './router-data.js';
2
2
  import { hydrateInitialRoute } from './router-hydration.js';
3
- import { getI18nConfig, getLocale, stripLocalePrefix, buildLocalePath } from './i18n.js';
3
+ import { getI18nConfig, getLocale, initI18n, stripLocalePrefix, buildLocalePath } from './i18n.js';
4
4
  /**
5
5
  * Simple client-side router for LumenJS pages.
6
6
  * Handles popstate and link clicks for SPA navigation.
@@ -12,27 +12,79 @@ export class NkRouter {
12
12
  this.outlet = null;
13
13
  this.currentTag = null;
14
14
  this.currentLayoutTags = [];
15
+ this.subscriptions = [];
15
16
  this.params = {};
16
17
  this.outlet = outlet;
18
+ this.siteTitle = document.title || 'LumenJS App';
17
19
  this.routes = routes.map(r => ({
18
20
  ...r,
19
21
  ...this.compilePattern(r.path),
20
22
  }));
23
+ // Initialize i18n from inlined data before any rendering
24
+ const i18nScript = document.getElementById('__nk_i18n__');
25
+ if (i18nScript) {
26
+ try {
27
+ const i18nData = JSON.parse(i18nScript.textContent || '');
28
+ initI18n(i18nData.config, i18nData.locale, i18nData.translations);
29
+ }
30
+ catch { /* ignore */ }
31
+ if (!hydrate)
32
+ i18nScript.remove();
33
+ }
21
34
  window.addEventListener('popstate', () => {
22
35
  const path = this.stripLocale(location.pathname);
23
36
  this.navigate(path, false);
24
37
  });
38
+ // Re-run loader when page is restored from bfcache (back/forward on mobile Safari, etc.)
39
+ window.addEventListener('pageshow', (e) => {
40
+ if (e.persisted) {
41
+ const path = this.stripLocale(location.pathname);
42
+ this.navigate(path, false);
43
+ }
44
+ });
25
45
  document.addEventListener('click', (e) => this.handleLinkClick(e));
46
+ window.__nk_navigate = (href) => {
47
+ const path = this.stripLocale(href);
48
+ if (this.matchRoute(path.split('?')[0])) {
49
+ this.navigate(path);
50
+ }
51
+ else {
52
+ window.location.href = href;
53
+ }
54
+ };
55
+ window.__nk_prefetch = (href) => {
56
+ const path = this.stripLocale(href);
57
+ this.prefetch(path);
58
+ };
26
59
  if (hydrate) {
27
60
  hydrateInitialRoute(this.routes, this.outlet, (p) => this.matchRoute(p), (tag, layoutTags, params) => {
28
61
  this.currentTag = tag;
29
62
  this.currentLayoutTags = layoutTags;
30
63
  this.params = params;
31
64
  });
65
+ // Wire up SSE subscriptions after hydration
66
+ const path = this.stripLocale(location.pathname);
67
+ this.setupSubscriptions(path);
32
68
  }
33
69
  else {
34
- const path = this.stripLocale(location.pathname);
35
- this.navigate(path, false);
70
+ // Initialize auth from inlined data before navigating (CSR path)
71
+ const authScript = document.getElementById('__nk_auth__');
72
+ if (authScript) {
73
+ import('@lumenjs/auth').then(({ initAuth }) => {
74
+ try {
75
+ initAuth(JSON.parse(authScript.textContent || ''));
76
+ }
77
+ catch { }
78
+ authScript.remove();
79
+ }).catch(() => { }).finally(() => {
80
+ const path = this.stripLocale(location.pathname);
81
+ this.navigate(path, false);
82
+ });
83
+ }
84
+ else {
85
+ const path = this.stripLocale(location.pathname);
86
+ this.navigate(path, false);
87
+ }
36
88
  }
37
89
  }
38
90
  compilePattern(path) {
@@ -43,7 +95,15 @@ export class NkRouter {
43
95
  });
44
96
  return { pattern: new RegExp(`^${pattern}$`), paramNames };
45
97
  }
46
- async navigate(pathname, pushState = true) {
98
+ cleanupSubscriptions() {
99
+ for (const es of this.subscriptions) {
100
+ es.close();
101
+ }
102
+ this.subscriptions = [];
103
+ }
104
+ async navigate(fullPath, pushState = true) {
105
+ this.cleanupSubscriptions();
106
+ const pathname = fullPath.split('?')[0];
47
107
  const match = this.matchRoute(pathname);
48
108
  if (!match) {
49
109
  if (this.outlet)
@@ -53,49 +113,74 @@ export class NkRouter {
53
113
  return;
54
114
  }
55
115
  if (pushState) {
56
- const localePath = this.withLocale(pathname);
116
+ const localePath = this.withLocale(fullPath);
57
117
  history.pushState(null, '', localePath);
118
+ window.scrollTo(0, 0);
58
119
  }
59
120
  this.params = match.params;
60
- // Lazy-load the page component if not yet registered
61
- if (match.route.load && !customElements.get(match.route.tagName)) {
62
- await match.route.load();
63
- }
64
- // Load layout components
65
- const layouts = match.route.layouts || [];
66
- for (const layout of layouts) {
67
- if (layout.load && !customElements.get(layout.tagName)) {
68
- await layout.load();
69
- }
70
- }
71
- // Fetch loader data for page
72
- let loaderData = undefined;
73
- if (match.route.hasLoader) {
121
+ // Auth guard: SPA-navigate unauthenticated users to login page
122
+ if (match.route.__nk_has_auth) {
74
123
  try {
75
- loaderData = await fetchLoaderData(pathname, match.params);
76
- }
77
- catch (err) {
78
- console.error('[NkRouter] Loader fetch failed:', err);
124
+ const { isAuthenticated } = await import('@lumenjs/auth');
125
+ if (!isAuthenticated()) {
126
+ const loginPath = '/auth/login';
127
+ const loginUrl = `${loginPath}?returnTo=${encodeURIComponent(pathname)}`;
128
+ history.pushState(null, '', loginUrl);
129
+ this.navigate(loginPath, false);
130
+ return;
131
+ }
79
132
  }
133
+ catch { }
80
134
  }
81
- // Fetch loader data for layouts
82
- const layoutDataList = [];
135
+ const layouts = match.route.layouts || [];
136
+ // Load all component JS chunks in parallel
137
+ await Promise.all([
138
+ match.route.load && !customElements.get(match.route.tagName) ? match.route.load() : undefined,
139
+ ...layouts.map(l => l.load && !customElements.get(l.tagName) ? l.load() : undefined),
140
+ ]);
141
+ // Fetch all loader data in parallel
142
+ const loaderPromises = [
143
+ match.route.hasLoader
144
+ ? fetchLoaderData(pathname, match.params).catch(err => { console.error('[NkRouter] Loader fetch failed:', err); return undefined; })
145
+ : Promise.resolve(undefined),
146
+ ...layouts.map(layout => layout.hasLoader
147
+ ? fetchLayoutLoaderData(layout.loaderPath || '').catch(err => { console.error('[NkRouter] Layout loader fetch failed:', err); return undefined; })
148
+ : Promise.resolve(undefined)),
149
+ ];
150
+ const [loaderData, ...layoutDataList] = await Promise.all(loaderPromises);
151
+ this.renderRoute(match.route, loaderData, layouts, layoutDataList);
152
+ // Update document.title and announce route change for screen readers
153
+ this.updatePageMeta(match.route, loaderData);
154
+ // Set up SSE subscriptions
155
+ this.setupSubscriptions(pathname);
156
+ }
157
+ setupSubscriptions(pathname) {
158
+ const match = this.matchRoute(pathname);
159
+ if (!match)
160
+ return;
161
+ const layouts = match.route.layouts || [];
162
+ // Page subscription
163
+ if (match.route.hasSubscribe) {
164
+ const es = connectSubscribe(pathname, match.params);
165
+ es.onmessage = (e) => {
166
+ const pageEl = this.findPageElement(match.route.tagName);
167
+ if (pageEl)
168
+ pageEl.liveData = JSON.parse(e.data);
169
+ };
170
+ this.subscriptions.push(es);
171
+ }
172
+ // Layout subscriptions
83
173
  for (const layout of layouts) {
84
- if (layout.hasLoader) {
85
- try {
86
- const data = await fetchLayoutLoaderData(layout.loaderPath || '');
87
- layoutDataList.push(data);
88
- }
89
- catch (err) {
90
- console.error('[NkRouter] Layout loader fetch failed:', err);
91
- layoutDataList.push(undefined);
92
- }
93
- }
94
- else {
95
- layoutDataList.push(undefined);
174
+ if (layout.hasSubscribe) {
175
+ const es = connectLayoutSubscribe(layout.loaderPath || '');
176
+ es.onmessage = (e) => {
177
+ const layoutEl = this.outlet?.querySelector(layout.tagName);
178
+ if (layoutEl)
179
+ layoutEl.liveData = JSON.parse(e.data);
180
+ };
181
+ this.subscriptions.push(es);
96
182
  }
97
183
  }
98
- this.renderRoute(match.route, loaderData, layouts, layoutDataList);
99
184
  }
100
185
  matchRoute(pathname) {
101
186
  for (const route of this.routes) {
@@ -146,6 +231,7 @@ export class NkRouter {
146
231
  }
147
232
  if (layoutDataList && layoutDataList[i] !== undefined) {
148
233
  layoutEl.loaderData = layoutDataList[i];
234
+ this.spreadData(layoutEl, layoutDataList[i]);
149
235
  }
150
236
  parentEl = layoutEl;
151
237
  }
@@ -170,12 +256,14 @@ export class NkRouter {
170
256
  const outerLayout = document.createElement(layouts[0].tagName);
171
257
  if (layoutDataList[0] !== undefined) {
172
258
  outerLayout.loaderData = layoutDataList[0];
259
+ this.spreadData(outerLayout, layoutDataList[0]);
173
260
  }
174
261
  let current = outerLayout;
175
262
  for (let i = 1; i < layouts.length; i++) {
176
263
  const inner = document.createElement(layouts[i].tagName);
177
264
  if (layoutDataList[i] !== undefined) {
178
265
  inner.loaderData = layoutDataList[i];
266
+ this.spreadData(inner, layoutDataList[i]);
179
267
  }
180
268
  current.appendChild(inner);
181
269
  current = inner;
@@ -184,6 +272,19 @@ export class NkRouter {
184
272
  current.appendChild(pageEl);
185
273
  return outerLayout;
186
274
  }
275
+ /** Spread loader data as individual properties on an element. */
276
+ spreadData(el, data) {
277
+ if (data && typeof data === 'object') {
278
+ const BLOCKED = new Set(['__proto__', 'constructor', 'prototype',
279
+ 'innerHTML', 'outerHTML', 'textContent',
280
+ 'render', 'connectedCallback', 'disconnectedCallback']);
281
+ for (const [key, value] of Object.entries(data)) {
282
+ if (!BLOCKED.has(key)) {
283
+ el[key] = value;
284
+ }
285
+ }
286
+ }
287
+ }
187
288
  createPageElement(route, loaderData) {
188
289
  const el = document.createElement(route.tagName);
189
290
  for (const [key, value] of Object.entries(this.params)) {
@@ -191,9 +292,56 @@ export class NkRouter {
191
292
  }
192
293
  if (loaderData !== undefined) {
193
294
  el.loaderData = loaderData;
295
+ this.spreadData(el, loaderData);
194
296
  }
195
297
  return el;
196
298
  }
299
+ findPageElement(tagName) {
300
+ if (!this.outlet)
301
+ return null;
302
+ return this.outlet.querySelector(tagName) ?? this.outlet.querySelector(`${tagName}:last-child`);
303
+ }
304
+ /**
305
+ * Resolve the page title from the route's meta export and update
306
+ * document.title, the aria-live announcer, and focus.
307
+ */
308
+ async updatePageMeta(route, loaderData) {
309
+ let pageTitle;
310
+ if (route.hasMeta && route.load) {
311
+ try {
312
+ const mod = await route.load();
313
+ if (mod) {
314
+ let meta;
315
+ if (typeof mod.meta === 'function') {
316
+ meta = mod.meta({ data: loaderData, params: this.params });
317
+ }
318
+ else if (mod.meta && typeof mod.meta === 'object') {
319
+ meta = mod.meta;
320
+ }
321
+ if (meta?.title) {
322
+ pageTitle = `${meta.title} | ${this.siteTitle}`;
323
+ }
324
+ }
325
+ }
326
+ catch { /* fall back to site title */ }
327
+ }
328
+ const title = pageTitle || this.siteTitle;
329
+ document.title = title;
330
+ // Announce route change to screen readers
331
+ const announcer = document.getElementById('nk-route-announcer');
332
+ if (announcer) {
333
+ announcer.textContent = '';
334
+ // Use a microtask delay so aria-live picks up the change
335
+ requestAnimationFrame(() => { announcer.textContent = title; });
336
+ }
337
+ // Move focus to the router outlet for keyboard/screen reader users
338
+ if (this.outlet) {
339
+ if (!this.outlet.hasAttribute('tabindex')) {
340
+ this.outlet.setAttribute('tabindex', '-1');
341
+ }
342
+ this.outlet.focus({ preventScroll: true });
343
+ }
344
+ }
197
345
  handleLinkClick(event) {
198
346
  const path = event.composedPath();
199
347
  const anchor = path.find((el) => el instanceof HTMLElement && el.tagName === 'A');
@@ -202,9 +350,27 @@ export class NkRouter {
202
350
  const href = anchor.getAttribute('href');
203
351
  if (!href || href.startsWith('http') || href.startsWith('#') || anchor.hasAttribute('target'))
204
352
  return;
353
+ // Allow modifier-key clicks to behave normally (Ctrl+Click = new tab, etc.)
354
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
355
+ return;
205
356
  event.preventDefault();
206
357
  this.navigate(this.stripLocale(href));
207
358
  }
359
+ async prefetch(fullPath) {
360
+ const pathname = fullPath.split('?')[0];
361
+ const match = this.matchRoute(pathname);
362
+ if (!match)
363
+ return;
364
+ const layouts = match.route.layouts || [];
365
+ await Promise.all([
366
+ // Preload component JS chunks
367
+ match.route.load && !customElements.get(match.route.tagName) ? match.route.load() : undefined,
368
+ ...layouts.map(l => l.load && !customElements.get(l.tagName) ? l.load() : undefined),
369
+ // Prefetch loader data (cached)
370
+ match.route.hasLoader ? prefetchLoaderData(pathname, match.params).catch(() => { }) : undefined,
371
+ ...layouts.map(l => l.hasLoader ? prefetchLayoutLoaderData(l.loaderPath || '').catch(() => { }) : undefined),
372
+ ]);
373
+ }
208
374
  /** Strip locale prefix from a path for internal route matching. */
209
375
  stripLocale(path) {
210
376
  const config = getI18nConfig();
@@ -216,3 +382,19 @@ export class NkRouter {
216
382
  return config ? buildLocalePath(getLocale(), path) : path;
217
383
  }
218
384
  }
385
+ /** Navigate via the client-side router. Falls back to full reload for unknown routes. */
386
+ export function navigate(href) {
387
+ const nav = window.__nk_navigate;
388
+ if (nav) {
389
+ nav(href);
390
+ }
391
+ else {
392
+ window.location.href = href;
393
+ }
394
+ }
395
+ /** Programmatically prefetch a route's JS chunks and loader data. */
396
+ export function prefetch(href) {
397
+ const pf = window.__nk_prefetch;
398
+ if (pf)
399
+ pf(href);
400
+ }
@@ -0,0 +1,2 @@
1
+ export declare function connectSocket(routePath: string, params: Record<string, string>): Promise<any>;
2
+ export declare function disconnectAllSockets(): void;
@@ -0,0 +1,30 @@
1
+ import { getI18nConfig, getLocale } from './i18n.js';
2
+ const connections = new Map();
3
+ export async function connectSocket(routePath, params) {
4
+ const { io } = await import('socket.io-client');
5
+ const ns = `/nk${routePath === '/' ? '/index' : routePath}`;
6
+ const query = {};
7
+ if (Object.keys(params).length > 0) {
8
+ query.__params = JSON.stringify(params);
9
+ }
10
+ try {
11
+ const config = getI18nConfig();
12
+ if (config) {
13
+ query.__locale = getLocale();
14
+ }
15
+ }
16
+ catch { }
17
+ // Disconnect existing socket for this route to prevent leaks
18
+ const existing = connections.get(routePath);
19
+ if (existing)
20
+ existing.disconnect();
21
+ const socket = io(ns, { path: '/__nk_socketio/', query });
22
+ connections.set(routePath, socket);
23
+ return socket;
24
+ }
25
+ export function disconnectAllSockets() {
26
+ for (const [, socket] of connections) {
27
+ socket.disconnect();
28
+ }
29
+ connections.clear();
30
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * WebRTC peer connection manager.
3
+ * Wraps RTCPeerConnection and wires to the LumenJS communication SDK signaling.
4
+ */
5
+ export type CallRole = 'caller' | 'callee';
6
+ export interface WebRTCCallbacks {
7
+ onRemoteStream: (stream: MediaStream) => void;
8
+ onLocalStream: (stream: MediaStream) => void;
9
+ onConnectionStateChange: (state: RTCPeerConnectionState) => void;
10
+ onIceCandidate: (candidate: RTCIceCandidate) => void;
11
+ onError: (error: Error) => void;
12
+ }
13
+ export declare class WebRTCManager {
14
+ private _pc;
15
+ private _localStream;
16
+ private _remoteStream;
17
+ private _callbacks;
18
+ private _pendingCandidates;
19
+ private _role;
20
+ constructor(callbacks: WebRTCCallbacks, iceServers?: RTCIceServer[]);
21
+ private _createPeerConnection;
22
+ get localStream(): MediaStream | null;
23
+ get remoteStream(): MediaStream | null;
24
+ get role(): CallRole;
25
+ get connectionState(): RTCPeerConnectionState | null;
26
+ /** Acquire local media (camera/mic) and add tracks to the peer connection */
27
+ startLocalMedia(video?: boolean, audio?: boolean): Promise<MediaStream>;
28
+ /** Create an SDP offer (caller side) */
29
+ createOffer(): Promise<string>;
30
+ /** Handle received SDP offer and create answer (callee side) */
31
+ handleOffer(sdp: string): Promise<string>;
32
+ /** Handle received SDP answer (caller side) */
33
+ handleAnswer(sdp: string): Promise<void>;
34
+ /** Add a received ICE candidate */
35
+ addIceCandidate(candidate: string, sdpMLineIndex: number | null, sdpMid: string | null): Promise<void>;
36
+ private _flushPendingCandidates;
37
+ /** Toggle audio mute */
38
+ setAudioEnabled(enabled: boolean): void;
39
+ /** Toggle video */
40
+ setVideoEnabled(enabled: boolean): void;
41
+ /** Replace video track with screen share */
42
+ startScreenShare(): Promise<MediaStream>;
43
+ /** Revert from screen share back to camera */
44
+ stopScreenShare(): Promise<void>;
45
+ /** Clean up everything */
46
+ destroy(): void;
47
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * WebRTC peer connection manager.
3
+ * Wraps RTCPeerConnection and wires to the LumenJS communication SDK signaling.
4
+ */
5
+ const ICE_SERVERS = [
6
+ { urls: 'stun:stun.l.google.com:19302' },
7
+ { urls: 'stun:stun1.l.google.com:19302' },
8
+ ];
9
+ export class WebRTCManager {
10
+ constructor(callbacks, iceServers) {
11
+ this._pc = null;
12
+ this._localStream = null;
13
+ this._remoteStream = null;
14
+ this._pendingCandidates = [];
15
+ this._role = 'caller';
16
+ this._callbacks = callbacks;
17
+ this._createPeerConnection(iceServers || ICE_SERVERS);
18
+ }
19
+ _createPeerConnection(iceServers) {
20
+ this._pc = new RTCPeerConnection({ iceServers });
21
+ this._pc.onicecandidate = (event) => {
22
+ if (event.candidate) {
23
+ this._callbacks.onIceCandidate(event.candidate);
24
+ }
25
+ };
26
+ this._pc.ontrack = (event) => {
27
+ if (!this._remoteStream) {
28
+ this._remoteStream = new MediaStream();
29
+ this._callbacks.onRemoteStream(this._remoteStream);
30
+ }
31
+ this._remoteStream.addTrack(event.track);
32
+ };
33
+ this._pc.onconnectionstatechange = () => {
34
+ if (this._pc) {
35
+ this._callbacks.onConnectionStateChange(this._pc.connectionState);
36
+ }
37
+ };
38
+ }
39
+ get localStream() { return this._localStream; }
40
+ get remoteStream() { return this._remoteStream; }
41
+ get role() { return this._role; }
42
+ get connectionState() { return this._pc?.connectionState ?? null; }
43
+ /** Acquire local media (camera/mic) and add tracks to the peer connection */
44
+ async startLocalMedia(video = true, audio = true) {
45
+ try {
46
+ this._localStream = await navigator.mediaDevices.getUserMedia({ video, audio });
47
+ this._callbacks.onLocalStream(this._localStream);
48
+ for (const track of this._localStream.getTracks()) {
49
+ this._pc?.addTrack(track, this._localStream);
50
+ }
51
+ return this._localStream;
52
+ }
53
+ catch (err) {
54
+ this._callbacks.onError(new Error(`Failed to access media: ${err.message}`));
55
+ throw err;
56
+ }
57
+ }
58
+ /** Create an SDP offer (caller side) */
59
+ async createOffer() {
60
+ this._role = 'caller';
61
+ if (!this._pc)
62
+ throw new Error('No peer connection');
63
+ const offer = await this._pc.createOffer();
64
+ await this._pc.setLocalDescription(offer);
65
+ return offer.sdp;
66
+ }
67
+ /** Handle received SDP offer and create answer (callee side) */
68
+ async handleOffer(sdp) {
69
+ this._role = 'callee';
70
+ if (!this._pc)
71
+ throw new Error('No peer connection');
72
+ await this._pc.setRemoteDescription({ type: 'offer', sdp });
73
+ // Flush pending ICE candidates
74
+ await this._flushPendingCandidates();
75
+ const answer = await this._pc.createAnswer();
76
+ await this._pc.setLocalDescription(answer);
77
+ return answer.sdp;
78
+ }
79
+ /** Handle received SDP answer (caller side) */
80
+ async handleAnswer(sdp) {
81
+ if (!this._pc)
82
+ throw new Error('No peer connection');
83
+ await this._pc.setRemoteDescription({ type: 'answer', sdp });
84
+ await this._flushPendingCandidates();
85
+ }
86
+ /** Add a received ICE candidate */
87
+ async addIceCandidate(candidate, sdpMLineIndex, sdpMid) {
88
+ const init = {
89
+ candidate,
90
+ sdpMLineIndex: sdpMLineIndex ?? undefined,
91
+ sdpMid: sdpMid ?? undefined,
92
+ };
93
+ if (!this._pc?.remoteDescription) {
94
+ // Queue candidates until remote description is set
95
+ this._pendingCandidates.push(init);
96
+ return;
97
+ }
98
+ try {
99
+ await this._pc.addIceCandidate(init);
100
+ }
101
+ catch (err) {
102
+ console.warn('[WebRTC] Failed to add ICE candidate:', err);
103
+ }
104
+ }
105
+ async _flushPendingCandidates() {
106
+ for (const c of this._pendingCandidates) {
107
+ try {
108
+ await this._pc?.addIceCandidate(c);
109
+ }
110
+ catch { }
111
+ }
112
+ this._pendingCandidates = [];
113
+ }
114
+ /** Toggle audio mute */
115
+ setAudioEnabled(enabled) {
116
+ if (this._localStream) {
117
+ for (const track of this._localStream.getAudioTracks()) {
118
+ track.enabled = enabled;
119
+ }
120
+ }
121
+ }
122
+ /** Toggle video */
123
+ setVideoEnabled(enabled) {
124
+ if (this._localStream) {
125
+ for (const track of this._localStream.getVideoTracks()) {
126
+ track.enabled = enabled;
127
+ }
128
+ }
129
+ }
130
+ /** Replace video track with screen share */
131
+ async startScreenShare() {
132
+ const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
133
+ const screenTrack = stream.getVideoTracks()[0];
134
+ if (this._pc && this._localStream) {
135
+ const sender = this._pc.getSenders().find(s => s.track?.kind === 'video');
136
+ if (sender) {
137
+ await sender.replaceTrack(screenTrack);
138
+ }
139
+ }
140
+ // When user stops sharing via browser UI
141
+ screenTrack.onended = () => {
142
+ this.stopScreenShare();
143
+ };
144
+ return stream;
145
+ }
146
+ /** Revert from screen share back to camera */
147
+ async stopScreenShare() {
148
+ if (this._localStream && this._pc) {
149
+ const cameraTrack = this._localStream.getVideoTracks()[0];
150
+ if (cameraTrack) {
151
+ const sender = this._pc.getSenders().find(s => s.track?.kind === 'video');
152
+ if (sender) {
153
+ await sender.replaceTrack(cameraTrack);
154
+ }
155
+ }
156
+ }
157
+ }
158
+ /** Clean up everything */
159
+ destroy() {
160
+ if (this._localStream) {
161
+ for (const track of this._localStream.getTracks()) {
162
+ track.stop();
163
+ }
164
+ this._localStream = null;
165
+ }
166
+ if (this._remoteStream) {
167
+ for (const track of this._remoteStream.getTracks()) {
168
+ track.stop();
169
+ }
170
+ this._remoteStream = null;
171
+ }
172
+ if (this._pc) {
173
+ this._pc.close();
174
+ this._pc = null;
175
+ }
176
+ this._pendingCandidates = [];
177
+ }
178
+ }
@@ -0,0 +1,8 @@
1
+ import type { Server } from 'http';
2
+ export interface ShutdownConfig {
3
+ /** Max time to wait for connections to drain (ms). Default: 30000. */
4
+ timeout?: number;
5
+ /** Extra cleanup functions to run before exit. */
6
+ onShutdown?: () => Promise<void> | void;
7
+ }
8
+ export declare function setupGracefulShutdown(server: Server, config?: ShutdownConfig): void;
@@ -0,0 +1,36 @@
1
+ import { logger } from './logger.js';
2
+ export function setupGracefulShutdown(server, config) {
3
+ const timeout = config?.timeout ?? 30_000;
4
+ let isShuttingDown = false;
5
+ const shutdown = async (signal) => {
6
+ if (isShuttingDown)
7
+ return;
8
+ isShuttingDown = true;
9
+ logger.info(`Received ${signal}, starting graceful shutdown...`);
10
+ // Stop accepting new connections
11
+ server.close(() => {
12
+ logger.info('All connections drained.');
13
+ });
14
+ // Force-close after timeout
15
+ const forceTimer = setTimeout(() => {
16
+ logger.warn('Shutdown timeout reached, forcing exit.', { timeout });
17
+ process.exit(1);
18
+ }, timeout);
19
+ forceTimer.unref();
20
+ // Run custom cleanup
21
+ if (config?.onShutdown) {
22
+ try {
23
+ await config.onShutdown();
24
+ logger.info('Custom cleanup completed.');
25
+ }
26
+ catch (err) {
27
+ logger.error('Error during custom cleanup.', { error: err?.message });
28
+ }
29
+ }
30
+ // Close idle keep-alive connections
31
+ server.closeIdleConnections();
32
+ logger.info('Graceful shutdown complete.');
33
+ };
34
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
35
+ process.on('SIGINT', () => shutdown('SIGINT'));
36
+ }
@@ -0,0 +1,8 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ export interface HealthCheckConfig {
3
+ /** Endpoint path. Default: '/__health'. */
4
+ path?: string;
5
+ /** App version string. Default: reads from package.json or 'unknown'. */
6
+ version?: string;
7
+ }
8
+ export declare function createHealthCheckHandler(config?: HealthCheckConfig): (req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => void;