@nuraly/lumenjs 0.1.3 → 0.2.0

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 (306) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +82 -0
  11. package/dist/auth/native-auth.js +340 -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 +121 -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/totp.d.ts +22 -0
  31. package/dist/auth/routes/totp.js +232 -0
  32. package/dist/auth/routes/utils.d.ts +7 -0
  33. package/dist/auth/routes/utils.js +35 -0
  34. package/dist/auth/routes/verify.d.ts +3 -0
  35. package/dist/auth/routes/verify.js +26 -0
  36. package/dist/auth/routes.d.ts +8 -0
  37. package/dist/auth/routes.js +124 -0
  38. package/dist/auth/session.d.ts +8 -0
  39. package/dist/auth/session.js +54 -0
  40. package/dist/auth/token.d.ts +33 -0
  41. package/dist/auth/token.js +90 -0
  42. package/dist/auth/types.d.ts +156 -0
  43. package/dist/auth/types.js +2 -0
  44. package/dist/build/build-client.d.ts +15 -0
  45. package/dist/build/build-client.js +45 -0
  46. package/dist/build/build-prerender.d.ts +11 -0
  47. package/dist/build/build-prerender.js +159 -0
  48. package/dist/build/build-server.d.ts +18 -0
  49. package/dist/build/build-server.js +107 -0
  50. package/dist/build/build.js +60 -123
  51. package/dist/build/scan.d.ts +18 -0
  52. package/dist/build/scan.js +77 -6
  53. package/dist/build/serve-api.js +8 -2
  54. package/dist/build/serve-loaders.d.ts +4 -4
  55. package/dist/build/serve-loaders.js +26 -18
  56. package/dist/build/serve-ssr.js +38 -11
  57. package/dist/build/serve-static.js +3 -3
  58. package/dist/build/serve.js +341 -18
  59. package/dist/cli.js +37 -6
  60. package/dist/communication/encryption.d.ts +35 -0
  61. package/dist/communication/encryption.js +90 -0
  62. package/dist/communication/handlers/context.d.ts +27 -0
  63. package/dist/communication/handlers/context.js +1 -0
  64. package/dist/communication/handlers/conversation.d.ts +24 -0
  65. package/dist/communication/handlers/conversation.js +113 -0
  66. package/dist/communication/handlers/file-upload.d.ts +17 -0
  67. package/dist/communication/handlers/file-upload.js +62 -0
  68. package/dist/communication/handlers/messaging.d.ts +30 -0
  69. package/dist/communication/handlers/messaging.js +237 -0
  70. package/dist/communication/handlers/presence.d.ts +15 -0
  71. package/dist/communication/handlers/presence.js +76 -0
  72. package/dist/communication/handlers.d.ts +5 -0
  73. package/dist/communication/handlers.js +5 -0
  74. package/dist/communication/index.d.ts +9 -0
  75. package/dist/communication/index.js +7 -0
  76. package/dist/communication/link-preview.d.ts +18 -0
  77. package/dist/communication/link-preview.js +115 -0
  78. package/dist/communication/schema.d.ts +10 -0
  79. package/dist/communication/schema.js +101 -0
  80. package/dist/communication/server.d.ts +86 -0
  81. package/dist/communication/server.js +212 -0
  82. package/dist/communication/signaling.d.ts +43 -0
  83. package/dist/communication/signaling.js +271 -0
  84. package/dist/communication/store.d.ts +71 -0
  85. package/dist/communication/store.js +289 -0
  86. package/dist/communication/types.d.ts +454 -0
  87. package/dist/communication/types.js +1 -0
  88. package/dist/create.d.ts +1 -0
  89. package/dist/create.js +55 -0
  90. package/dist/db/auto-migrate.d.ts +3 -0
  91. package/dist/db/auto-migrate.js +100 -0
  92. package/dist/db/client.d.ts +3 -0
  93. package/dist/db/client.js +18 -0
  94. package/dist/db/index.d.ts +17 -13
  95. package/dist/db/index.js +205 -26
  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 +11 -0
  101. package/dist/dev-server/config.js +40 -20
  102. package/dist/dev-server/index-html.d.ts +4 -0
  103. package/dist/dev-server/index-html.js +21 -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.js +146 -13
  122. package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
  123. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  125. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  127. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  128. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  129. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
  130. package/dist/dev-server/server.js +242 -70
  131. package/dist/dev-server/ssr-render.d.ts +2 -1
  132. package/dist/dev-server/ssr-render.js +117 -50
  133. package/dist/editor/ai/backend.d.ts +20 -0
  134. package/dist/editor/ai/backend.js +113 -0
  135. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  136. package/dist/editor/ai/claude-code-client.js +145 -0
  137. package/dist/editor/ai/deepseek-client.d.ts +7 -0
  138. package/dist/editor/ai/deepseek-client.js +113 -0
  139. package/dist/editor/ai/opencode-client.d.ts +14 -0
  140. package/dist/editor/ai/opencode-client.js +99 -0
  141. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  142. package/dist/editor/ai/snapshot-store.js +35 -0
  143. package/dist/editor/ai/types.d.ts +30 -0
  144. package/dist/editor/ai/types.js +136 -0
  145. package/dist/editor/ai-chat-panel.d.ts +13 -0
  146. package/dist/editor/ai-chat-panel.js +613 -0
  147. package/dist/editor/ai-markdown.d.ts +10 -0
  148. package/dist/editor/ai-markdown.js +70 -0
  149. package/dist/editor/ai-project-panel.d.ts +11 -0
  150. package/dist/editor/ai-project-panel.js +332 -0
  151. package/dist/editor/ast-modification.d.ts +11 -0
  152. package/dist/editor/ast-modification.js +1 -0
  153. package/dist/editor/ast-service.d.ts +30 -0
  154. package/dist/editor/ast-service.js +180 -0
  155. package/dist/editor/css-rules.d.ts +54 -0
  156. package/dist/editor/css-rules.js +423 -0
  157. package/dist/editor/editor-api-client.d.ts +51 -0
  158. package/dist/editor/editor-api-client.js +162 -0
  159. package/dist/editor/editor-bridge.d.ts +1 -0
  160. package/dist/editor/editor-bridge.js +18 -8
  161. package/dist/editor/editor-toolbar.d.ts +14 -0
  162. package/dist/editor/editor-toolbar.js +115 -0
  163. package/dist/editor/file-editor.d.ts +9 -0
  164. package/dist/editor/file-editor.js +236 -0
  165. package/dist/editor/file-service.d.ts +16 -0
  166. package/dist/editor/file-service.js +52 -0
  167. package/dist/editor/i18n-key-gen.d.ts +1 -0
  168. package/dist/editor/i18n-key-gen.js +7 -0
  169. package/dist/editor/inline-text-edit.d.ts +5 -0
  170. package/dist/editor/inline-text-edit.js +173 -92
  171. package/dist/editor/overlay-events.d.ts +5 -0
  172. package/dist/editor/overlay-events.js +364 -0
  173. package/dist/editor/overlay-hmr.d.ts +2 -0
  174. package/dist/editor/overlay-hmr.js +76 -0
  175. package/dist/editor/overlay-selection.d.ts +29 -0
  176. package/dist/editor/overlay-selection.js +148 -0
  177. package/dist/editor/overlay-utils.d.ts +12 -0
  178. package/dist/editor/overlay-utils.js +59 -0
  179. package/dist/editor/properties-panel-persist.d.ts +14 -0
  180. package/dist/editor/properties-panel-persist.js +70 -0
  181. package/dist/editor/properties-panel-rows.d.ts +10 -0
  182. package/dist/editor/properties-panel-rows.js +349 -0
  183. package/dist/editor/properties-panel-styles.d.ts +4 -0
  184. package/dist/editor/properties-panel-styles.js +174 -0
  185. package/dist/editor/properties-panel.d.ts +4 -0
  186. package/dist/editor/properties-panel.js +148 -0
  187. package/dist/editor/property-registry.d.ts +16 -0
  188. package/dist/editor/property-registry.js +303 -0
  189. package/dist/editor/standalone-file-panel.d.ts +0 -0
  190. package/dist/editor/standalone-file-panel.js +1 -0
  191. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  192. package/dist/editor/standalone-overlay-dom.js +1 -0
  193. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  194. package/dist/editor/standalone-overlay-styles.js +1 -0
  195. package/dist/editor/standalone-overlay.d.ts +1 -0
  196. package/dist/editor/standalone-overlay.js +76 -0
  197. package/dist/editor/syntax-highlighter.d.ts +4 -0
  198. package/dist/editor/syntax-highlighter.js +81 -0
  199. package/dist/editor/text-toolbar.d.ts +11 -0
  200. package/dist/editor/text-toolbar.js +327 -0
  201. package/dist/editor/toolbar-styles.d.ts +4 -0
  202. package/dist/editor/toolbar-styles.js +198 -0
  203. package/dist/email/index.d.ts +32 -0
  204. package/dist/email/index.js +154 -0
  205. package/dist/email/providers/resend.d.ts +2 -0
  206. package/dist/email/providers/resend.js +24 -0
  207. package/dist/email/providers/sendgrid.d.ts +2 -0
  208. package/dist/email/providers/sendgrid.js +31 -0
  209. package/dist/email/providers/smtp.d.ts +13 -0
  210. package/dist/email/providers/smtp.js +125 -0
  211. package/dist/email/template-engine.d.ts +18 -0
  212. package/dist/email/template-engine.js +116 -0
  213. package/dist/email/templates/base.d.ts +9 -0
  214. package/dist/email/templates/base.js +65 -0
  215. package/dist/email/templates/password-reset.d.ts +5 -0
  216. package/dist/email/templates/password-reset.js +15 -0
  217. package/dist/email/templates/verify-email.d.ts +5 -0
  218. package/dist/email/templates/verify-email.js +15 -0
  219. package/dist/email/templates/welcome.d.ts +5 -0
  220. package/dist/email/templates/welcome.js +13 -0
  221. package/dist/email/types.d.ts +49 -0
  222. package/dist/email/types.js +1 -0
  223. package/dist/llms/generate.d.ts +46 -0
  224. package/dist/llms/generate.js +185 -0
  225. package/dist/permissions/guard.d.ts +28 -0
  226. package/dist/permissions/guard.js +30 -0
  227. package/dist/permissions/index.d.ts +6 -0
  228. package/dist/permissions/index.js +3 -0
  229. package/dist/permissions/service.d.ts +80 -0
  230. package/dist/permissions/service.js +210 -0
  231. package/dist/permissions/tables.d.ts +5 -0
  232. package/dist/permissions/tables.js +68 -0
  233. package/dist/permissions/types.d.ts +33 -0
  234. package/dist/permissions/types.js +1 -0
  235. package/dist/runtime/app-shell.d.ts +1 -1
  236. package/dist/runtime/app-shell.js +164 -0
  237. package/dist/runtime/auth.d.ts +10 -0
  238. package/dist/runtime/auth.js +30 -0
  239. package/dist/runtime/communication.d.ts +137 -0
  240. package/dist/runtime/communication.js +228 -0
  241. package/dist/runtime/error-boundary.d.ts +23 -0
  242. package/dist/runtime/error-boundary.js +120 -0
  243. package/dist/runtime/i18n.d.ts +6 -1
  244. package/dist/runtime/i18n.js +42 -21
  245. package/dist/runtime/island.d.ts +16 -0
  246. package/dist/runtime/island.js +80 -0
  247. package/dist/runtime/router-data.d.ts +3 -0
  248. package/dist/runtime/router-data.js +102 -17
  249. package/dist/runtime/router-hydration.js +34 -2
  250. package/dist/runtime/router.d.ts +19 -2
  251. package/dist/runtime/router.js +237 -43
  252. package/dist/runtime/socket-client.d.ts +2 -0
  253. package/dist/runtime/socket-client.js +30 -0
  254. package/dist/runtime/webrtc.d.ts +91 -0
  255. package/dist/runtime/webrtc.js +428 -0
  256. package/dist/shared/dom-shims.js +4 -2
  257. package/dist/shared/graceful-shutdown.d.ts +8 -0
  258. package/dist/shared/graceful-shutdown.js +36 -0
  259. package/dist/shared/health.d.ts +8 -0
  260. package/dist/shared/health.js +25 -0
  261. package/dist/shared/llms-txt.d.ts +31 -0
  262. package/dist/shared/llms-txt.js +85 -0
  263. package/dist/shared/logger.d.ts +32 -0
  264. package/dist/shared/logger.js +93 -0
  265. package/dist/shared/meta.d.ts +27 -0
  266. package/dist/shared/meta.js +71 -0
  267. package/dist/shared/middleware-runner.d.ts +9 -0
  268. package/dist/shared/middleware-runner.js +29 -0
  269. package/dist/shared/rate-limit.d.ts +18 -0
  270. package/dist/shared/rate-limit.js +71 -0
  271. package/dist/shared/request-id.d.ts +5 -0
  272. package/dist/shared/request-id.js +18 -0
  273. package/dist/shared/route-matching.js +16 -1
  274. package/dist/shared/security-headers.d.ts +18 -0
  275. package/dist/shared/security-headers.js +38 -0
  276. package/dist/shared/socket-io-setup.d.ts +11 -0
  277. package/dist/shared/socket-io-setup.js +51 -0
  278. package/dist/shared/types.d.ts +15 -0
  279. package/dist/shared/utils.d.ts +33 -7
  280. package/dist/shared/utils.js +164 -27
  281. package/dist/storage/adapters/local.d.ts +44 -0
  282. package/dist/storage/adapters/local.js +85 -0
  283. package/dist/storage/adapters/s3.d.ts +32 -0
  284. package/dist/storage/adapters/s3.js +119 -0
  285. package/dist/storage/adapters/types.d.ts +53 -0
  286. package/dist/storage/adapters/types.js +1 -0
  287. package/dist/storage/index.d.ts +76 -0
  288. package/dist/storage/index.js +83 -0
  289. package/package.json +45 -7
  290. package/templates/blog/api/posts.ts +4 -18
  291. package/templates/blog/data/migrations/001_init.sql +6 -5
  292. package/templates/blog/lumenjs.config.ts +3 -0
  293. package/templates/blog/package.json +14 -0
  294. package/templates/blog/pages/_layout.ts +25 -0
  295. package/templates/blog/pages/index.ts +48 -22
  296. package/templates/blog/pages/posts/[slug].ts +45 -20
  297. package/templates/blog/pages/tag/[tag].ts +44 -0
  298. package/templates/dashboard/api/stats.ts +8 -5
  299. package/templates/dashboard/lumenjs.config.ts +3 -0
  300. package/templates/dashboard/package.json +14 -0
  301. package/templates/dashboard/pages/_layout.ts +25 -0
  302. package/templates/dashboard/pages/index.ts +54 -23
  303. package/templates/dashboard/pages/settings/index.ts +29 -0
  304. package/templates/default/lumenjs.config.ts +3 -0
  305. package/templates/default/package.json +14 -0
  306. package/templates/default/pages/index.ts +24 -0
@@ -12,11 +12,6 @@ export declare function stripOuterLitMarkers(html: string): string;
12
12
  * 'app/[id]' → 'layout-app-id'
13
13
  */
14
14
  export declare function dirToLayoutTagName(dir: string): string;
15
- /**
16
- * Find the custom element tag name from a page module.
17
- * Pages are auto-registered by the auto-define plugin based on file path.
18
- */
19
- export declare function findTagName(mod: Record<string, any>): string | null;
20
15
  /**
21
16
  * Convert a relative file path within pages/ to a page tag name.
22
17
  * 'index.ts' → 'page-index'
@@ -32,13 +27,23 @@ export declare function isRedirectResponse(value: any): value is {
32
27
  status?: number;
33
28
  };
34
29
  /**
35
- * Read and parse the body of an HTTP request.
30
+ * Patch a custom element class's loaderData setter to also spread
31
+ * individual properties from the loader data object onto the element.
32
+ * This enables components to declare individual reactive properties
33
+ * (e.g., `stats: { type: Array }`) instead of using `this.loaderData.stats`.
34
+ *
35
+ * Must be called AFTER the element is registered and BEFORE SSR rendering.
36
36
  */
37
- export declare function readBody(req: any): Promise<any>;
37
+ export declare function patchLoaderDataSpread(tagName: string): void;
38
+ export declare function readBody(req: any, maxSize?: number): Promise<any>;
38
39
  /**
39
40
  * Escape HTML special characters for safe embedding.
40
41
  */
41
42
  export declare function escapeHtml(text: string): string;
43
+ /**
44
+ * Check if a page file exports `export const prerender = true`.
45
+ */
46
+ export declare function fileHasPrerender(filePath: string): boolean;
42
47
  /**
43
48
  * Check if a page/layout file exports a loader() function.
44
49
  */
@@ -47,6 +52,27 @@ export declare function fileHasLoader(filePath: string): boolean;
47
52
  * Check if a page/layout file exports a subscribe() function.
48
53
  */
49
54
  export declare function fileHasSubscribe(filePath: string): boolean;
55
+ /**
56
+ * Check if a page file exports an `auth` constant (before the class definition).
57
+ */
58
+ export declare function fileHasAuth(filePath: string): boolean;
59
+ /**
60
+ * Check if a page file exports a `meta` object or function.
61
+ * Supports both `export const meta = { ... }` and `export function meta(...)`.
62
+ */
63
+ export declare function fileHasMeta(filePath: string): boolean;
64
+ /**
65
+ * Check if a page file exports a `socket` function or constant.
66
+ */
67
+ export declare function fileHasSocket(filePath: string): boolean;
68
+ /**
69
+ * Check if a page exports `standalone = true` (renders without any layout).
70
+ */
71
+ export declare function fileHasStandalone(filePath: string): boolean;
72
+ /**
73
+ * Return the HTTP methods exported by an API route file (e.g. ['GET', 'POST']).
74
+ */
75
+ export declare function fileGetApiMethods(filePath: string): string[];
50
76
  /**
51
77
  * Convert a file path (relative to pages/) to a route path.
52
78
  */
@@ -27,28 +27,6 @@ export function dirToLayoutTagName(dir) {
27
27
  .toLowerCase();
28
28
  return `layout-${name}`;
29
29
  }
30
- /**
31
- * Find the custom element tag name from a page module.
32
- * Pages are auto-registered by the auto-define plugin based on file path.
33
- */
34
- export function findTagName(mod) {
35
- for (const key of Object.keys(mod)) {
36
- const val = mod[key];
37
- if (typeof val === 'function' && val.prototype) {
38
- if (val.is)
39
- return val.is;
40
- if (val.elementProperties || val.properties) {
41
- const className = val.name || key;
42
- const tag = className
43
- .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
44
- .toLowerCase();
45
- if (tag.includes('-'))
46
- return tag;
47
- }
48
- }
49
- }
50
- return null;
51
- }
52
30
  /**
53
31
  * Convert a relative file path within pages/ to a page tag name.
54
32
  * 'index.ts' → 'page-index'
@@ -71,13 +49,65 @@ export function filePathToTagName(filePath) {
71
49
  export function isRedirectResponse(value) {
72
50
  return value && typeof value === 'object' && typeof value.location === 'string' && value.__nk_redirect === true;
73
51
  }
52
+ /**
53
+ * Patch a custom element class's loaderData setter to also spread
54
+ * individual properties from the loader data object onto the element.
55
+ * This enables components to declare individual reactive properties
56
+ * (e.g., `stats: { type: Array }`) instead of using `this.loaderData.stats`.
57
+ *
58
+ * Must be called AFTER the element is registered and BEFORE SSR rendering.
59
+ */
60
+ export function patchLoaderDataSpread(tagName) {
61
+ const g = globalThis;
62
+ const ElementClass = g.customElements?.get?.(tagName);
63
+ if (!ElementClass)
64
+ return;
65
+ const proto = ElementClass.prototype;
66
+ const original = Object.getOwnPropertyDescriptor(proto, 'loaderData');
67
+ Object.defineProperty(proto, 'loaderData', {
68
+ set(value) {
69
+ if (original?.set) {
70
+ original.set.call(this, value);
71
+ }
72
+ else {
73
+ this.__nk_loaderData = value;
74
+ }
75
+ if (value && typeof value === 'object') {
76
+ const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype', 'loaderData',
77
+ 'render', 'connectedCallback', 'disconnectedCallback', 'attributeChangedCallback',
78
+ 'adoptedCallback', 'innerHTML', 'outerHTML', 'textContent']);
79
+ for (const [key, val] of Object.entries(value)) {
80
+ if (!BLOCKED_KEYS.has(key)) {
81
+ this[key] = val;
82
+ }
83
+ }
84
+ }
85
+ },
86
+ get() {
87
+ if (original?.get)
88
+ return original.get.call(this);
89
+ return this.__nk_loaderData;
90
+ },
91
+ configurable: true
92
+ });
93
+ }
74
94
  /**
75
95
  * Read and parse the body of an HTTP request.
76
96
  */
77
- export function readBody(req) {
97
+ const DEFAULT_MAX_BODY = 1024 * 1024; // 1 MB
98
+ export function readBody(req, maxSize = DEFAULT_MAX_BODY) {
78
99
  return new Promise((resolve, reject) => {
79
100
  let data = '';
80
- req.on('data', (chunk) => { data += chunk.toString(); });
101
+ let size = 0;
102
+ req.on('data', (chunk) => {
103
+ size += chunk.length;
104
+ if (size > maxSize) {
105
+ req.destroy();
106
+ reject(new Error('Request body too large'));
107
+ return;
108
+ }
109
+ data += chunk.toString();
110
+ });
81
111
  req.on('end', () => {
82
112
  if (!data)
83
113
  return resolve(undefined);
@@ -95,7 +125,33 @@ export function readBody(req) {
95
125
  * Escape HTML special characters for safe embedding.
96
126
  */
97
127
  export function escapeHtml(text) {
98
- return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
128
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
129
+ }
130
+ /**
131
+ * Check if code has a top-level export of a named function (before the class definition).
132
+ * In LumenJS, loader/subscribe are always declared before `export class`.
133
+ */
134
+ function hasTopLevelExport(content, fnName) {
135
+ const classStart = content.search(/export\s+class\s+\w+/);
136
+ const fnRegex = new RegExp(`export\\s+(async\\s+)?function\\s+${fnName}\\s*\\(`);
137
+ const match = fnRegex.exec(content);
138
+ if (!match)
139
+ return false;
140
+ if (classStart >= 0 && match.index > classStart)
141
+ return false;
142
+ return true;
143
+ }
144
+ /**
145
+ * Check if a page file exports `export const prerender = true`.
146
+ */
147
+ export function fileHasPrerender(filePath) {
148
+ try {
149
+ const content = fs.readFileSync(filePath, 'utf-8');
150
+ return /export\s+const\s+prerender\s*=\s*true/.test(content);
151
+ }
152
+ catch {
153
+ return false;
154
+ }
99
155
  }
100
156
  /**
101
157
  * Check if a page/layout file exports a loader() function.
@@ -103,7 +159,7 @@ export function escapeHtml(text) {
103
159
  export function fileHasLoader(filePath) {
104
160
  try {
105
161
  const content = fs.readFileSync(filePath, 'utf-8');
106
- return /export\s+(async\s+)?function\s+loader\s*\(/.test(content);
162
+ return hasTopLevelExport(content, 'loader');
107
163
  }
108
164
  catch {
109
165
  return false;
@@ -115,12 +171,93 @@ export function fileHasLoader(filePath) {
115
171
  export function fileHasSubscribe(filePath) {
116
172
  try {
117
173
  const content = fs.readFileSync(filePath, 'utf-8');
118
- return /export\s+(async\s+)?function\s+subscribe\s*\(/.test(content);
174
+ return hasTopLevelExport(content, 'subscribe');
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
180
+ /**
181
+ * Check if a page file exports an `auth` constant (before the class definition).
182
+ */
183
+ export function fileHasAuth(filePath) {
184
+ try {
185
+ const content = fs.readFileSync(filePath, 'utf-8');
186
+ const classStart = content.search(/export\s+class\s+\w+/);
187
+ const match = /export\s+const\s+auth\s*=/.exec(content);
188
+ if (!match)
189
+ return false;
190
+ if (classStart >= 0 && match.index > classStart)
191
+ return false;
192
+ return true;
119
193
  }
120
194
  catch {
121
195
  return false;
122
196
  }
123
197
  }
198
+ /**
199
+ * Check if a page file exports a `meta` object or function.
200
+ * Supports both `export const meta = { ... }` and `export function meta(...)`.
201
+ */
202
+ export function fileHasMeta(filePath) {
203
+ try {
204
+ const content = fs.readFileSync(filePath, 'utf-8');
205
+ const classStart = content.search(/export\s+class\s+\w+/);
206
+ // Check for `export const meta` or `export function meta`
207
+ const match = /export\s+(const\s+meta\s*=|(async\s+)?function\s+meta\s*\()/.exec(content);
208
+ if (!match)
209
+ return false;
210
+ if (classStart >= 0 && match.index > classStart)
211
+ return false;
212
+ return true;
213
+ }
214
+ catch {
215
+ return false;
216
+ }
217
+ }
218
+ /**
219
+ * Check if a page file exports a `socket` function or constant.
220
+ */
221
+ export function fileHasSocket(filePath) {
222
+ try {
223
+ const content = fs.readFileSync(filePath, 'utf-8');
224
+ return /export\s+(function|const)\s+socket[\s(=]/.test(content);
225
+ }
226
+ catch {
227
+ return false;
228
+ }
229
+ }
230
+ /**
231
+ * Check if a page exports `standalone = true` (renders without any layout).
232
+ */
233
+ export function fileHasStandalone(filePath) {
234
+ try {
235
+ const content = fs.readFileSync(filePath, 'utf-8');
236
+ const classStart = content.search(/export\s+class\s+\w+/);
237
+ const match = /export\s+const\s+standalone\s*=/.exec(content);
238
+ if (!match)
239
+ return false;
240
+ if (classStart >= 0 && match.index > classStart)
241
+ return false;
242
+ return true;
243
+ }
244
+ catch {
245
+ return false;
246
+ }
247
+ }
248
+ /**
249
+ * Return the HTTP methods exported by an API route file (e.g. ['GET', 'POST']).
250
+ */
251
+ export function fileGetApiMethods(filePath) {
252
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
253
+ try {
254
+ const content = fs.readFileSync(filePath, 'utf-8');
255
+ return HTTP_METHODS.filter(m => new RegExp(`export\\s+(async\\s+)?function\\s+${m}[\\s(]`).test(content));
256
+ }
257
+ catch {
258
+ return [];
259
+ }
260
+ }
124
261
  /**
125
262
  * Convert a file path (relative to pages/) to a route path.
126
263
  */
@@ -0,0 +1,44 @@
1
+ import type { StorageAdapter, StoredFile, PutOptions, PresignPutOptions, PresignGetOptions, PresignedUpload } from './types.js';
2
+ interface PendingUpload {
3
+ key: string;
4
+ mimeType?: string;
5
+ maxSize?: number;
6
+ /** Absolute expiry time in ms (Date.now()) */
7
+ expiresAt: number;
8
+ }
9
+ export interface LocalStorageOptions {
10
+ /** Directory to write uploaded files to. Default: './uploads' */
11
+ uploadDir: string;
12
+ /** URL path prefix for serving files. Default: '/uploads' */
13
+ publicPath: string;
14
+ }
15
+ /**
16
+ * Local-disk storage adapter for development.
17
+ *
18
+ * Files are written to `uploadDir` and served at `publicPath`.
19
+ * Presigned uploads use in-memory tokens consumed by `vite-plugin-storage`.
20
+ */
21
+ export declare class LocalStorageAdapter implements StorageAdapter {
22
+ readonly uploadDir: string;
23
+ readonly publicPath: string;
24
+ /**
25
+ * Pending presigned upload tokens.
26
+ * Keyed by token → consumed by the Vite dev server plugin on upload.
27
+ */
28
+ readonly pendingUploads: Map<string, PendingUpload>;
29
+ constructor(options: LocalStorageOptions);
30
+ /** Resolve a storage key to an absolute path and ensure it stays within uploadDir. */
31
+ private safePath;
32
+ put(data: Buffer, options?: PutOptions): Promise<StoredFile>;
33
+ delete(key: string): Promise<void>;
34
+ presignPut(key: string, options?: PresignPutOptions): Promise<PresignedUpload>;
35
+ presignGet(_key: string, _options?: PresignGetOptions): Promise<string>;
36
+ publicUrl(key: string): string;
37
+ /**
38
+ * Consume a presigned upload token.
39
+ * Returns the pending upload metadata if the token is valid and unexpired, else undefined.
40
+ * Calling this removes the token (one-time use).
41
+ */
42
+ consumeUpload(token: string): PendingUpload | undefined;
43
+ }
44
+ export {};
@@ -0,0 +1,85 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import crypto from 'crypto';
4
+ /**
5
+ * Local-disk storage adapter for development.
6
+ *
7
+ * Files are written to `uploadDir` and served at `publicPath`.
8
+ * Presigned uploads use in-memory tokens consumed by `vite-plugin-storage`.
9
+ */
10
+ export class LocalStorageAdapter {
11
+ constructor(options) {
12
+ /**
13
+ * Pending presigned upload tokens.
14
+ * Keyed by token → consumed by the Vite dev server plugin on upload.
15
+ */
16
+ this.pendingUploads = new Map();
17
+ this.uploadDir = path.resolve(options.uploadDir);
18
+ this.publicPath = options.publicPath.replace(/\/$/, '');
19
+ fs.mkdirSync(this.uploadDir, { recursive: true });
20
+ }
21
+ /** Resolve a storage key to an absolute path and ensure it stays within uploadDir. */
22
+ safePath(key) {
23
+ const resolved = path.resolve(this.uploadDir, key);
24
+ if (!resolved.startsWith(this.uploadDir + path.sep) && resolved !== this.uploadDir) {
25
+ throw new Error('Invalid storage key: path traversal detected');
26
+ }
27
+ return resolved;
28
+ }
29
+ async put(data, options) {
30
+ const key = options?.key ?? crypto.randomUUID();
31
+ const filePath = this.safePath(key);
32
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
33
+ fs.writeFileSync(filePath, data);
34
+ return {
35
+ key,
36
+ url: this.publicUrl(key),
37
+ size: data.length,
38
+ mimeType: options?.mimeType ?? 'application/octet-stream',
39
+ fileName: options?.fileName ?? key,
40
+ };
41
+ }
42
+ async delete(key) {
43
+ const filePath = this.safePath(key);
44
+ if (fs.existsSync(filePath)) {
45
+ fs.unlinkSync(filePath);
46
+ }
47
+ }
48
+ async presignPut(key, options) {
49
+ const expiresIn = options?.expiresIn ?? 3600;
50
+ const expiresAt = new Date(Date.now() + expiresIn * 1000);
51
+ const token = crypto.randomBytes(32).toString('hex');
52
+ this.pendingUploads.set(token, {
53
+ key,
54
+ mimeType: options?.mimeType,
55
+ maxSize: options?.maxSize,
56
+ expiresAt: expiresAt.getTime(),
57
+ });
58
+ return {
59
+ uploadUrl: `/__nk_storage/upload/${token}`,
60
+ key,
61
+ expiresAt: expiresAt.toISOString(),
62
+ };
63
+ }
64
+ async presignGet(_key, _options) {
65
+ // Local dev — files are served publicly at the static path.
66
+ return this.publicUrl(_key);
67
+ }
68
+ publicUrl(key) {
69
+ return `${this.publicPath}/${key}`;
70
+ }
71
+ /**
72
+ * Consume a presigned upload token.
73
+ * Returns the pending upload metadata if the token is valid and unexpired, else undefined.
74
+ * Calling this removes the token (one-time use).
75
+ */
76
+ consumeUpload(token) {
77
+ const pending = this.pendingUploads.get(token);
78
+ if (!pending)
79
+ return undefined;
80
+ this.pendingUploads.delete(token);
81
+ if (Date.now() > pending.expiresAt)
82
+ return undefined;
83
+ return pending;
84
+ }
85
+ }
@@ -0,0 +1,32 @@
1
+ import type { StorageAdapter, StoredFile, PutOptions, PresignPutOptions, PresignGetOptions, PresignedUpload } from './types.js';
2
+ export interface S3StorageOptions {
3
+ bucket: string;
4
+ region: string;
5
+ accessKeyId: string;
6
+ secretAccessKey: string;
7
+ /** Custom endpoint for S3-compatible APIs: MinIO, Cloudflare R2, etc. */
8
+ endpoint?: string;
9
+ /** Public CDN base URL. Defaults to https://{bucket}.s3.{region}.amazonaws.com */
10
+ publicBaseUrl?: string;
11
+ }
12
+ /**
13
+ * S3-compatible storage adapter.
14
+ * Works with AWS S3, Cloudflare R2, MinIO, and any S3-compatible API.
15
+ *
16
+ * Requires optional peer dependencies:
17
+ * @aws-sdk/client-s3
18
+ * @aws-sdk/s3-request-presigner
19
+ */
20
+ export declare class S3StorageAdapter implements StorageAdapter {
21
+ private readonly options;
22
+ private _client;
23
+ private _getSignedUrl;
24
+ constructor(options: S3StorageOptions);
25
+ private getClient;
26
+ private getSignedUrl;
27
+ put(data: Buffer, options?: PutOptions): Promise<StoredFile>;
28
+ delete(key: string): Promise<void>;
29
+ presignPut(key: string, options?: PresignPutOptions): Promise<PresignedUpload>;
30
+ presignGet(key: string, options?: PresignGetOptions): Promise<string>;
31
+ publicUrl(key: string): string;
32
+ }
@@ -0,0 +1,119 @@
1
+ import crypto from 'crypto';
2
+ const SDK_INSTALL_HINT = 'Install with: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner';
3
+ /**
4
+ * S3-compatible storage adapter.
5
+ * Works with AWS S3, Cloudflare R2, MinIO, and any S3-compatible API.
6
+ *
7
+ * Requires optional peer dependencies:
8
+ * @aws-sdk/client-s3
9
+ * @aws-sdk/s3-request-presigner
10
+ */
11
+ export class S3StorageAdapter {
12
+ constructor(options) {
13
+ this._client = null;
14
+ this._getSignedUrl = null;
15
+ this.options = options;
16
+ }
17
+ async getClient() {
18
+ if (this._client)
19
+ return this._client;
20
+ let S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand;
21
+ try {
22
+ const mod = await import('@aws-sdk/client-s3');
23
+ S3Client = mod.S3Client;
24
+ PutObjectCommand = mod.PutObjectCommand;
25
+ DeleteObjectCommand = mod.DeleteObjectCommand;
26
+ GetObjectCommand = mod.GetObjectCommand;
27
+ }
28
+ catch {
29
+ throw new Error(`[LumenJS:Storage] @aws-sdk/client-s3 is required for S3 storage. ${SDK_INSTALL_HINT}`);
30
+ }
31
+ this._client = {
32
+ s3: new S3Client({
33
+ region: this.options.region,
34
+ endpoint: this.options.endpoint,
35
+ credentials: {
36
+ accessKeyId: this.options.accessKeyId,
37
+ secretAccessKey: this.options.secretAccessKey,
38
+ },
39
+ forcePathStyle: !!this.options.endpoint, // required for MinIO
40
+ }),
41
+ PutObjectCommand,
42
+ DeleteObjectCommand,
43
+ GetObjectCommand,
44
+ };
45
+ return this._client;
46
+ }
47
+ async getSignedUrl() {
48
+ if (this._getSignedUrl)
49
+ return this._getSignedUrl;
50
+ try {
51
+ const mod = await import('@aws-sdk/s3-request-presigner');
52
+ this._getSignedUrl = mod.getSignedUrl;
53
+ return this._getSignedUrl;
54
+ }
55
+ catch {
56
+ throw new Error(`[LumenJS:Storage] @aws-sdk/s3-request-presigner is required for presigned URLs. ${SDK_INSTALL_HINT}`);
57
+ }
58
+ }
59
+ async put(data, options) {
60
+ const key = options?.key ?? crypto.randomUUID();
61
+ const mimeType = options?.mimeType ?? 'application/octet-stream';
62
+ const acl = options?.acl ?? 'public-read';
63
+ const { s3, PutObjectCommand } = await this.getClient();
64
+ const cmd = {
65
+ Bucket: this.options.bucket,
66
+ Key: key,
67
+ Body: data,
68
+ ContentType: mimeType,
69
+ ...(options?.fileName
70
+ ? { ContentDisposition: `inline; filename="${options.fileName.replace(/[\r\n"\\]/g, '_')}"` }
71
+ : {}),
72
+ };
73
+ // R2 and some S3-compatible APIs don't support ACL
74
+ if (!this.options.endpoint)
75
+ cmd.ACL = acl;
76
+ await s3.send(new PutObjectCommand(cmd));
77
+ return {
78
+ key,
79
+ url: this.publicUrl(key),
80
+ size: data.length,
81
+ mimeType,
82
+ fileName: options?.fileName ?? key,
83
+ };
84
+ }
85
+ async delete(key) {
86
+ const { s3, DeleteObjectCommand } = await this.getClient();
87
+ await s3.send(new DeleteObjectCommand({ Bucket: this.options.bucket, Key: key }));
88
+ }
89
+ async presignPut(key, options) {
90
+ const expiresIn = options?.expiresIn ?? 3600;
91
+ const expiresAt = new Date(Date.now() + expiresIn * 1000);
92
+ const { s3, PutObjectCommand } = await this.getClient();
93
+ const getSignedUrl = await this.getSignedUrl();
94
+ const command = new PutObjectCommand({
95
+ Bucket: this.options.bucket,
96
+ Key: key,
97
+ ...(options?.mimeType ? { ContentType: options.mimeType } : {}),
98
+ });
99
+ const uploadUrl = await getSignedUrl(s3, command, { expiresIn });
100
+ return { uploadUrl, key, expiresAt: expiresAt.toISOString() };
101
+ }
102
+ async presignGet(key, options) {
103
+ const expiresIn = options?.expiresIn ?? 3600;
104
+ const { s3, GetObjectCommand } = await this.getClient();
105
+ const getSignedUrl = await this.getSignedUrl();
106
+ const command = new GetObjectCommand({ Bucket: this.options.bucket, Key: key });
107
+ return getSignedUrl(s3, command, { expiresIn });
108
+ }
109
+ publicUrl(key) {
110
+ if (this.options.publicBaseUrl) {
111
+ return `${this.options.publicBaseUrl.replace(/\/$/, '')}/${key}`;
112
+ }
113
+ if (this.options.endpoint) {
114
+ // MinIO / R2: {endpoint}/{bucket}/{key}
115
+ return `${this.options.endpoint.replace(/\/$/, '')}/${this.options.bucket}/${key}`;
116
+ }
117
+ return `https://${this.options.bucket}.s3.${this.options.region}.amazonaws.com/${key}`;
118
+ }
119
+ }
@@ -0,0 +1,53 @@
1
+ export interface StoredFile {
2
+ /** Storage key (path within bucket / upload-dir) */
3
+ key: string;
4
+ /** Publicly accessible URL (for public-read files) */
5
+ url: string;
6
+ size: number;
7
+ mimeType: string;
8
+ fileName: string;
9
+ }
10
+ export interface PutOptions {
11
+ /** Custom storage key. Auto-generated UUID if omitted. */
12
+ key?: string;
13
+ mimeType?: string;
14
+ fileName?: string;
15
+ /** Access control. Default: 'public-read' */
16
+ acl?: 'public-read' | 'private';
17
+ }
18
+ export interface PresignPutOptions {
19
+ /** Expected MIME type (enforced in local adapter; advisory on S3 via Content-Type condition) */
20
+ mimeType?: string;
21
+ /** URL expiry in seconds. Default: 3600 */
22
+ expiresIn?: number;
23
+ /** Maximum file size in bytes */
24
+ maxSize?: number;
25
+ }
26
+ export interface PresignGetOptions {
27
+ /** URL expiry in seconds. Default: 3600 */
28
+ expiresIn?: number;
29
+ }
30
+ export interface PresignedUpload {
31
+ /** Presigned URL the client should PUT the encrypted file to */
32
+ uploadUrl: string;
33
+ /** Storage key to reference the file after upload */
34
+ key: string;
35
+ /** ISO 8601 expiry timestamp */
36
+ expiresAt: string;
37
+ }
38
+ /** Storage adapter interface — implement for any backend (S3, R2, MinIO, local disk, etc.) */
39
+ export interface StorageAdapter {
40
+ /** Upload a file buffer server-side. Returns stored file metadata with URL. */
41
+ put(data: Buffer, options?: PutOptions): Promise<StoredFile>;
42
+ /** Delete a file by its storage key. */
43
+ delete(key: string): Promise<void>;
44
+ /**
45
+ * Generate a presigned PUT URL for direct client-to-storage upload.
46
+ * Used for E2E-encrypted chat attachments — the server never sees the plaintext file.
47
+ */
48
+ presignPut(key: string, options?: PresignPutOptions): Promise<PresignedUpload>;
49
+ /** Generate a presigned GET URL for temporarily accessing a private file. */
50
+ presignGet(key: string, options?: PresignGetOptions): Promise<string>;
51
+ /** Return the permanent public URL for a public-read file. */
52
+ publicUrl(key: string): string;
53
+ }
@@ -0,0 +1 @@
1
+ export {};