@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
@@ -0,0 +1,23 @@
1
+ /**
2
+ * <nk-error-boundary> — Client-side error boundary for Lit/web component pages.
3
+ *
4
+ * Wraps page content via <slot>. If a child throws during render or in an
5
+ * event handler, catches the error and displays a fallback UI.
6
+ *
7
+ * Usage:
8
+ * <nk-error-boundary>
9
+ * <my-page></my-page>
10
+ * </nk-error-boundary>
11
+ *
12
+ * Attributes:
13
+ * fallback-message — Custom message shown on error (default: "Something went wrong")
14
+ */
15
+ declare class NkErrorBoundary extends HTMLElement {
16
+ private hasError;
17
+ private caughtError;
18
+ connectedCallback(): void;
19
+ disconnectedCallback(): void;
20
+ private handleError;
21
+ private showFallback;
22
+ private recover;
23
+ }
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * <nk-error-boundary> — Client-side error boundary for Lit/web component pages.
4
+ *
5
+ * Wraps page content via <slot>. If a child throws during render or in an
6
+ * event handler, catches the error and displays a fallback UI.
7
+ *
8
+ * Usage:
9
+ * <nk-error-boundary>
10
+ * <my-page></my-page>
11
+ * </nk-error-boundary>
12
+ *
13
+ * Attributes:
14
+ * fallback-message — Custom message shown on error (default: "Something went wrong")
15
+ */
16
+ class NkErrorBoundary extends HTMLElement {
17
+ constructor() {
18
+ super(...arguments);
19
+ this.hasError = false;
20
+ this.caughtError = null;
21
+ this.handleError = (event) => {
22
+ event.stopPropagation();
23
+ this.showFallback(event.error || new Error(event.message));
24
+ };
25
+ }
26
+ connectedCallback() {
27
+ // Listen for unhandled errors from slotted children
28
+ this.addEventListener('error', this.handleError);
29
+ // Create a slot for normal content
30
+ if (!this.shadowRoot) {
31
+ const shadow = this.attachShadow({ mode: 'open' });
32
+ shadow.innerHTML = `
33
+ <style>
34
+ :host { display: contents; }
35
+ .nk-error-fallback {
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: center;
39
+ justify-content: center;
40
+ padding: 2rem;
41
+ min-height: 200px;
42
+ font-family: system-ui, -apple-system, sans-serif;
43
+ color: #dc2626;
44
+ text-align: center;
45
+ }
46
+ .nk-error-fallback h2 {
47
+ margin: 0 0 0.5rem;
48
+ font-size: 1.25rem;
49
+ font-weight: 600;
50
+ }
51
+ .nk-error-fallback p {
52
+ margin: 0 0 1rem;
53
+ color: #6b7280;
54
+ font-size: 0.875rem;
55
+ }
56
+ .nk-error-fallback button {
57
+ padding: 0.5rem 1rem;
58
+ border: 1px solid #d1d5db;
59
+ border-radius: 0.375rem;
60
+ background: white;
61
+ color: #374151;
62
+ font-size: 0.875rem;
63
+ cursor: pointer;
64
+ }
65
+ .nk-error-fallback button:hover {
66
+ background: #f9fafb;
67
+ }
68
+ .nk-error-hidden { display: none; }
69
+ </style>
70
+ <slot></slot>
71
+ <div class="nk-error-fallback nk-error-hidden">
72
+ <h2></h2>
73
+ <p></p>
74
+ <button>Try again</button>
75
+ </div>
76
+ `;
77
+ const button = shadow.querySelector('button');
78
+ button.addEventListener('click', () => this.recover());
79
+ }
80
+ }
81
+ disconnectedCallback() {
82
+ this.removeEventListener('error', this.handleError);
83
+ }
84
+ showFallback(error) {
85
+ this.hasError = true;
86
+ this.caughtError = error;
87
+ const shadow = this.shadowRoot;
88
+ if (!shadow)
89
+ return;
90
+ const slot = shadow.querySelector('slot');
91
+ const fallback = shadow.querySelector('.nk-error-fallback');
92
+ const message = this.getAttribute('fallback-message') || 'Something went wrong';
93
+ slot.classList.add('nk-error-hidden');
94
+ fallback.classList.remove('nk-error-hidden');
95
+ fallback.querySelector('h2').textContent = message;
96
+ fallback.querySelector('p').textContent = error.message || 'An unexpected error occurred.';
97
+ // Dispatch custom event for external error tracking
98
+ this.dispatchEvent(new CustomEvent('nk-error', {
99
+ bubbles: true,
100
+ composed: true,
101
+ detail: { error },
102
+ }));
103
+ }
104
+ recover() {
105
+ this.hasError = false;
106
+ this.caughtError = null;
107
+ const shadow = this.shadowRoot;
108
+ if (!shadow)
109
+ return;
110
+ const slot = shadow.querySelector('slot');
111
+ const fallback = shadow.querySelector('.nk-error-fallback');
112
+ fallback.classList.add('nk-error-hidden');
113
+ slot.classList.remove('nk-error-hidden');
114
+ // Re-trigger the current route to re-render
115
+ window.dispatchEvent(new PopStateEvent('popstate'));
116
+ }
117
+ }
118
+ if (!customElements.get('nk-error-boundary')) {
119
+ customElements.define('nk-error-boundary', NkErrorBoundary);
120
+ }
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * LumenJS i18n runtime — provides translation lookup, locale management,
3
3
  * and translation loading for both SSR and client-side navigation.
4
+ *
5
+ * State is stored on globalThis so that it survives Vite's SSR module
6
+ * invalidation. During dev SSR, page and layout modules may each get a
7
+ * separate copy of this module after cache invalidation — globalThis
8
+ * ensures they all read from the same translation map.
4
9
  */
5
10
  /**
6
11
  * Look up a translation key. Returns the translated string, or the key itself
@@ -31,7 +36,7 @@ export declare function initI18n(config: {
31
36
  }, locale: string, trans: Record<string, string>): void;
32
37
  /**
33
38
  * Load translations for a locale from the server and swap them in.
34
- * Used during client-side locale switches.
39
+ * Used during client-side locale switches and HMR updates.
35
40
  */
36
41
  export declare function loadTranslations(locale: string): Promise<void>;
37
42
  /**
@@ -1,31 +1,38 @@
1
1
  /**
2
2
  * LumenJS i18n runtime — provides translation lookup, locale management,
3
3
  * and translation loading for both SSR and client-side navigation.
4
+ *
5
+ * State is stored on globalThis so that it survives Vite's SSR module
6
+ * invalidation. During dev SSR, page and layout modules may each get a
7
+ * separate copy of this module after cache invalidation — globalThis
8
+ * ensures they all read from the same translation map.
4
9
  */
5
- let currentLocale = 'en';
6
- let translations = {};
7
- let i18nConfig = null;
10
+ const G = globalThis;
11
+ if (!G.__nk_i18n) {
12
+ G.__nk_i18n = { locale: 'en', translations: {}, config: null };
13
+ }
14
+ const state = G.__nk_i18n;
8
15
  /**
9
16
  * Look up a translation key. Returns the translated string, or the key itself
10
17
  * if no translation is found.
11
18
  */
12
19
  export function t(key) {
13
- return translations[key] ?? key;
20
+ return state.translations[key] ?? key;
14
21
  }
15
22
  /** Returns the current locale. */
16
23
  export function getLocale() {
17
- return currentLocale;
24
+ return state.locale;
18
25
  }
19
26
  /** Returns the i18n config, or null if i18n is not enabled. */
20
27
  export function getI18nConfig() {
21
- return i18nConfig;
28
+ return state.config;
22
29
  }
23
30
  /**
24
31
  * Switch to a new locale. Navigates to the same pathname under the new
25
32
  * locale prefix and sets the `nk-locale` cookie.
26
33
  */
27
34
  export function setLocale(locale) {
28
- if (!i18nConfig || !i18nConfig.locales.includes(locale))
35
+ if (!state.config || !state.config.locales.includes(locale))
29
36
  return;
30
37
  document.cookie = `nk-locale=${locale};path=/;max-age=${60 * 60 * 24 * 365};SameSite=Lax`;
31
38
  const pathname = stripLocalePrefix(location.pathname);
@@ -37,13 +44,13 @@ export function setLocale(locale) {
37
44
  * Called during hydration (from SSR data) or on first load.
38
45
  */
39
46
  export function initI18n(config, locale, trans) {
40
- i18nConfig = config;
41
- currentLocale = locale;
42
- translations = trans;
47
+ state.config = config;
48
+ state.locale = locale;
49
+ state.translations = trans;
43
50
  }
44
51
  /**
45
52
  * Load translations for a locale from the server and swap them in.
46
- * Used during client-side locale switches.
53
+ * Used during client-side locale switches and HMR updates.
47
54
  */
48
55
  export async function loadTranslations(locale) {
49
56
  const res = await fetch(`/__nk_i18n/${locale}.json`);
@@ -51,8 +58,22 @@ export async function loadTranslations(locale) {
51
58
  console.error(`[i18n] Failed to load translations for locale "${locale}"`);
52
59
  return;
53
60
  }
54
- translations = await res.json();
55
- currentLocale = locale;
61
+ state.translations = await res.json();
62
+ state.locale = locale;
63
+ }
64
+ /**
65
+ * Register the HMR reload handler on the global scope.
66
+ * The i18n Vite plugin injects an inline script that calls this function
67
+ * when a locale file changes — ensuring translations are updated in this
68
+ * module instance (not a duplicate created by Vite's cache-busting).
69
+ */
70
+ if (typeof window !== 'undefined') {
71
+ window.__lumenjs_i18n_reload = async (locale) => {
72
+ if (locale !== state.locale)
73
+ return false;
74
+ await loadTranslations(locale);
75
+ return true;
76
+ };
56
77
  }
57
78
  /**
58
79
  * Strip the locale prefix from a pathname.
@@ -60,10 +81,10 @@ export async function loadTranslations(locale) {
60
81
  * /about → /about
61
82
  */
62
83
  export function stripLocalePrefix(pathname) {
63
- if (!i18nConfig)
84
+ if (!state.config)
64
85
  return pathname;
65
- for (const loc of i18nConfig.locales) {
66
- if (loc === i18nConfig.defaultLocale && !i18nConfig.prefixDefault)
86
+ for (const loc of state.config.locales) {
87
+ if (loc === state.config.defaultLocale && !state.config.prefixDefault)
67
88
  continue;
68
89
  if (pathname === `/${loc}` || pathname.startsWith(`/${loc}/`)) {
69
90
  return pathname.slice(loc.length + 1) || '/';
@@ -77,9 +98,9 @@ export function stripLocalePrefix(pathname) {
77
98
  * (en, /about) → /about (when prefixDefault=false)
78
99
  */
79
100
  export function buildLocalePath(locale, pathname) {
80
- if (!i18nConfig)
101
+ if (!state.config)
81
102
  return pathname;
82
- if (locale === i18nConfig.defaultLocale && !i18nConfig.prefixDefault) {
103
+ if (locale === state.config.defaultLocale && !state.config.prefixDefault) {
83
104
  return pathname;
84
105
  }
85
106
  return `/${locale}${pathname === '/' ? '' : pathname}` || `/${locale}`;
@@ -89,12 +110,12 @@ export function buildLocalePath(locale, pathname) {
89
110
  * Returns the locale and the pathname with the prefix stripped.
90
111
  */
91
112
  export function detectLocaleFromPath(pathname) {
92
- if (!i18nConfig)
113
+ if (!state.config)
93
114
  return { locale: 'en', pathname };
94
- for (const loc of i18nConfig.locales) {
115
+ for (const loc of state.config.locales) {
95
116
  if (pathname === `/${loc}` || pathname.startsWith(`/${loc}/`)) {
96
117
  return { locale: loc, pathname: pathname.slice(loc.length + 1) || '/' };
97
118
  }
98
119
  }
99
- return { locale: i18nConfig.defaultLocale, pathname };
120
+ return { locale: state.config.defaultLocale, pathname };
100
121
  }
@@ -1,3 +1,8 @@
1
+ export declare function getCachedLoaderData(key: string): any | undefined;
2
+ export declare function prefetchLoaderData(pathname: string, params: Record<string, string>): Promise<any>;
3
+ export declare function prefetchLayoutLoaderData(dir: string): Promise<any>;
1
4
  export declare function fetchLoaderData(pathname: string, params: Record<string, string>): Promise<any>;
2
5
  export declare function fetchLayoutLoaderData(dir: string): Promise<any>;
6
+ export declare function connectSubscribe(pathname: string, params: Record<string, string>): EventSource;
7
+ export declare function connectLayoutSubscribe(dir: string): EventSource;
3
8
  export declare function render404(pathname: string): string;
@@ -1,5 +1,65 @@
1
1
  import { getI18nConfig, getLocale } from './i18n.js';
2
+ const PREFETCH_TTL = 30_000; // 30 seconds
3
+ const MAX_PREFETCH_CACHE_SIZE = 50;
4
+ const prefetchCache = new Map();
5
+ const inflightRequests = new Map();
6
+ // Periodic sweep of expired prefetch entries (every 60s)
7
+ if (typeof setInterval !== 'undefined') {
8
+ setInterval(() => {
9
+ const now = Date.now();
10
+ for (const [key, entry] of prefetchCache) {
11
+ if (now - entry.timestamp > PREFETCH_TTL)
12
+ prefetchCache.delete(key);
13
+ }
14
+ }, 60_000);
15
+ }
16
+ export function getCachedLoaderData(key) {
17
+ const entry = prefetchCache.get(key);
18
+ if (!entry)
19
+ return undefined;
20
+ if (Date.now() - entry.timestamp > PREFETCH_TTL) {
21
+ prefetchCache.delete(key);
22
+ return undefined;
23
+ }
24
+ return entry.data;
25
+ }
26
+ function setCachedLoaderData(key, data) {
27
+ // Evict oldest entry if cache is full
28
+ if (prefetchCache.size >= MAX_PREFETCH_CACHE_SIZE) {
29
+ const firstKey = prefetchCache.keys().next().value;
30
+ if (firstKey)
31
+ prefetchCache.delete(firstKey);
32
+ }
33
+ prefetchCache.set(key, { data, timestamp: Date.now() });
34
+ }
35
+ export async function prefetchLoaderData(pathname, params) {
36
+ const cacheKey = `page:${pathname}`;
37
+ const cached = getCachedLoaderData(cacheKey);
38
+ if (cached !== undefined)
39
+ return cached;
40
+ const data = await fetchLoaderDataRaw(pathname, params);
41
+ setCachedLoaderData(cacheKey, data);
42
+ return data;
43
+ }
44
+ export async function prefetchLayoutLoaderData(dir) {
45
+ const cacheKey = `layout:${dir}`;
46
+ const cached = getCachedLoaderData(cacheKey);
47
+ if (cached !== undefined)
48
+ return cached;
49
+ const data = await fetchLayoutLoaderDataRaw(dir);
50
+ setCachedLoaderData(cacheKey, data);
51
+ return data;
52
+ }
2
53
  export async function fetchLoaderData(pathname, params) {
54
+ const cacheKey = `page:${pathname}`;
55
+ const cached = getCachedLoaderData(cacheKey);
56
+ if (cached !== undefined) {
57
+ prefetchCache.delete(cacheKey);
58
+ return cached;
59
+ }
60
+ return fetchLoaderDataRaw(pathname, params);
61
+ }
62
+ async function fetchLoaderDataRaw(pathname, params) {
3
63
  const url = new URL(`/__nk_loader${pathname}`, location.origin);
4
64
  if (Object.keys(params).length > 0) {
5
65
  url.searchParams.set('__params', JSON.stringify(params));
@@ -8,38 +68,83 @@ export async function fetchLoaderData(pathname, params) {
8
68
  if (config) {
9
69
  url.searchParams.set('__locale', getLocale());
10
70
  }
11
- const res = await fetch(url.toString());
12
- if (!res.ok) {
13
- throw new Error(`Loader returned ${res.status}`);
14
- }
15
- const data = await res.json();
16
- if (data?.__nk_no_loader)
17
- return undefined;
18
- return data;
71
+ const key = url.toString();
72
+ const inflight = inflightRequests.get(key);
73
+ if (inflight)
74
+ return inflight;
75
+ const promise = fetch(key)
76
+ .then(async (res) => {
77
+ if (!res.ok)
78
+ throw new Error(`Loader returned ${res.status}`);
79
+ const data = await res.json();
80
+ return data?.__nk_no_loader ? undefined : data;
81
+ })
82
+ .finally(() => inflightRequests.delete(key));
83
+ inflightRequests.set(key, promise);
84
+ return promise;
19
85
  }
20
86
  export async function fetchLayoutLoaderData(dir) {
87
+ const cacheKey = `layout:${dir}`;
88
+ const cached = getCachedLoaderData(cacheKey);
89
+ if (cached !== undefined) {
90
+ prefetchCache.delete(cacheKey);
91
+ return cached;
92
+ }
93
+ return fetchLayoutLoaderDataRaw(dir);
94
+ }
95
+ async function fetchLayoutLoaderDataRaw(dir) {
21
96
  const url = new URL(`/__nk_loader/__layout/`, location.origin);
22
97
  url.searchParams.set('__dir', dir);
23
98
  const config = getI18nConfig();
24
99
  if (config) {
25
100
  url.searchParams.set('__locale', getLocale());
26
101
  }
27
- const res = await fetch(url.toString());
28
- if (!res.ok) {
29
- throw new Error(`Layout loader returned ${res.status}`);
102
+ const key = url.toString();
103
+ const inflight = inflightRequests.get(key);
104
+ if (inflight)
105
+ return inflight;
106
+ const promise = fetch(key)
107
+ .then(async (res) => {
108
+ if (!res.ok)
109
+ throw new Error(`Layout loader returned ${res.status}`);
110
+ const data = await res.json();
111
+ return data?.__nk_no_loader ? undefined : data;
112
+ })
113
+ .finally(() => inflightRequests.delete(key));
114
+ inflightRequests.set(key, promise);
115
+ return promise;
116
+ }
117
+ export function connectSubscribe(pathname, params) {
118
+ const url = new URL(`/__nk_subscribe${pathname}`, location.origin);
119
+ if (Object.keys(params).length > 0) {
120
+ url.searchParams.set('__params', JSON.stringify(params));
30
121
  }
31
- const data = await res.json();
32
- if (data?.__nk_no_loader)
33
- return undefined;
34
- return data;
122
+ const config = getI18nConfig();
123
+ if (config) {
124
+ url.searchParams.set('__locale', getLocale());
125
+ }
126
+ return new EventSource(url.toString());
127
+ }
128
+ export function connectLayoutSubscribe(dir) {
129
+ const url = new URL('/__nk_subscribe/__layout/', location.origin);
130
+ url.searchParams.set('__dir', dir);
131
+ const config = getI18nConfig();
132
+ if (config) {
133
+ url.searchParams.set('__locale', getLocale());
134
+ }
135
+ return new EventSource(url.toString());
136
+ }
137
+ function escapeHtml(text) {
138
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
35
139
  }
36
140
  export function render404(pathname) {
141
+ const safe = escapeHtml(pathname);
37
142
  return `<div style="display:flex;align-items:center;justify-content:center;min-height:80vh;font-family:system-ui,-apple-system,sans-serif;padding:2rem">
38
143
  <div style="text-align:center;max-width:400px">
39
144
  <div style="font-size:5rem;font-weight:200;letter-spacing:-2px;color:#cbd5e1;line-height:1">404</div>
40
145
  <div style="width:32px;height:2px;background:#e2e8f0;border-radius:1px;margin:1.25rem auto"></div>
41
146
  <h1 style="font-size:1rem;font-weight:500;color:#334155;margin:1.25rem 0 .5rem">Page not found</h1>
42
- <p style="color:#94a3b8;font-size:.8125rem;line-height:1.5;margin:0 0 2rem"><code style="background:#f8fafc;padding:.125rem .375rem;border-radius:3px;font-size:.75rem;color:#64748b;border:1px solid #f1f5f9">${pathname}</code> doesn't exist</p>
147
+ <p style="color:#94a3b8;font-size:.8125rem;line-height:1.5;margin:0 0 2rem"><code style="background:#f8fafc;padding:.125rem .375rem;border-radius:3px;font-size:.75rem;color:#64748b;border:1px solid #f1f5f9">${safe}</code> doesn't exist</p>
43
148
  <a href="/" style="display:inline-flex;align-items:center;gap:.375rem;padding:.4375rem 1rem;background:#f8fafc;color:#475569;border:1px solid #e2e8f0;border-radius:6px;font-size:.8125rem;font-weight:400;text-decoration:none;transition:all .15s">
44
149
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
45
150
  Back to home
@@ -15,6 +15,16 @@ export async function hydrateInitialRoute(routes, outlet, matchRoute, onHydrated
15
15
  catch { /* ignore */ }
16
16
  i18nScript.remove();
17
17
  }
18
+ // Read auth data and init before route matching
19
+ const authScript = document.getElementById('__nk_auth__');
20
+ if (authScript) {
21
+ try {
22
+ const { initAuth } = await import('@lumenjs/auth');
23
+ initAuth(JSON.parse(authScript.textContent || ''));
24
+ }
25
+ catch { }
26
+ authScript.remove();
27
+ }
18
28
  // Strip locale prefix for route matching (routes are locale-agnostic)
19
29
  const config = getI18nConfig();
20
30
  const matchPath = config ? stripLocalePrefix(location.pathname) : location.pathname;
@@ -45,6 +55,19 @@ export async function hydrateInitialRoute(routes, outlet, matchRoute, onHydrated
45
55
  }
46
56
  }
47
57
  }
58
+ /** Spread loader data as individual properties on an element. */
59
+ const BLOCKED = new Set(['__proto__', 'constructor', 'prototype',
60
+ 'innerHTML', 'outerHTML', 'textContent',
61
+ 'render', 'connectedCallback', 'disconnectedCallback']);
62
+ function spreadData(el, data) {
63
+ if (data && typeof data === 'object') {
64
+ for (const [key, value] of Object.entries(data)) {
65
+ if (!BLOCKED.has(key)) {
66
+ el[key] = value;
67
+ }
68
+ }
69
+ }
70
+ }
48
71
  // Load each layout module and immediately set loaderData on the
49
72
  // existing DOM element BEFORE the next await yields to microtasks.
50
73
  for (const layout of layouts) {
@@ -53,6 +76,7 @@ export async function hydrateInitialRoute(routes, outlet, matchRoute, onHydrated
53
76
  const data = layoutDataMap.get(layout.loaderPath ?? '');
54
77
  if (data !== undefined) {
55
78
  existingLayout.loaderData = data;
79
+ spreadData(existingLayout, data);
56
80
  }
57
81
  }
58
82
  if (layout.load && !customElements.get(layout.tagName)) {
@@ -66,6 +90,7 @@ export async function hydrateInitialRoute(routes, outlet, matchRoute, onHydrated
66
90
  const existingPage = outlet?.querySelector(match.route.tagName);
67
91
  if (existingPage && pageData !== undefined) {
68
92
  existingPage.loaderData = pageData;
93
+ spreadData(existingPage, pageData);
69
94
  }
70
95
  // Load the page module (registers element, triggers hydration microtask)
71
96
  if (match.route.load && !customElements.get(match.route.tagName)) {
@@ -1,6 +1,7 @@
1
1
  export interface LayoutInfo {
2
2
  tagName: string;
3
3
  hasLoader?: boolean;
4
+ hasSubscribe?: boolean;
4
5
  load?: () => Promise<any>;
5
6
  loaderPath?: string;
6
7
  }
@@ -8,6 +9,8 @@ export interface Route {
8
9
  path: string;
9
10
  tagName: string;
10
11
  hasLoader?: boolean;
12
+ hasSubscribe?: boolean;
13
+ hasMeta?: boolean;
11
14
  load?: () => Promise<any>;
12
15
  layouts?: LayoutInfo[];
13
16
  pattern?: RegExp;
@@ -23,17 +26,34 @@ export declare class NkRouter {
23
26
  private outlet;
24
27
  private currentTag;
25
28
  private currentLayoutTags;
29
+ private subscriptions;
30
+ private siteTitle;
26
31
  params: Record<string, string>;
27
32
  constructor(routes: Route[], outlet: HTMLElement, hydrate?: boolean);
28
33
  private compilePattern;
29
- navigate(pathname: string, pushState?: boolean): Promise<void>;
34
+ private cleanupSubscriptions;
35
+ navigate(fullPath: string, pushState?: boolean): Promise<void>;
36
+ private setupSubscriptions;
30
37
  private matchRoute;
31
38
  private renderRoute;
32
39
  private buildLayoutTree;
40
+ /** Spread loader data as individual properties on an element. */
41
+ private spreadData;
33
42
  private createPageElement;
43
+ private findPageElement;
44
+ /**
45
+ * Resolve the page title from the route's meta export and update
46
+ * document.title, the aria-live announcer, and focus.
47
+ */
48
+ private updatePageMeta;
34
49
  private handleLinkClick;
50
+ prefetch(fullPath: string): Promise<void>;
35
51
  /** Strip locale prefix from a path for internal route matching. */
36
52
  private stripLocale;
37
53
  /** Prepend locale prefix for browser-facing URLs. */
38
54
  private withLocale;
39
55
  }
56
+ /** Navigate via the client-side router. Falls back to full reload for unknown routes. */
57
+ export declare function navigate(href: string): void;
58
+ /** Programmatically prefetch a route's JS chunks and loader data. */
59
+ export declare function prefetch(href: string): void;