@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
@@ -47,10 +47,77 @@ export function i18nPlugin(projectDir, config) {
47
47
  res.end(JSON.stringify({ error: 'Invalid locale file' }));
48
48
  }
49
49
  });
50
- // Watch locale directory for changes and trigger HMR
50
+ // Watch locale files for changes and send HMR event.
51
+ // Uses fs.watchFile (stat-based polling) instead of inotify/chokidar so
52
+ // it works reliably inside Docker containers where inotify misses changes.
51
53
  if (fs.existsSync(localesDir)) {
52
- server.watcher.add(localesDir);
54
+ for (const locale of config.locales) {
55
+ const filePath = path.join(localesDir, `${locale}.json`);
56
+ if (!fs.existsSync(filePath))
57
+ continue;
58
+ fs.watchFile(filePath, { interval: 500 }, (curr, prev) => {
59
+ if (curr.mtimeMs === prev.mtimeMs)
60
+ return;
61
+ server.ws.send({
62
+ type: 'custom',
63
+ event: 'lumenjs:i18n-update',
64
+ data: { locale },
65
+ });
66
+ });
67
+ }
68
+ server.httpServer?.on('close', () => {
69
+ for (const locale of config.locales) {
70
+ fs.unwatchFile(path.join(localesDir, `${locale}.json`));
71
+ }
72
+ });
73
+ }
74
+ },
75
+ transformIndexHtml() {
76
+ return [
77
+ {
78
+ tag: 'script',
79
+ attrs: { type: 'module' },
80
+ children: `
81
+ // i18n HMR: listen on Vite's WebSocket for locale file changes.
82
+ // Uses window.__lumenjs_i18n_reload (set by the i18n runtime) to update
83
+ // translations in the correct module instance — avoids the duplicate-module
84
+ // problem caused by Vite's cache-busting query strings on /@fs/ imports.
85
+ import { createHotContext } from '/@vite/client';
86
+ const hot = createHotContext('/__nk_i18n_hmr');
87
+ hot.on('lumenjs:i18n-update', async ({ locale }) => {
88
+ const reload = window.__lumenjs_i18n_reload;
89
+ if (!reload) return;
90
+ const updated = await reload(locale);
91
+ if (!updated) return;
92
+ function __updateAll(root) {
93
+ for (const el of root.querySelectorAll('*')) {
94
+ if (el.requestUpdate) {
95
+ // Clear Lit's template cache to force a full re-render.
96
+ // Deleting _$litPart$ forces Lit to re-create the template from scratch.
97
+ if (el.renderRoot) {
98
+ if ('_$litPart$' in el.renderRoot) {
99
+ delete el.renderRoot['_$litPart$'];
100
+ } else {
101
+ for (const s of Object.getOwnPropertySymbols(el.renderRoot)) {
102
+ const v = el.renderRoot[s];
103
+ if (v && typeof v === 'object' && '_$committedValue' in v) {
104
+ v._$committedValue = undefined;
105
+ break;
106
+ }
53
107
  }
108
+ }
109
+ }
110
+ el.requestUpdate();
111
+ }
112
+ if (el.shadowRoot) __updateAll(el.shadowRoot);
113
+ }
114
+ }
115
+ __updateAll(document);
116
+ });
117
+ `,
118
+ injectTo: 'body',
119
+ },
120
+ ];
54
121
  },
55
122
  };
56
123
  }
@@ -1,5 +1,11 @@
1
1
  import { Plugin } from 'vite';
2
2
  /**
3
3
  * Force ALL lit imports to resolve to lumenjs's single lit copy.
4
+ *
5
+ * Instead of returning absolute paths directly (which bypasses Vite's ?v= cache
6
+ * busting and causes module duplication), we resolve the correct entry point
7
+ * from package.json exports, then delegate to Vite's resolver using a fake
8
+ * importer within lumenjs node_modules. This ensures consistent ?v= hashing
9
+ * across all import chains.
4
10
  */
5
11
  export declare function litDedupPlugin(lumenNodeModules: string, isDev: boolean): Plugin;
@@ -2,12 +2,76 @@ import path from 'path';
2
2
  import fs from 'fs';
3
3
  /**
4
4
  * Force ALL lit imports to resolve to lumenjs's single lit copy.
5
+ *
6
+ * Instead of returning absolute paths directly (which bypasses Vite's ?v= cache
7
+ * busting and causes module duplication), we resolve the correct entry point
8
+ * from package.json exports, then delegate to Vite's resolver using a fake
9
+ * importer within lumenjs node_modules. This ensures consistent ?v= hashing
10
+ * across all import chains.
5
11
  */
6
12
  export function litDedupPlugin(lumenNodeModules, isDev) {
13
+ // Cache resolved export paths per (pkgName, subpath) to avoid repeated pkg.json reads
14
+ const exportCache = new Map();
15
+ function resolveExport(pkgName, subpath, ssr) {
16
+ const cacheKey = `${pkgName}:${subpath}:${ssr}`;
17
+ if (exportCache.has(cacheKey))
18
+ return exportCache.get(cacheKey);
19
+ const pkgDir = path.join(lumenNodeModules, pkgName);
20
+ if (!fs.existsSync(pkgDir)) {
21
+ exportCache.set(cacheKey, null);
22
+ return null;
23
+ }
24
+ let resolved = null;
25
+ try {
26
+ const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8'));
27
+ const exports = pkg.exports;
28
+ if (exports) {
29
+ const exportKey = subpath ? './' + subpath : '.';
30
+ const entry = exports[exportKey];
31
+ if (entry) {
32
+ let entryPath;
33
+ if (ssr) {
34
+ entryPath = isDev
35
+ ? (entry?.node?.development || entry?.node?.default || entry?.node || entry?.development || entry?.default || entry)
36
+ : (entry?.node?.default || entry?.node || entry?.default || entry);
37
+ }
38
+ else {
39
+ entryPath = isDev
40
+ ? (entry?.browser?.development || entry?.development || entry?.browser?.default || entry?.default || entry)
41
+ : (entry?.browser?.default || entry?.browser || entry?.default || entry);
42
+ }
43
+ if (typeof entryPath === 'string') {
44
+ let finalPath = entryPath;
45
+ // In dev mode, prefer development/ builds to avoid mixing prod/dev modules
46
+ if (isDev && !entryPath.includes('/development/')) {
47
+ const devEntryPath = entryPath.replace(/^\.\//, '');
48
+ const devFullPath = path.join(pkgDir, 'development', devEntryPath);
49
+ if (fs.existsSync(devFullPath)) {
50
+ finalPath = './development/' + devEntryPath;
51
+ }
52
+ }
53
+ resolved = finalPath;
54
+ }
55
+ }
56
+ }
57
+ if (!resolved && subpath) {
58
+ resolved = './' + subpath;
59
+ }
60
+ if (!resolved) {
61
+ const entry = pkg.module || pkg.main || 'index.js';
62
+ resolved = './' + entry;
63
+ }
64
+ }
65
+ catch {
66
+ resolved = subpath ? './' + subpath : null;
67
+ }
68
+ exportCache.set(cacheKey, resolved);
69
+ return resolved;
70
+ }
7
71
  return {
8
72
  name: 'lumenjs-lit-dedup',
9
73
  enforce: 'pre',
10
- resolveId(source, importer, options) {
74
+ async resolveId(source, importer, options) {
11
75
  if (!importer)
12
76
  return;
13
77
  const isLitImport = source === 'lit' || source.startsWith('lit/')
@@ -23,40 +87,20 @@ export function litDedupPlugin(lumenNodeModules, isDev) {
23
87
  const pkgName = source.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
24
88
  const subpath = source.startsWith('@') ? parts.slice(2).join('/') : parts.slice(1).join('/');
25
89
  const pkgDir = path.join(lumenNodeModules, pkgName);
26
- if (!fs.existsSync(pkgDir))
90
+ const resolved = resolveExport(pkgName, subpath, !!options?.ssr);
91
+ if (!resolved)
27
92
  return;
28
- try {
29
- const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8'));
30
- const exports = pkg.exports;
31
- if (exports) {
32
- const exportKey = subpath ? './' + subpath : '.';
33
- const entry = exports[exportKey];
34
- if (entry) {
35
- let resolved;
36
- if (options?.ssr) {
37
- resolved = isDev
38
- ? (entry?.node?.development || entry?.node?.default || entry?.node || entry?.development || entry?.default || entry)
39
- : (entry?.node?.default || entry?.node || entry?.default || entry);
40
- }
41
- else {
42
- resolved = isDev
43
- ? (entry?.browser?.development || entry?.development || entry?.browser?.default || entry?.default || entry)
44
- : (entry?.browser?.default || entry?.browser || entry?.default || entry);
45
- }
46
- if (typeof resolved === 'string') {
47
- return path.join(pkgDir, resolved);
48
- }
49
- }
50
- }
51
- if (subpath) {
52
- return path.join(pkgDir, subpath);
53
- }
54
- const entry = pkg.module || pkg.main || 'index.js';
55
- return path.join(pkgDir, entry);
56
- }
57
- catch {
58
- return subpath ? path.join(pkgDir, subpath) : pkgDir;
59
- }
93
+ // Compute the absolute path for the resolved entry
94
+ const absolutePath = path.join(pkgDir, resolved);
95
+ // Delegate to Vite's resolver using a fake importer inside lumenjs node_modules.
96
+ // This ensures Vite's importAnalysis adds consistent ?v= cache busting,
97
+ // preventing module duplication between different import chains.
98
+ const fakeImporter = path.join(lumenNodeModules, '_lumenjs_dedup_anchor.js');
99
+ const result = await this.resolve(absolutePath, fakeImporter, { skipSelf: true });
100
+ if (result)
101
+ return result;
102
+ // Fallback to absolute path if Vite's resolver fails
103
+ return absolutePath;
60
104
  }
61
105
  };
62
106
  }
@@ -26,15 +26,57 @@ if (import.meta.hot) {
26
26
  if (key === "constructor") continue;
27
27
  Object.defineProperty(OldClass.prototype, key, desc);
28
28
  }
29
+ const newCssText = NewClass.styles?.cssText || '';
29
30
  if (NewClass.styles) {
30
31
  OldClass.styles = NewClass.styles;
31
32
  OldClass.elementStyles = undefined;
32
- OldClass.finalizeStyles();
33
+ OldClass.finalized = false;
34
+ try { OldClass.finalizeStyles(); } catch {}
33
35
  }
34
36
  if (NewClass.properties) {
35
37
  OldClass.properties = NewClass.properties;
36
38
  }
37
- document.querySelectorAll("${tagName}").forEach((el) => {
39
+ function __queryShadowAll(root, sel) {
40
+ const results = [...root.querySelectorAll(sel)];
41
+ for (const el of root.querySelectorAll('*')) {
42
+ if (el.shadowRoot) results.push(...__queryShadowAll(el.shadowRoot, sel));
43
+ }
44
+ return results;
45
+ }
46
+ __queryShadowAll(document, "${tagName}").forEach((el) => {
47
+ if (el.renderRoot) {
48
+ // Update styles: try adoptedStyleSheets first, fall back to <style> tag
49
+ if (OldClass.elementStyles && OldClass.elementStyles.length > 0) {
50
+ const sheets = OldClass.elementStyles
51
+ .filter(s => s instanceof CSSStyleSheet || (s && s.styleSheet))
52
+ .map(s => s instanceof CSSStyleSheet ? s : s.styleSheet);
53
+ if (sheets.length && el.renderRoot.adoptedStyleSheets !== undefined) {
54
+ el.renderRoot.adoptedStyleSheets = sheets;
55
+ }
56
+ } else if (newCssText) {
57
+ const styleEl = el.renderRoot.querySelector('style');
58
+ if (styleEl) styleEl.textContent = newCssText;
59
+ }
60
+ // Clear stale SSR inline styles from child elements
61
+ el.renderRoot.querySelectorAll('[style]').forEach((child) => {
62
+ child.removeAttribute('style');
63
+ });
64
+ // Clear Lit's template cache to force a full re-render.
65
+ // Deleting the ChildPart forces Lit to re-create the entire template
66
+ // from scratch instead of diffing against stale cached values.
67
+ if ('_$litPart$' in el.renderRoot) {
68
+ delete el.renderRoot['_$litPart$'];
69
+ } else {
70
+ // Lit 2.x fallback: clear via symbols
71
+ for (const s of Object.getOwnPropertySymbols(el.renderRoot)) {
72
+ const v = el.renderRoot[s];
73
+ if (v && typeof v === 'object' && '_$committedValue' in v) {
74
+ v._$committedValue = undefined;
75
+ break;
76
+ }
77
+ }
78
+ }
79
+ }
38
80
  if (el.requestUpdate) el.requestUpdate();
39
81
  });
40
82
  });
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function lumenLlmsPlugin(projectDir: string): Plugin;
@@ -0,0 +1,92 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { scanPages, scanApiRoutes } from '../../build/scan.js';
4
+ import { readProjectConfig } from '../config.js';
5
+ import { fileGetApiMethods } from '../../shared/utils.js';
6
+ import { generateLlmsTxt, resolveDynamicEntries } from '../../llms/generate.js';
7
+ export function lumenLlmsPlugin(projectDir) {
8
+ const pagesDir = path.join(projectDir, 'pages');
9
+ const apiDir = path.join(projectDir, 'api');
10
+ return {
11
+ name: 'lumenjs-llms',
12
+ configureServer(server) {
13
+ server.middlewares.use(async (req, res, next) => {
14
+ const url = (req.url || '').split('?')[0];
15
+ if (url !== '/llms.txt')
16
+ return next();
17
+ // Check for user override in public/
18
+ const publicOverride = path.join(projectDir, 'public', 'llms.txt');
19
+ if (fs.existsSync(publicOverride)) {
20
+ // Let Vite's static serving handle it
21
+ return next();
22
+ }
23
+ try {
24
+ const config = readProjectConfig(projectDir);
25
+ const pages = scanPages(pagesDir);
26
+ const apiEntries = scanApiRoutes(apiDir);
27
+ // Build API routes with methods
28
+ const apiRoutes = apiEntries.map(entry => ({
29
+ path: entry.routePath,
30
+ methods: fileGetApiMethods(entry.filePath),
31
+ })).filter(r => r.methods.length > 0);
32
+ // Build page data with loader resolution
33
+ const llmsPages = [];
34
+ for (const page of pages) {
35
+ const llmsPage = {
36
+ path: page.routePath,
37
+ hasLoader: page.hasLoader,
38
+ hasSubscribe: page.hasSubscribe,
39
+ };
40
+ if (page.hasLoader) {
41
+ const isDynamic = page.routePath.includes(':');
42
+ if (isDynamic) {
43
+ // Extract param name from route like /blog/:slug
44
+ const paramMatch = page.routePath.match(/:([^/]+)/);
45
+ const paramName = paramMatch ? paramMatch[1] : '';
46
+ if (paramName) {
47
+ const entries = await resolveDynamicEntries({ path: page.routePath, paramName }, (filePath) => server.ssrLoadModule(filePath), pages.map(p => ({ path: p.routePath, filePath: p.filePath, hasLoader: p.hasLoader })));
48
+ if (entries) {
49
+ llmsPage.dynamicEntries = entries;
50
+ }
51
+ }
52
+ }
53
+ else {
54
+ // Static page — call loader directly
55
+ try {
56
+ const mod = await server.ssrLoadModule(page.filePath);
57
+ if (mod?.loader) {
58
+ const data = await mod.loader({ params: {}, query: {}, url: page.routePath, headers: {} });
59
+ if (data && !data.__nk_redirect) {
60
+ llmsPage.loaderData = data;
61
+ }
62
+ }
63
+ }
64
+ catch {
65
+ // Skip loader errors
66
+ }
67
+ }
68
+ }
69
+ llmsPages.push(llmsPage);
70
+ }
71
+ const content = generateLlmsTxt({
72
+ title: config.title,
73
+ pages: llmsPages,
74
+ apiRoutes,
75
+ integrations: config.integrations,
76
+ i18n: config.i18n ? { locales: config.i18n.locales, defaultLocale: config.i18n.defaultLocale } : undefined,
77
+ db: config.db,
78
+ });
79
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
80
+ res.setHeader('Cache-Control', 'no-store');
81
+ res.statusCode = 200;
82
+ res.end(content);
83
+ }
84
+ catch (err) {
85
+ console.error('[LumenJS] Error generating llms.txt:', err);
86
+ res.statusCode = 500;
87
+ res.end('Error generating llms.txt');
88
+ }
89
+ });
90
+ },
91
+ };
92
+ }
@@ -61,6 +61,11 @@ export function lumenLoadersPlugin(pagesDir) {
61
61
  delete query.__params;
62
62
  }
63
63
  const filePath = resolvePageFile(pagesDir, pagePath);
64
+ if (filePath && !filePath.startsWith(path.resolve(pagesDir) + path.sep)) {
65
+ res.statusCode = 400;
66
+ res.end();
67
+ return;
68
+ }
64
69
  if (!filePath) {
65
70
  res.statusCode = 404;
66
71
  res.end();
@@ -87,7 +92,7 @@ export function lumenLoadersPlugin(pagesDir) {
87
92
  const push = (data) => {
88
93
  res.write(`data: ${JSON.stringify(data)}\n\n`);
89
94
  };
90
- const cleanup = mod.subscribe({ params, push, headers: req.headers, locale });
95
+ const cleanup = mod.subscribe({ params, push, headers: req.headers, locale, user: req.nkAuth?.user ?? null });
91
96
  res.on('close', () => {
92
97
  if (typeof cleanup === 'function')
93
98
  cleanup();
@@ -131,8 +136,14 @@ export function lumenLoadersPlugin(pagesDir) {
131
136
  catch { /* ignore */ }
132
137
  delete query.__params;
133
138
  }
134
- // Find the page file
139
+ // Find the page file (validate path stays within pagesDir)
135
140
  const filePath = resolvePageFile(pagesDir, pagePath);
141
+ if (filePath && !filePath.startsWith(path.resolve(pagesDir) + path.sep)) {
142
+ res.statusCode = 400;
143
+ res.setHeader('Content-Type', 'application/json');
144
+ res.end(JSON.stringify({ error: 'Invalid page path' }));
145
+ return;
146
+ }
136
147
  if (!filePath) {
137
148
  res.statusCode = 404;
138
149
  res.setHeader('Content-Type', 'application/json');
@@ -157,7 +168,43 @@ export function lumenLoadersPlugin(pagesDir) {
157
168
  // Extract locale from query if provided by the client router
158
169
  const locale = query.__locale;
159
170
  delete query.__locale;
160
- const result = await mod.loader({ params, query, url: pagePath, headers: req.headers, locale });
171
+ // If auth middleware hasn't populated nkAuth, try to parse from cookie or bearer token
172
+ let user = req.nkAuth?.user ?? null;
173
+ if (!user) {
174
+ try {
175
+ const authConfigPath = path.join(pagesDir, '..', 'lumenjs.auth.ts');
176
+ if (fs.existsSync(authConfigPath)) {
177
+ const { loadAuthConfig } = await import('../../auth/config.js');
178
+ const authCfg = await loadAuthConfig(path.join(pagesDir, '..'), server.ssrLoadModule.bind(server));
179
+ if (authCfg) {
180
+ // Try bearer token first
181
+ const authHeader = req.headers.authorization;
182
+ if (authHeader?.startsWith('Bearer ')) {
183
+ const { verifyAccessToken } = await import('../../auth/token.js');
184
+ const tokenUser = verifyAccessToken(authHeader.slice(7), authCfg.session.secret);
185
+ if (tokenUser) {
186
+ user = tokenUser;
187
+ req.nkAuth = { user: tokenUser, session: { accessToken: authHeader.slice(7), expiresAt: 0, user: tokenUser } };
188
+ }
189
+ }
190
+ // Fall back to cookie
191
+ if (!user && req.headers.cookie) {
192
+ const { parseSessionCookie, decryptSession } = await import('../../auth/session.js');
193
+ const cookieVal = parseSessionCookie(req.headers.cookie, authCfg.session.cookieName);
194
+ if (cookieVal) {
195
+ const session = await decryptSession(cookieVal, authCfg.session.secret);
196
+ if (session?.user) {
197
+ user = session.user;
198
+ req.nkAuth = { user: session.user, session };
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ catch { }
206
+ }
207
+ const result = await mod.loader({ params, query, url: pagePath, headers: req.headers, locale, user });
161
208
  if (isRedirectResponse(result)) {
162
209
  res.statusCode = result.status || 302;
163
210
  res.setHeader('Location', result.location);
@@ -197,8 +244,8 @@ export function lumenLoadersPlugin(pagesDir) {
197
244
  // Apply to page files and layout files within the pages directory
198
245
  if (!id.startsWith(pagesDir) || !id.endsWith('.ts'))
199
246
  return;
200
- const hasLoader = code.includes('export') && code.includes('loader') && /export\s+(async\s+)?function\s+loader\s*\(/.test(code);
201
- const hasSubscribe = code.includes('export') && code.includes('subscribe') && /export\s+(async\s+)?function\s+subscribe\s*\(/.test(code);
247
+ const hasLoader = hasTopLevelServerFunction(code, 'loader');
248
+ const hasSubscribe = hasTopLevelServerFunction(code, 'subscribe');
202
249
  if (!hasLoader && !hasSubscribe)
203
250
  return;
204
251
  let result = code;
@@ -225,8 +272,13 @@ export function lumenLoadersPlugin(pagesDir) {
225
272
  * GET /__nk_loader/__layout/?__dir=dashboard
226
273
  */
227
274
  async function handleLayoutLoader(server, pagesDir, dir, req, res) {
228
- // Resolve the layout file from the directory
229
- const layoutDir = path.join(pagesDir, dir);
275
+ // Resolve the layout file from the directory (validate path stays within pagesDir)
276
+ const layoutDir = path.resolve(pagesDir, dir);
277
+ if (!layoutDir.startsWith(path.resolve(pagesDir) + path.sep) && layoutDir !== path.resolve(pagesDir)) {
278
+ res.writeHead(400, { 'Content-Type': 'application/json' });
279
+ res.end(JSON.stringify({ error: 'Invalid layout directory' }));
280
+ return;
281
+ }
230
282
  let layoutFile = null;
231
283
  for (const ext of ['.ts', '.js']) {
232
284
  const p = path.join(layoutDir, `_layout${ext}`);
@@ -261,7 +313,37 @@ async function handleLayoutLoader(server, pagesDir, dir, req, res) {
261
313
  }
262
314
  }
263
315
  const locale = query.__locale;
264
- const result = await mod.loader({ params: {}, query: {}, url: `/__layout/${dir}`, headers: req.headers, locale });
316
+ // If auth middleware hasn't populated nkAuth, try to parse from cookie or bearer token
317
+ let user = req.nkAuth?.user ?? null;
318
+ if (!user) {
319
+ try {
320
+ const authConfigPath = path.join(pagesDir, '..', 'lumenjs.auth.ts');
321
+ if (fs.existsSync(authConfigPath)) {
322
+ const { loadAuthConfig } = await import('../../auth/config.js');
323
+ const authCfg = await loadAuthConfig(path.join(pagesDir, '..'), server.ssrLoadModule.bind(server));
324
+ if (authCfg) {
325
+ const authHeader = req.headers.authorization;
326
+ if (authHeader?.startsWith('Bearer ')) {
327
+ const { verifyAccessToken } = await import('../../auth/token.js');
328
+ const tokenUser = verifyAccessToken(authHeader.slice(7), authCfg.session.secret);
329
+ if (tokenUser)
330
+ user = tokenUser;
331
+ }
332
+ if (!user && req.headers.cookie) {
333
+ const { parseSessionCookie, decryptSession } = await import('../../auth/session.js');
334
+ const cookieVal = parseSessionCookie(req.headers.cookie, authCfg.session.cookieName);
335
+ if (cookieVal) {
336
+ const session = await decryptSession(cookieVal, authCfg.session.secret);
337
+ if (session?.user)
338
+ user = session.user;
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+ catch { }
345
+ }
346
+ const result = await mod.loader({ params: {}, query: {}, url: `/__layout/${dir}`, headers: req.headers, locale, user });
265
347
  if (isRedirectResponse(result)) {
266
348
  res.statusCode = result.status || 302;
267
349
  res.setHeader('Location', result.location);
@@ -362,14 +444,33 @@ function findDynamicPage(baseDir, segments) {
362
444
  }
363
445
  return null;
364
446
  }
447
+ /**
448
+ * Check if code has a top-level export of a server function (before the class definition).
449
+ * In LumenJS, loader/subscribe are always declared before `export class`.
450
+ */
451
+ function hasTopLevelServerFunction(code, fnName) {
452
+ const classStart = code.search(/export\s+class\s+\w+/);
453
+ const fnRegex = new RegExp(`export\\s+(async\\s+)?function\\s+${fnName}\\s*\\(`);
454
+ const match = fnRegex.exec(code);
455
+ if (!match)
456
+ return false;
457
+ // Real server functions appear before the class; code examples appear inside the class
458
+ if (classStart >= 0 && match.index > classStart)
459
+ return false;
460
+ return true;
461
+ }
365
462
  /**
366
463
  * Strip a named server-side function (loader/subscribe) from client code using brace-depth tracking.
367
464
  */
368
465
  function stripServerFunction(code, fnName) {
466
+ const classStart = code.search(/export\s+class\s+\w+/);
369
467
  const regex = new RegExp(`export\\s+(async\\s+)?function\\s+${fnName}\\s*\\(`);
370
- const match = code.match(regex);
468
+ const match = regex.exec(code);
371
469
  if (!match)
372
470
  return code;
471
+ // Only strip if the match is before the class (top-level, not inside a code example)
472
+ if (classStart >= 0 && match.index > classStart)
473
+ return code;
373
474
  const startIdx = match.index;
374
475
  let parenDepth = 1;
375
476
  let sigIdx = startIdx + match[0].length;
@@ -386,9 +487,36 @@ function stripServerFunction(code, fnName) {
386
487
  let depth = 1;
387
488
  let i = braceStart + 1;
388
489
  while (i < code.length && depth > 0) {
389
- if (code[i] === '{')
490
+ const ch = code[i];
491
+ // Skip string literals and template literals to avoid counting braces inside them
492
+ if (ch === "'" || ch === '"' || ch === '`') {
493
+ const quote = ch;
494
+ i++;
495
+ while (i < code.length && code[i] !== quote) {
496
+ if (code[i] === '\\')
497
+ i++; // skip escaped char
498
+ i++;
499
+ }
500
+ i++; // skip closing quote
501
+ continue;
502
+ }
503
+ // Skip single-line comments
504
+ if (ch === '/' && code[i + 1] === '/') {
505
+ while (i < code.length && code[i] !== '\n')
506
+ i++;
507
+ continue;
508
+ }
509
+ // Skip multi-line comments
510
+ if (ch === '/' && code[i + 1] === '*') {
511
+ i += 2;
512
+ while (i < code.length - 1 && !(code[i] === '*' && code[i + 1] === '/'))
513
+ i++;
514
+ i += 2;
515
+ continue;
516
+ }
517
+ if (ch === '{')
390
518
  depth++;
391
- else if (code[i] === '}')
519
+ else if (ch === '}')
392
520
  depth--;
393
521
  i++;
394
522
  }
@@ -401,7 +529,12 @@ function stripServerFunction(code, fnName) {
401
529
  * GET /__nk_subscribe/__layout/?__dir=<dir>
402
530
  */
403
531
  async function handleLayoutSubscribe(server, pagesDir, dir, query, req, res) {
404
- const layoutDir = path.join(pagesDir, dir);
532
+ const layoutDir = path.resolve(pagesDir, dir);
533
+ if (!layoutDir.startsWith(path.resolve(pagesDir) + path.sep) && layoutDir !== path.resolve(pagesDir)) {
534
+ res.writeHead(400, { 'Content-Type': 'application/json' });
535
+ res.end(JSON.stringify({ error: 'Invalid layout directory' }));
536
+ return;
537
+ }
405
538
  let layoutFile = null;
406
539
  for (const ext of ['.ts', '.js']) {
407
540
  const p = path.join(layoutDir, `_layout${ext}`);
@@ -432,7 +565,7 @@ async function handleLayoutSubscribe(server, pagesDir, dir, query, req, res) {
432
565
  const push = (data) => {
433
566
  res.write(`data: ${JSON.stringify(data)}\n\n`);
434
567
  };
435
- const cleanup = mod.subscribe({ params: {}, push, headers: req.headers, locale });
568
+ const cleanup = mod.subscribe({ params: {}, push, headers: req.headers, locale, user: req.nkAuth?.user ?? null });
436
569
  res.on('close', () => {
437
570
  if (typeof cleanup === 'function')
438
571
  cleanup();