@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
@@ -15,7 +15,6 @@ import { installDomShims } from '../../shared/dom-shims.js';
15
15
  * return { item: data, timestamp: Date.now() };
16
16
  * }
17
17
  *
18
- * @customElement('page-item')
19
18
  * export class PageItem extends LitElement {
20
19
  * @property({ type: Object }) loaderData = {};
21
20
  * render() {
@@ -31,6 +30,83 @@ export function lumenLoadersPlugin(pagesDir) {
31
30
  return {
32
31
  name: 'lumenjs-loaders',
33
32
  configureServer(server) {
33
+ // SSE subscribe middleware
34
+ server.middlewares.use(async (req, res, next) => {
35
+ if (!req.url?.startsWith('/__nk_subscribe/')) {
36
+ return next();
37
+ }
38
+ const [pathname, queryString] = req.url.split('?');
39
+ // Parse query params
40
+ const query = {};
41
+ if (queryString) {
42
+ for (const pair of queryString.split('&')) {
43
+ const [key, val] = pair.split('=');
44
+ query[decodeURIComponent(key)] = decodeURIComponent(val || '');
45
+ }
46
+ }
47
+ // Handle layout subscribe: /__nk_subscribe/__layout/?__dir=<dir>
48
+ if (pathname === '/__nk_subscribe/__layout/' || pathname === '/__nk_subscribe/__layout') {
49
+ const dir = query.__dir || '';
50
+ await handleLayoutSubscribe(server, pagesDir, dir, query, req, res);
51
+ return;
52
+ }
53
+ const pagePath = pathname.replace('/__nk_subscribe', '') || '/';
54
+ // Parse URL params
55
+ let params = {};
56
+ if (query.__params) {
57
+ try {
58
+ params = JSON.parse(query.__params);
59
+ }
60
+ catch { /* ignore */ }
61
+ delete query.__params;
62
+ }
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
+ }
69
+ if (!filePath) {
70
+ res.statusCode = 404;
71
+ res.end();
72
+ return;
73
+ }
74
+ if (Object.keys(params).length === 0) {
75
+ Object.assign(params, extractRouteParams(pagesDir, pagePath, filePath));
76
+ }
77
+ try {
78
+ installDomShims();
79
+ const mod = await server.ssrLoadModule(filePath);
80
+ if (!mod.subscribe || typeof mod.subscribe !== 'function') {
81
+ res.statusCode = 204;
82
+ res.end();
83
+ return;
84
+ }
85
+ // Set SSE headers
86
+ res.writeHead(200, {
87
+ 'Content-Type': 'text/event-stream',
88
+ 'Cache-Control': 'no-cache',
89
+ 'Connection': 'keep-alive',
90
+ });
91
+ const locale = query.__locale;
92
+ const push = (data) => {
93
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
94
+ };
95
+ const cleanup = mod.subscribe({ params, push, headers: req.headers, locale, user: req.nkAuth?.user ?? null });
96
+ res.on('close', () => {
97
+ if (typeof cleanup === 'function')
98
+ cleanup();
99
+ });
100
+ }
101
+ catch (err) {
102
+ console.error(`[LumenJS] Subscribe error for ${pagePath}:`, err);
103
+ if (!res.headersSent) {
104
+ res.statusCode = 500;
105
+ res.end();
106
+ }
107
+ }
108
+ });
109
+ // Loader middleware
34
110
  server.middlewares.use(async (req, res, next) => {
35
111
  if (!req.url?.startsWith('/__nk_loader/')) {
36
112
  return next();
@@ -60,8 +136,14 @@ export function lumenLoadersPlugin(pagesDir) {
60
136
  catch { /* ignore */ }
61
137
  delete query.__params;
62
138
  }
63
- // Find the page file
139
+ // Find the page file (validate path stays within pagesDir)
64
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
+ }
65
147
  if (!filePath) {
66
148
  res.statusCode = 404;
67
149
  res.setHeader('Content-Type', 'application/json');
@@ -86,7 +168,43 @@ export function lumenLoadersPlugin(pagesDir) {
86
168
  // Extract locale from query if provided by the client router
87
169
  const locale = query.__locale;
88
170
  delete query.__locale;
89
- 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 });
90
208
  if (isRedirectResponse(result)) {
91
209
  res.statusCode = result.status || 302;
92
210
  res.setHeader('Location', result.location);
@@ -126,45 +244,26 @@ export function lumenLoadersPlugin(pagesDir) {
126
244
  // Apply to page files and layout files within the pages directory
127
245
  if (!id.startsWith(pagesDir) || !id.endsWith('.ts'))
128
246
  return;
129
- if (!code.includes('export') || !code.includes('loader'))
130
- return;
131
- const hasLoader = /export\s+(async\s+)?function\s+loader\s*\(/.test(code);
132
- if (!hasLoader)
247
+ const hasLoader = hasTopLevelServerFunction(code, 'loader');
248
+ const hasSubscribe = hasTopLevelServerFunction(code, 'subscribe');
249
+ if (!hasLoader && !hasSubscribe)
133
250
  return;
134
- // Find the loader function by tracking brace depth
135
- const match = code.match(/export\s+(async\s+)?function\s+loader\s*\(/);
136
- if (!match)
137
- return;
138
- const startIdx = match.index;
139
- // Skip past the function signature's closing parenthesis (handles nested braces in type annotations)
140
- let parenDepth = 1;
141
- let sigIdx = startIdx + match[0].length;
142
- while (sigIdx < code.length && parenDepth > 0) {
143
- if (code[sigIdx] === '(')
144
- parenDepth++;
145
- else if (code[sigIdx] === ')')
146
- parenDepth--;
147
- sigIdx++;
251
+ let result = code;
252
+ // Strip loader function
253
+ if (hasLoader) {
254
+ result = stripServerFunction(result, 'loader');
148
255
  }
149
- // Find the opening brace of the function body (after the closing paren and optional return type)
150
- let braceStart = code.indexOf('{', sigIdx);
151
- if (braceStart === -1)
152
- return;
153
- let depth = 1;
154
- let i = braceStart + 1;
155
- while (i < code.length && depth > 0) {
156
- if (code[i] === '{')
157
- depth++;
158
- else if (code[i] === '}')
159
- depth--;
160
- i++;
256
+ // Strip subscribe function
257
+ if (hasSubscribe) {
258
+ result = stripServerFunction(result, 'subscribe');
161
259
  }
162
- // Replace the entire loader function
163
- const transformed = code.substring(0, startIdx)
164
- + '// loader() — runs server-side only'
165
- + code.substring(i);
166
- const withFlag = transformed + '\nexport const __nk_has_loader = true;\n';
167
- return { code: withFlag, map: null };
260
+ if (hasLoader) {
261
+ result += '\nexport const __nk_has_loader = true;\n';
262
+ }
263
+ if (hasSubscribe) {
264
+ result += '\nexport const __nk_has_subscribe = true;\n';
265
+ }
266
+ return { code: result, map: null };
168
267
  },
169
268
  };
170
269
  }
@@ -173,8 +272,13 @@ export function lumenLoadersPlugin(pagesDir) {
173
272
  * GET /__nk_loader/__layout/?__dir=dashboard
174
273
  */
175
274
  async function handleLayoutLoader(server, pagesDir, dir, req, res) {
176
- // Resolve the layout file from the directory
177
- 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
+ }
178
282
  let layoutFile = null;
179
283
  for (const ext of ['.ts', '.js']) {
180
284
  const p = path.join(layoutDir, `_layout${ext}`);
@@ -209,7 +313,37 @@ async function handleLayoutLoader(server, pagesDir, dir, req, res) {
209
313
  }
210
314
  }
211
315
  const locale = query.__locale;
212
- 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 });
213
347
  if (isRedirectResponse(result)) {
214
348
  res.statusCode = result.status || 302;
215
349
  res.setHeader('Location', result.location);
@@ -310,6 +444,141 @@ function findDynamicPage(baseDir, segments) {
310
444
  }
311
445
  return null;
312
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
+ }
462
+ /**
463
+ * Strip a named server-side function (loader/subscribe) from client code using brace-depth tracking.
464
+ */
465
+ function stripServerFunction(code, fnName) {
466
+ const classStart = code.search(/export\s+class\s+\w+/);
467
+ const regex = new RegExp(`export\\s+(async\\s+)?function\\s+${fnName}\\s*\\(`);
468
+ const match = regex.exec(code);
469
+ if (!match)
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;
474
+ const startIdx = match.index;
475
+ let parenDepth = 1;
476
+ let sigIdx = startIdx + match[0].length;
477
+ while (sigIdx < code.length && parenDepth > 0) {
478
+ if (code[sigIdx] === '(')
479
+ parenDepth++;
480
+ else if (code[sigIdx] === ')')
481
+ parenDepth--;
482
+ sigIdx++;
483
+ }
484
+ let braceStart = code.indexOf('{', sigIdx);
485
+ if (braceStart === -1)
486
+ return code;
487
+ let depth = 1;
488
+ let i = braceStart + 1;
489
+ while (i < code.length && depth > 0) {
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 === '{')
518
+ depth++;
519
+ else if (ch === '}')
520
+ depth--;
521
+ i++;
522
+ }
523
+ return code.substring(0, startIdx)
524
+ + `// ${fnName}() — runs server-side only`
525
+ + code.substring(i);
526
+ }
527
+ /**
528
+ * Handle layout subscribe requests in dev mode.
529
+ * GET /__nk_subscribe/__layout/?__dir=<dir>
530
+ */
531
+ async function handleLayoutSubscribe(server, pagesDir, dir, query, req, res) {
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
+ }
538
+ let layoutFile = null;
539
+ for (const ext of ['.ts', '.js']) {
540
+ const p = path.join(layoutDir, `_layout${ext}`);
541
+ if (fs.existsSync(p)) {
542
+ layoutFile = p;
543
+ break;
544
+ }
545
+ }
546
+ if (!layoutFile) {
547
+ res.statusCode = 204;
548
+ res.end();
549
+ return;
550
+ }
551
+ try {
552
+ installDomShims();
553
+ const mod = await server.ssrLoadModule(layoutFile);
554
+ if (!mod.subscribe || typeof mod.subscribe !== 'function') {
555
+ res.statusCode = 204;
556
+ res.end();
557
+ return;
558
+ }
559
+ res.writeHead(200, {
560
+ 'Content-Type': 'text/event-stream',
561
+ 'Cache-Control': 'no-cache',
562
+ 'Connection': 'keep-alive',
563
+ });
564
+ const locale = query.__locale;
565
+ const push = (data) => {
566
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
567
+ };
568
+ const cleanup = mod.subscribe({ params: {}, push, headers: req.headers, locale, user: req.nkAuth?.user ?? null });
569
+ res.on('close', () => {
570
+ if (typeof cleanup === 'function')
571
+ cleanup();
572
+ });
573
+ }
574
+ catch (err) {
575
+ console.error(`[LumenJS] Layout subscribe error for dir=${dir}:`, err);
576
+ if (!res.headersSent) {
577
+ res.statusCode = 500;
578
+ res.end();
579
+ }
580
+ }
581
+ }
313
582
  /**
314
583
  * Extract dynamic route params by comparing URL segments against [param] file path segments.
315
584
  */
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { dirToLayoutTagName, fileHasLoader, filePathToRoute, filePathToTagName } from '../../shared/utils.js';
3
+ import { dirToLayoutTagName, fileHasLoader, fileHasSubscribe, fileHasAuth, fileHasMeta, fileHasStandalone, filePathToRoute, filePathToTagName } from '../../shared/utils.js';
4
4
  const VIRTUAL_MODULE_ID = 'virtual:lumenjs-routes';
5
5
  const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
6
6
  /**
@@ -28,7 +28,7 @@ export function lumenRoutesPlugin(pagesDir) {
28
28
  const tagName = dirToLayoutTagName(relativePath);
29
29
  layouts.push({ dir: relativePath.replace(/\\/g, '/'), filePath, tagName });
30
30
  }
31
- if (entry.isDirectory()) {
31
+ if (entry.isDirectory() && !entry.name.startsWith('_')) {
32
32
  walkForLayouts(baseDir, path.join(relativePath, entry.name), layouts);
33
33
  }
34
34
  }
@@ -55,12 +55,18 @@ export function lumenRoutesPlugin(pagesDir) {
55
55
  function walkDir(baseDir, relativePath, routes) {
56
56
  const fullDir = path.join(baseDir, relativePath);
57
57
  const entries = fs.readdirSync(fullDir, { withFileTypes: true });
58
+ // Check if this subdirectory contains an index file (folder route)
59
+ // Only applies to subdirectories, not the root pages directory
60
+ const hasIndex = relativePath !== '' && entries.some(e => e.isFile() && /^index\.(ts|js)$/.test(e.name));
58
61
  for (const entry of entries) {
59
62
  const entryRelative = path.join(relativePath, entry.name);
60
- if (entry.isDirectory()) {
63
+ if (entry.isDirectory() && !entry.name.startsWith('_')) {
61
64
  walkDir(baseDir, entryRelative, routes);
62
65
  }
63
66
  else if (entry.isFile() && /\.(ts|js)$/.test(entry.name) && !entry.name.startsWith('_')) {
67
+ // In a folder route (has index file), only register the index file and dynamic param files
68
+ if (hasIndex && !/^index\.(ts|js)$/.test(entry.name) && !entry.name.startsWith('['))
69
+ continue;
64
70
  const routePath = filePathToRoute(entryRelative);
65
71
  const componentPath = path.join(pagesDir, entryRelative);
66
72
  const tagName = filePathToTagName(entryRelative);
@@ -98,18 +104,24 @@ export function lumenRoutesPlugin(pagesDir) {
98
104
  const routeArray = routes
99
105
  .map(r => {
100
106
  const hasLoader = fileHasLoader(r.componentPath);
107
+ const hasSubscribe = fileHasSubscribe(r.componentPath);
108
+ const hasAuth = fileHasAuth(r.componentPath);
109
+ const hasMeta = fileHasMeta(r.componentPath);
110
+ const isStandalone = fileHasStandalone(r.componentPath);
101
111
  const componentPath = r.componentPath.replace(/\\/g, '/');
102
- const chain = getLayoutChain(r.componentPath, layouts);
112
+ // Standalone pages skip all layouts
113
+ const chain = isStandalone ? [] : getLayoutChain(r.componentPath, layouts);
103
114
  let layoutsStr = '';
104
115
  if (chain.length > 0) {
105
116
  const items = chain.map(l => {
106
117
  const lHasLoader = fileHasLoader(l.filePath);
118
+ const lHasSubscribe = fileHasSubscribe(l.filePath);
107
119
  const lPath = l.filePath.replace(/\\/g, '/');
108
- return `{ tagName: ${JSON.stringify(l.tagName)}, loaderPath: ${JSON.stringify(l.dir)}${lHasLoader ? ', hasLoader: true' : ''}, load: () => import('${lPath}') }`;
120
+ return `{ tagName: ${JSON.stringify(l.tagName)}, loaderPath: ${JSON.stringify(l.dir)}${lHasLoader ? ', hasLoader: true' : ''}${lHasSubscribe ? ', hasSubscribe: true' : ''}, load: () => import('${lPath}') }`;
109
121
  });
110
122
  layoutsStr = `, layouts: [${items.join(', ')}]`;
111
123
  }
112
- return ` { path: ${JSON.stringify(r.path)}, tagName: ${JSON.stringify(r.tagName)}${hasLoader ? ', hasLoader: true' : ''}, load: () => import('${componentPath}')${layoutsStr} }`;
124
+ return ` { path: ${JSON.stringify(r.path)}, tagName: ${JSON.stringify(r.tagName)}${hasLoader ? ', hasLoader: true' : ''}${hasSubscribe ? ', hasSubscribe: true' : ''}${hasMeta ? ', hasMeta: true' : ''}${hasAuth ? ', __nk_has_auth: true' : ''}, load: () => import('${componentPath}')${layoutsStr} }`;
113
125
  })
114
126
  .join(',\n');
115
127
  return `export const routes = [\n${routeArray}\n];\n`;
@@ -0,0 +1,2 @@
1
+ import { Plugin } from 'vite';
2
+ export declare function lumenSocketIOPlugin(pagesDir: string): Plugin;
@@ -0,0 +1,51 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileHasSocket, filePathToRoute } from '../../shared/utils.js';
4
+ export function lumenSocketIOPlugin(pagesDir) {
5
+ return {
6
+ name: 'lumenjs-socketio',
7
+ configureServer(server) {
8
+ if (!server.httpServer)
9
+ return;
10
+ import('../../shared/socket-io-setup.js').then(({ setupSocketIO }) => {
11
+ const routes = scanSocketRoutes(pagesDir);
12
+ setupSocketIO({
13
+ httpServer: server.httpServer,
14
+ loadModule: (fp) => server.ssrLoadModule(fp),
15
+ routes,
16
+ projectDir: path.dirname(pagesDir),
17
+ }).catch((err) => {
18
+ console.warn('[LumenJS] Socket.IO setup failed:', err.message);
19
+ console.warn('[LumenJS] Make sure socket.io is installed: lumenjs add socketio');
20
+ });
21
+ }).catch((err) => {
22
+ console.warn('[LumenJS] Socket.IO plugin load failed:', err.message);
23
+ });
24
+ },
25
+ };
26
+ }
27
+ function scanSocketRoutes(pagesDir) {
28
+ const routes = [];
29
+ if (!fs.existsSync(pagesDir))
30
+ return routes;
31
+ walkDir(pagesDir, '', routes, pagesDir);
32
+ return routes;
33
+ }
34
+ function walkDir(baseDir, relativePath, routes, pagesDir) {
35
+ const fullDir = path.join(baseDir, relativePath);
36
+ const entries = fs.readdirSync(fullDir, { withFileTypes: true });
37
+ for (const entry of entries) {
38
+ const entryRelative = path.join(relativePath, entry.name);
39
+ if (entry.isDirectory()) {
40
+ walkDir(baseDir, entryRelative, routes, pagesDir);
41
+ }
42
+ else if (entry.isFile() && /\.(ts|js)$/.test(entry.name) && !entry.name.startsWith('_')) {
43
+ const filePath = path.join(pagesDir, entryRelative);
44
+ const hasSocket = fileHasSocket(filePath);
45
+ if (hasSocket) {
46
+ const routePath = filePathToRoute(entryRelative);
47
+ routes.push({ path: routePath, hasSocket: true, filePath });
48
+ }
49
+ }
50
+ }
51
+ }
@@ -1,5 +1,7 @@
1
1
  import { Plugin } from 'vite';
2
2
  /**
3
3
  * In editor mode, inject data-nk-source attributes into html`` template literals.
4
+ * Reads the original source file from disk to compute correct line numbers,
5
+ * since Vite's transform hook receives esbuild-compiled code with shifted lines.
4
6
  */
5
7
  export declare function sourceAnnotatorPlugin(projectDir: string): Plugin;
@@ -1,21 +1,44 @@
1
1
  import path from 'path';
2
+ import fs from 'fs';
2
3
  /**
3
4
  * In editor mode, inject data-nk-source attributes into html`` template literals.
5
+ * Reads the original source file from disk to compute correct line numbers,
6
+ * since Vite's transform hook receives esbuild-compiled code with shifted lines.
4
7
  */
5
8
  export function sourceAnnotatorPlugin(projectDir) {
6
9
  return {
7
10
  name: 'lumenjs-source-annotator',
11
+ enforce: 'pre',
8
12
  transform(code, id) {
9
13
  if (!id.startsWith(projectDir) || !id.endsWith('.ts'))
10
14
  return;
11
15
  if (!code.includes('html`'))
12
16
  return;
17
+ // Read the original source to get correct line numbers
18
+ let originalSource;
19
+ try {
20
+ originalSource = fs.readFileSync(id, 'utf-8');
21
+ }
22
+ catch {
23
+ originalSource = code;
24
+ }
13
25
  const relativePath = path.relative(projectDir, id);
26
+ // Build ordered list of html` base lines from original source
27
+ const originalBaseLines = [];
28
+ const origRegex = /html`/g;
29
+ let origMatch;
30
+ while ((origMatch = origRegex.exec(originalSource)) !== null) {
31
+ const before = originalSource.substring(0, origMatch.index + 5); // include 'html`'
32
+ originalBaseLines.push(before.split('\n').length);
33
+ }
34
+ let templateIndex = 0;
14
35
  const transformed = code.replace(/html`([\s\S]*?)`/g, (match, templateContent) => {
15
36
  let offset = 0;
16
- const beforeTemplate = code.substring(0, code.indexOf(match));
17
- const baseLine = beforeTemplate.split('\n').length;
18
- const annotated = templateContent.replace(/<([a-z][a-z0-9]*-[a-z0-9-]*)([\s>])/gi, (tagMatch, tagName, after) => {
37
+ // Use original source line for this Nth html` template
38
+ const baseLine = originalBaseLines[templateIndex] ?? code.substring(0, code.indexOf(match)).split('\n').length;
39
+ templateIndex++;
40
+ // Annotate both custom elements (tags with hyphens) and standard HTML elements
41
+ const annotated = templateContent.replace(/<((?:[a-z][a-z0-9]*-[a-z0-9-]*)|(?:div|section|article|aside|main|nav|header|footer|h[1-6]|p|span|a|ul|ol|li|button|form|input|textarea|select|label|img|table|tr|td|th|thead|tbody))([\s>])/gi, (tagMatch, tagName, after) => {
19
42
  const beforeTag = templateContent.substring(0, templateContent.indexOf(tagMatch, offset));
20
43
  const lineInTemplate = beforeTag.split('\n').length - 1;
21
44
  offset = templateContent.indexOf(tagMatch, offset) + tagMatch.length;
@@ -0,0 +1,10 @@
1
+ import type { Plugin } from 'vite';
2
+ /**
3
+ * LumenJS storage plugin (dev mode).
4
+ *
5
+ * - Creates a LocalStorageAdapter pointing to `{projectDir}/uploads`
6
+ * - Registers it as the global storage singleton (`useStorage()`)
7
+ * - Serves uploaded files at `/uploads/*`
8
+ * - Handles presigned PUT requests at `/__nk_storage/upload/:token`
9
+ */
10
+ export declare function lumenStoragePlugin(projectDir: string): Plugin;