@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,111 @@
1
+ import path from 'path';
2
+ import { filePathToTagName, dirToLayoutTagName } from '../../shared/utils.js';
3
+ /**
4
+ * Auto-registers custom elements for page and layout files.
5
+ *
6
+ * Scans for the exported class that extends LitElement and appends a
7
+ * `customElements.define('tag-name', ClassName)` call. The tag name is
8
+ * derived from the file path using the same convention the router uses:
9
+ *
10
+ * pages/index.ts → page-index
11
+ * pages/docs/routing.ts → page-docs-routing
12
+ * pages/_layout.ts → layout-root
13
+ * pages/docs/_layout.ts → layout-docs
14
+ *
15
+ * This removes the need for `@customElement('...')` in page/layout files.
16
+ */
17
+ export function autoDefinePlugin(pagesDir) {
18
+ return {
19
+ name: 'lumenjs-auto-define',
20
+ enforce: 'pre',
21
+ transform(code, id) {
22
+ if (!id.startsWith(pagesDir) || !id.endsWith('.ts'))
23
+ return;
24
+ const relative = path.relative(pagesDir, id).replace(/\\/g, '/');
25
+ const basename = path.basename(relative, '.ts');
26
+ // Determine if this is a layout or a page
27
+ const isLayout = basename === '_layout';
28
+ if (!isLayout && basename.startsWith('_'))
29
+ return; // skip other _ files
30
+ // Derive the tag name
31
+ const tagName = isLayout
32
+ ? dirToLayoutTagName(path.dirname(relative) === '.' ? '' : path.dirname(relative))
33
+ : filePathToTagName(relative);
34
+ // Find the exported class that extends LitElement or any parent class
35
+ const classMatch = code.match(/export\s+class\s+(\w+)\s+extends\s+(\w+)\b/);
36
+ if (!classMatch)
37
+ return;
38
+ const className = classMatch[1];
39
+ // Skip if already has a customElements.define or @customElement for this class
40
+ if (code.includes(`customElements.define('${tagName}'`))
41
+ return;
42
+ // Check for actual decorator usage (not mentions in HTML/text content)
43
+ if (/^\s*@customElement\s*\(/m.test(code))
44
+ return;
45
+ // Append the define call + HMR support
46
+ const defineCall = `
47
+ if (!customElements.get('${tagName}')) {
48
+ customElements.define('${tagName}', ${className});
49
+ }
50
+ if (import.meta.hot) {
51
+ import.meta.hot.accept((newModule) => {
52
+ if (!newModule) return;
53
+ const NewClass = newModule.${className};
54
+ if (!NewClass) return;
55
+ const OldClass = customElements.get('${tagName}');
56
+ if (!OldClass) return;
57
+ const descriptors = Object.getOwnPropertyDescriptors(NewClass.prototype);
58
+ for (const [key, desc] of Object.entries(descriptors)) {
59
+ if (key === 'constructor') continue;
60
+ Object.defineProperty(OldClass.prototype, key, desc);
61
+ }
62
+ const newCssText = NewClass.styles?.cssText || '';
63
+ if (NewClass.styles) {
64
+ OldClass.styles = NewClass.styles;
65
+ OldClass.elementStyles = undefined;
66
+ OldClass.finalized = false;
67
+ try { OldClass.finalizeStyles(); } catch {}
68
+ }
69
+ if (NewClass.properties) {
70
+ OldClass.properties = NewClass.properties;
71
+ }
72
+ function __queryShadowAll(root, sel) {
73
+ const results = [...root.querySelectorAll(sel)];
74
+ for (const el of root.querySelectorAll('*')) {
75
+ if (el.shadowRoot) results.push(...__queryShadowAll(el.shadowRoot, sel));
76
+ }
77
+ return results;
78
+ }
79
+ __queryShadowAll(document, '${tagName}').forEach((el) => {
80
+ if (el.renderRoot) {
81
+ // Update styles: try adoptedStyleSheets first, fall back to <style> tag
82
+ if (OldClass.elementStyles && OldClass.elementStyles.length > 0) {
83
+ const sheets = OldClass.elementStyles
84
+ .filter(s => s instanceof CSSStyleSheet || (s && s.styleSheet))
85
+ .map(s => s instanceof CSSStyleSheet ? s : s.styleSheet);
86
+ if (sheets.length && el.renderRoot.adoptedStyleSheets !== undefined) {
87
+ el.renderRoot.adoptedStyleSheets = sheets;
88
+ }
89
+ } else if (newCssText) {
90
+ const styleEl = el.renderRoot.querySelector('style');
91
+ if (styleEl) styleEl.textContent = newCssText;
92
+ }
93
+ // Clear stale SSR inline styles from child elements
94
+ el.renderRoot.querySelectorAll('[style]').forEach((child) => {
95
+ child.removeAttribute('style');
96
+ });
97
+ // Clear Lit's template cache to force re-render with new template
98
+ const childPart = Object.getOwnPropertySymbols(el.renderRoot)
99
+ .map(s => el.renderRoot[s])
100
+ .find(v => v && typeof v === 'object' && '_$committedValue' in v);
101
+ if (childPart) childPart._$committedValue = undefined;
102
+ }
103
+ if (el.requestUpdate) el.requestUpdate();
104
+ });
105
+ });
106
+ }
107
+ `;
108
+ return { code: code + defineCall, map: null };
109
+ },
110
+ };
111
+ }
@@ -0,0 +1,6 @@
1
+ import { Plugin } from 'vite';
2
+ /**
3
+ * Vite dev plugin for the communication module.
4
+ * Initializes DB tables and provides REST API endpoints for conversations/messages.
5
+ */
6
+ export declare function communicationPlugin(projectDir: string): Plugin;
@@ -0,0 +1,205 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import crypto from 'crypto';
4
+ import { setProjectDir } from '../../db/context.js';
5
+ import { useDb } from '../../db/index.js';
6
+ import { ensureCommunicationTables } from '../../communication/schema.js';
7
+ import { createCommunicationApiHandlers } from '../../communication/server.js';
8
+ import { fetchLinkPreview, extractUrls } from '../../communication/link-preview.js';
9
+ /**
10
+ * Vite dev plugin for the communication module.
11
+ * Initializes DB tables and provides REST API endpoints for conversations/messages.
12
+ */
13
+ export function communicationPlugin(projectDir) {
14
+ let db = null;
15
+ let api = null;
16
+ async function getApi() {
17
+ if (api)
18
+ return api;
19
+ try {
20
+ setProjectDir(projectDir);
21
+ db = useDb();
22
+ await ensureCommunicationTables(db);
23
+ api = createCommunicationApiHandlers(db);
24
+ console.log('[LumenJS] Communication module initialized');
25
+ }
26
+ catch (err) {
27
+ console.warn('[LumenJS Communication] DB init failed:', err?.message);
28
+ }
29
+ return api;
30
+ }
31
+ const MAX_BODY_SIZE = 1024 * 1024; // 1 MB
32
+ function readBody(req, maxSize = MAX_BODY_SIZE) {
33
+ return new Promise((resolve, reject) => {
34
+ const chunks = [];
35
+ let size = 0;
36
+ req.on('data', (c) => {
37
+ size += c.length;
38
+ if (size > maxSize) {
39
+ req.destroy();
40
+ reject(new Error('Request body too large'));
41
+ return;
42
+ }
43
+ chunks.push(c);
44
+ });
45
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
46
+ req.on('error', reject);
47
+ });
48
+ }
49
+ function sendJson(res, status, data) {
50
+ res.writeHead(status, { 'Content-Type': 'application/json' });
51
+ res.end(JSON.stringify(data));
52
+ }
53
+ return {
54
+ name: 'lumenjs-communication',
55
+ configureServer(server) {
56
+ // REST API for conversations and messages
57
+ server.middlewares.use(async (req, res, next) => {
58
+ const url = req.url || '';
59
+ if (!url.startsWith('/__nk_comm/'))
60
+ return next();
61
+ const commApi = await getApi();
62
+ if (!commApi) {
63
+ sendJson(res, 500, { error: 'Communication module not available' });
64
+ return;
65
+ }
66
+ const userId = req.nkAuth?.user?.sub;
67
+ const rest = url.split('?')[0].slice('/__nk_comm/'.length);
68
+ try {
69
+ // GET /__nk_comm/conversations
70
+ if (rest === 'conversations' && req.method === 'GET') {
71
+ if (!userId) {
72
+ sendJson(res, 401, { error: 'Unauthorized' });
73
+ return;
74
+ }
75
+ const data = await commApi.getConversations(userId);
76
+ sendJson(res, 200, data);
77
+ return;
78
+ }
79
+ // POST /__nk_comm/conversations
80
+ if (rest === 'conversations' && req.method === 'POST') {
81
+ if (!userId) {
82
+ sendJson(res, 401, { error: 'Unauthorized' });
83
+ return;
84
+ }
85
+ const body = JSON.parse(await readBody(req));
86
+ const conv = await commApi.createConversation({ ...body, participantIds: [userId, ...(body.participantIds || [])] });
87
+ sendJson(res, 201, conv);
88
+ return;
89
+ }
90
+ // GET /__nk_comm/messages/<conversationId>
91
+ if (rest.startsWith('messages/') && req.method === 'GET') {
92
+ if (!userId) {
93
+ sendJson(res, 401, { error: 'Unauthorized' });
94
+ return;
95
+ }
96
+ const conversationId = rest.slice('messages/'.length);
97
+ const data = await commApi.getMessages(conversationId);
98
+ sendJson(res, 200, data);
99
+ return;
100
+ }
101
+ // GET /__nk_comm/search?q=...
102
+ if (rest === 'search' && req.method === 'GET') {
103
+ if (!userId) {
104
+ sendJson(res, 401, { error: 'Unauthorized' });
105
+ return;
106
+ }
107
+ const params = new URL(url, 'http://localhost').searchParams;
108
+ const data = await commApi.searchMessages(params.get('q') || '');
109
+ sendJson(res, 200, data);
110
+ return;
111
+ }
112
+ // POST /__nk_comm/upload — file upload
113
+ if (rest === 'upload' && req.method === 'POST') {
114
+ if (!userId) {
115
+ sendJson(res, 401, { error: 'Unauthorized' });
116
+ return;
117
+ }
118
+ const uploadDir = path.join(projectDir, 'data', 'uploads');
119
+ if (!fs.existsSync(uploadDir))
120
+ fs.mkdirSync(uploadDir, { recursive: true });
121
+ const MAX_UPLOAD_SIZE = 10 * 1024 * 1024; // 10 MB
122
+ const chunks = [];
123
+ let uploadSize = 0;
124
+ req.on('data', (c) => {
125
+ uploadSize += c.length;
126
+ if (uploadSize > MAX_UPLOAD_SIZE) {
127
+ req.destroy();
128
+ sendJson(res, 413, { error: 'File too large' });
129
+ return;
130
+ }
131
+ chunks.push(c);
132
+ });
133
+ req.on('end', () => {
134
+ if (uploadSize > MAX_UPLOAD_SIZE)
135
+ return;
136
+ const body = Buffer.concat(chunks);
137
+ const id = crypto.randomUUID();
138
+ const contentType = req.headers['content-type'] || '';
139
+ // Simple raw upload (client sends encrypted blob or raw file)
140
+ const ext = contentType.includes('image') ? '.bin' : '.bin';
141
+ const filePath = path.join(uploadDir, `${id}${ext}`);
142
+ fs.writeFileSync(filePath, body);
143
+ const fileUrl = `/__nk_comm/files/${id}`;
144
+ if (db) {
145
+ db.run('INSERT INTO attachments (id, filename, mimetype, size, url, uploaded_by, encrypted) VALUES (?, ?, ?, ?, ?, ?, ?)', id, req.headers['x-filename'] || `file-${id}`, contentType, body.length, fileUrl, userId, req.headers['x-encrypted'] === '1' ? 1 : 0).catch(() => { });
146
+ }
147
+ sendJson(res, 201, { id, url: fileUrl, size: body.length });
148
+ });
149
+ return;
150
+ }
151
+ // GET /__nk_comm/files/:id — serve uploaded file
152
+ if (rest.startsWith('files/') && req.method === 'GET') {
153
+ const fileId = rest.slice('files/'.length);
154
+ // Validate fileId contains only safe characters (UUID format)
155
+ if (!/^[a-zA-Z0-9._-]+$/.test(fileId)) {
156
+ sendJson(res, 400, { error: 'Invalid file ID' });
157
+ return;
158
+ }
159
+ const uploadDir = path.join(projectDir, 'data', 'uploads');
160
+ const filePath = path.resolve(uploadDir, `${fileId}.bin`);
161
+ if (!filePath.startsWith(uploadDir + path.sep)) {
162
+ sendJson(res, 400, { error: 'Invalid file ID' });
163
+ return;
164
+ }
165
+ if (!fs.existsSync(filePath)) {
166
+ sendJson(res, 404, { error: 'File not found' });
167
+ return;
168
+ }
169
+ const stat = fs.statSync(filePath);
170
+ let contentType = 'application/octet-stream';
171
+ if (db) {
172
+ const att = await db.get('SELECT mimetype FROM attachments WHERE id = ?', fileId);
173
+ if (att)
174
+ contentType = att.mimetype;
175
+ }
176
+ res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': stat.size, 'Cache-Control': 'public, max-age=86400' });
177
+ fs.createReadStream(filePath).pipe(res);
178
+ return;
179
+ }
180
+ // POST /__nk_comm/link-preview — fetch link preview
181
+ if (rest === 'link-preview' && req.method === 'POST') {
182
+ if (!userId) {
183
+ sendJson(res, 401, { error: 'Unauthorized' });
184
+ return;
185
+ }
186
+ const body = JSON.parse(await readBody(req));
187
+ const urls = extractUrls(body.text || '');
188
+ const previews = [];
189
+ for (const u of urls) {
190
+ const preview = await fetchLinkPreview(u, db);
191
+ if (preview)
192
+ previews.push(preview);
193
+ }
194
+ sendJson(res, 200, { previews });
195
+ return;
196
+ }
197
+ sendJson(res, 404, { error: 'Not found' });
198
+ }
199
+ catch (err) {
200
+ sendJson(res, 500, { error: err?.message || 'Internal error' });
201
+ }
202
+ });
203
+ },
204
+ };
205
+ }
@@ -0,0 +1,6 @@
1
+ import { Plugin } from 'vite';
2
+ /**
3
+ * Vite plugin exposing editor API endpoints (only active in editor mode).
4
+ * Provides file CRUD and AST modification via /__nk_editor/* routes.
5
+ */
6
+ export declare function editorApiPlugin(projectDir: string): Plugin;
@@ -0,0 +1,318 @@
1
+ import { EditorFileService } from '../../editor/file-service.js';
2
+ import { AstService } from '../../editor/ast-service.js';
3
+ import { streamAiChat, checkAiStatus, warmUpAiSession } from '../../editor/ai/backend.js';
4
+ import * as snapshotStore from '../../editor/ai/snapshot-store.js';
5
+ import crypto from 'crypto';
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ const EDITOR_PREFIX = '/__nk_editor/';
9
+ /**
10
+ * Vite plugin exposing editor API endpoints (only active in editor mode).
11
+ * Provides file CRUD and AST modification via /__nk_editor/* routes.
12
+ */
13
+ export function editorApiPlugin(projectDir) {
14
+ const fileService = new EditorFileService(projectDir);
15
+ const astService = new AstService();
16
+ // Per-file write lock to prevent race conditions
17
+ const writeLocks = new Map();
18
+ async function withLock(filePath, fn) {
19
+ const prev = writeLocks.get(filePath) || Promise.resolve();
20
+ let resolve;
21
+ const next = new Promise(r => { resolve = r; });
22
+ writeLocks.set(filePath, next);
23
+ try {
24
+ await prev;
25
+ return await fn();
26
+ }
27
+ finally {
28
+ resolve();
29
+ if (writeLocks.get(filePath) === next) {
30
+ writeLocks.delete(filePath);
31
+ }
32
+ }
33
+ }
34
+ const MAX_EDITOR_BODY = 5 * 1024 * 1024; // 5 MB
35
+ function readBody(req, maxSize = MAX_EDITOR_BODY) {
36
+ return new Promise((resolve, reject) => {
37
+ const chunks = [];
38
+ let size = 0;
39
+ req.on('data', (chunk) => {
40
+ size += chunk.length;
41
+ if (size > maxSize) {
42
+ req.destroy();
43
+ reject(new Error('Request body too large'));
44
+ return;
45
+ }
46
+ chunks.push(chunk);
47
+ });
48
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
49
+ req.on('error', reject);
50
+ });
51
+ }
52
+ function sendJson(res, status, data) {
53
+ res.writeHead(status, { 'Content-Type': 'application/json' });
54
+ res.end(JSON.stringify(data));
55
+ }
56
+ function sendError(res, status, message) {
57
+ sendJson(res, status, { error: message });
58
+ }
59
+ return {
60
+ name: 'lumenjs-editor-api',
61
+ configureServer(server) {
62
+ // Warm up AI session in background so first request is fast
63
+ warmUpAiSession(projectDir).catch(() => { });
64
+ server.middlewares.use(async (req, res, next) => {
65
+ if (!req.url || !req.url.startsWith(EDITOR_PREFIX))
66
+ return next();
67
+ const urlPath = req.url.split('?')[0];
68
+ const rest = urlPath.slice(EDITOR_PREFIX.length);
69
+ try {
70
+ // GET /__nk_editor/files — list all files
71
+ if (rest === 'files' && req.method === 'GET') {
72
+ const files = fileService.listFiles();
73
+ sendJson(res, 200, { files });
74
+ return;
75
+ }
76
+ // GET /__nk_editor/files/<path> — read file
77
+ if (rest.startsWith('files/') && req.method === 'GET') {
78
+ const filePath = decodeURIComponent(rest.slice('files/'.length));
79
+ const content = fileService.readFile(filePath);
80
+ sendJson(res, 200, { content });
81
+ return;
82
+ }
83
+ // PUT /__nk_editor/files/<path> — write file
84
+ if (rest.startsWith('files/') && req.method === 'PUT') {
85
+ const filePath = decodeURIComponent(rest.slice('files/'.length));
86
+ const body = JSON.parse(await readBody(req));
87
+ await withLock(filePath, async () => {
88
+ fileService.writeFile(filePath, body.content);
89
+ });
90
+ // Notify i18n HMR when a locale file is written
91
+ const localeMatch = filePath.match(/^locales\/([a-z]{2}(?:-[a-zA-Z]+)?)\.json$/);
92
+ if (localeMatch) {
93
+ server.ws.send({
94
+ type: 'custom',
95
+ event: 'lumenjs:i18n-update',
96
+ data: { locale: localeMatch[1] },
97
+ });
98
+ }
99
+ res.writeHead(204);
100
+ res.end();
101
+ return;
102
+ }
103
+ // POST /__nk_editor/ast/<path> — apply AST modification
104
+ if (rest.startsWith('ast/') && req.method === 'POST') {
105
+ const filePath = decodeURIComponent(rest.slice('ast/'.length));
106
+ const mod = JSON.parse(await readBody(req));
107
+ const content = await withLock(filePath, async () => {
108
+ const source = fileService.readFile(filePath);
109
+ const modified = await astService.applyModification(source, mod);
110
+ fileService.writeFile(filePath, modified);
111
+ return modified;
112
+ });
113
+ sendJson(res, 200, { content });
114
+ return;
115
+ }
116
+ // GET /__nk_editor/ai/status — check if OpenCode is reachable
117
+ if (rest === 'ai/status' && req.method === 'GET') {
118
+ const status = await checkAiStatus();
119
+ sendJson(res, 200, status);
120
+ return;
121
+ }
122
+ // POST /__nk_editor/ai/chat — proxy to OpenCode with SSE streaming
123
+ if (rest === 'ai/chat' && req.method === 'POST') {
124
+ const body = JSON.parse(await readBody(req));
125
+ const { mode, prompt, context, sessionId, model } = body;
126
+ if (!prompt) {
127
+ sendError(res, 400, 'Missing prompt');
128
+ return;
129
+ }
130
+ // Snapshot files before AI modifies them
131
+ // For element mode, only snapshot the source file (much faster)
132
+ // For project mode, snapshot all files
133
+ const turnId = crypto.randomUUID();
134
+ try {
135
+ const fileContents = new Map();
136
+ if (mode === 'element' && context?.sourceFile) {
137
+ try {
138
+ fileContents.set(context.sourceFile, fileService.readFile(context.sourceFile));
139
+ }
140
+ catch { /* skip */ }
141
+ // Multi-element: also snapshot additional source files
142
+ if (context.sourceFiles && Array.isArray(context.sourceFiles)) {
143
+ for (const sf of context.sourceFiles) {
144
+ if (!fileContents.has(sf)) {
145
+ try {
146
+ fileContents.set(sf, fileService.readFile(sf));
147
+ }
148
+ catch { /* skip */ }
149
+ }
150
+ }
151
+ }
152
+ }
153
+ else {
154
+ const files = fileService.listFiles();
155
+ for (const f of files) {
156
+ try {
157
+ fileContents.set(f, fileService.readFile(f));
158
+ }
159
+ catch { /* skip */ }
160
+ }
161
+ }
162
+ snapshotStore.save(turnId, fileContents);
163
+ }
164
+ catch {
165
+ // Non-fatal — rollback just won't be available
166
+ }
167
+ // If element mode with a source file, read its content for richer context
168
+ let enrichedContext = context || {};
169
+ if (mode === 'element' && context?.sourceFile) {
170
+ try {
171
+ const fullContent = fileService.readFile(context.sourceFile);
172
+ let sourceContent = fullContent;
173
+ // Trim to ±20 lines around the target line to reduce token usage
174
+ if (context.sourceLine && typeof context.sourceLine === 'number') {
175
+ const lines = fullContent.split('\n');
176
+ const start = Math.max(0, context.sourceLine - 20);
177
+ const end = Math.min(lines.length, context.sourceLine + 20);
178
+ const trimmed = [];
179
+ if (start > 0)
180
+ trimmed.push(`// ... (lines 1-${start} omitted)`);
181
+ trimmed.push(...lines.slice(start, end));
182
+ if (end < lines.length)
183
+ trimmed.push(`// ... (lines ${end + 1}-${lines.length} omitted)`);
184
+ sourceContent = trimmed.join('\n');
185
+ }
186
+ enrichedContext = { ...context, sourceContent };
187
+ }
188
+ catch {
189
+ // File might not exist, continue without content
190
+ }
191
+ // Multi-element: read additional source files beyond the primary
192
+ if (context.sourceFiles && Array.isArray(context.sourceFiles)) {
193
+ const additionalSources = {};
194
+ for (const sf of context.sourceFiles) {
195
+ if (sf === context.sourceFile)
196
+ continue; // already read above
197
+ try {
198
+ additionalSources[sf] = fileService.readFile(sf);
199
+ }
200
+ catch { /* skip */ }
201
+ }
202
+ if (Object.keys(additionalSources).length > 0) {
203
+ enrichedContext = { ...enrichedContext, additionalSources };
204
+ }
205
+ }
206
+ }
207
+ // If project has i18n, include locale translations so the AI knows
208
+ // to edit locale JSON files instead of hardcoding text in templates.
209
+ const localesDir = path.join(projectDir, 'locales');
210
+ if (fs.existsSync(localesDir)) {
211
+ try {
212
+ const localeFiles = fs.readdirSync(localesDir).filter(f => f.endsWith('.json'));
213
+ const translations = {};
214
+ for (const f of localeFiles) {
215
+ const locale = f.replace('.json', '');
216
+ translations[locale] = JSON.parse(fs.readFileSync(path.join(localesDir, f), 'utf-8'));
217
+ }
218
+ enrichedContext = { ...enrichedContext, i18n: { translations } };
219
+ }
220
+ catch {
221
+ // Non-fatal
222
+ }
223
+ }
224
+ // Set up SSE response
225
+ res.writeHead(200, {
226
+ 'Content-Type': 'text/event-stream',
227
+ 'Cache-Control': 'no-cache',
228
+ 'Connection': 'keep-alive',
229
+ 'X-Accel-Buffering': 'no',
230
+ });
231
+ const result = streamAiChat(projectDir, {
232
+ mode: mode || 'project',
233
+ prompt,
234
+ context: enrichedContext,
235
+ sessionId,
236
+ model: model || 'default',
237
+ });
238
+ // Handle client disconnect
239
+ req.on('close', () => result.abort());
240
+ result.onToken((text) => {
241
+ res.write(`event: token\ndata: ${JSON.stringify({ text })}\n\n`);
242
+ });
243
+ result.onDone((fullText) => {
244
+ res.write(`event: done\ndata: ${JSON.stringify({ sessionId: result.sessionId, turnId, fullText })}\n\n`);
245
+ res.end();
246
+ });
247
+ result.onError((err) => {
248
+ res.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`);
249
+ res.end();
250
+ });
251
+ return;
252
+ }
253
+ // POST /__nk_editor/ai/rollback — restore file snapshots
254
+ if (rest === 'ai/rollback' && req.method === 'POST') {
255
+ const body = JSON.parse(await readBody(req));
256
+ const { turnId } = body;
257
+ if (!turnId) {
258
+ sendError(res, 400, 'Missing turnId');
259
+ return;
260
+ }
261
+ const files = snapshotStore.restore(turnId);
262
+ if (!files) {
263
+ sendError(res, 404, 'Snapshot not found');
264
+ return;
265
+ }
266
+ const restoredFiles = [];
267
+ for (const [filePath, content] of files) {
268
+ try {
269
+ fileService.writeFile(filePath, content);
270
+ restoredFiles.push(filePath);
271
+ }
272
+ catch {
273
+ // Skip files that can't be written
274
+ }
275
+ }
276
+ sendJson(res, 200, { restored: true, files: restoredFiles });
277
+ return;
278
+ }
279
+ sendError(res, 404, 'Not found');
280
+ }
281
+ catch (err) {
282
+ const message = err?.message || 'Internal server error';
283
+ const status = message === 'Path traversal detected' ? 403 : 500;
284
+ sendError(res, status, message);
285
+ }
286
+ });
287
+ // Watch package.json for changes (e.g. after AI runs npm install).
288
+ // When dependencies change, restart Vite to re-run dependency optimization.
289
+ const pkgJsonPath = path.join(projectDir, 'package.json');
290
+ if (fs.existsSync(pkgJsonPath)) {
291
+ let lastPkgDeps = readDepsHash(pkgJsonPath);
292
+ const watcher = fs.watch(pkgJsonPath, { persistent: false }, () => {
293
+ const currentDeps = readDepsHash(pkgJsonPath);
294
+ if (currentDeps !== lastPkgDeps) {
295
+ lastPkgDeps = currentDeps;
296
+ console.log('[LumenJS] Dependencies changed — restarting dev server...');
297
+ server.restart();
298
+ }
299
+ });
300
+ server.httpServer?.on('close', () => watcher.close());
301
+ }
302
+ },
303
+ };
304
+ }
305
+ /** Hash the dependencies/devDependencies from package.json to detect changes. */
306
+ function readDepsHash(pkgJsonPath) {
307
+ try {
308
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
309
+ const deps = JSON.stringify({
310
+ d: pkg.dependencies || {},
311
+ v: pkg.devDependencies || {},
312
+ });
313
+ return deps;
314
+ }
315
+ catch {
316
+ return '';
317
+ }
318
+ }