@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
package/dist/db/index.js CHANGED
@@ -1,67 +1,223 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
+ import { createRequire } from 'module';
4
+ import { AsyncLocalStorage } from 'async_hooks';
3
5
  import Database from 'better-sqlite3';
6
+ const _require = createRequire(import.meta.url);
4
7
  import { getProjectDir } from './context.js';
5
- import { readProjectConfig } from '../dev-server/config.js';
8
+ import { autoMigrate } from './auto-migrate.js';
9
+ import { getRegisteredTables } from './table.js';
10
+ import { autoSeed } from './seed.js';
11
+ export { defineTable, getRegisteredTables } from './table.js';
12
+ export { waitForSeed } from './seed.js';
13
+ // ── Abstract async interface ───────────────────────────────────────────────
6
14
  export class LumenDb {
15
+ constructor() {
16
+ this.isPg = false;
17
+ }
18
+ /** Raw SQLite Database — only available in SQLite mode */
19
+ get raw() {
20
+ throw new Error('raw is only available in SQLite mode');
21
+ }
22
+ }
23
+ // ── SQLite implementation ─────────────────────────────────────────────────
24
+ class LumenDbSqlite extends LumenDb {
7
25
  constructor(db) {
26
+ super();
8
27
  this.db = db;
28
+ this.isPg = false;
9
29
  }
10
- /** SELECT multiple rows */
11
- all(sql, ...params) {
30
+ async all(sql, ...params) {
12
31
  return this.db.prepare(sql).all(...params);
13
32
  }
14
- /** SELECT single row */
15
- get(sql, ...params) {
33
+ async get(sql, ...params) {
16
34
  return this.db.prepare(sql).get(...params);
17
35
  }
18
- /** INSERT/UPDATE/DELETE */
19
- run(sql, ...params) {
20
- const result = this.db.prepare(sql).run(...params);
36
+ async run(sql, ...params) {
37
+ const stmt = this.db.prepare(sql);
38
+ // RETURNING clause → use .get() to capture the returned row's id
39
+ if (/\bRETURNING\b/i.test(sql)) {
40
+ const row = stmt.get(...params);
41
+ return { changes: 1, lastInsertRowid: row?.id ?? 0 };
42
+ }
43
+ const result = stmt.run(...params);
21
44
  return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
22
45
  }
23
- /** Multi-statement DDL */
24
- exec(sql) {
46
+ async exec(sql) {
25
47
  this.db.exec(sql);
26
48
  }
27
- /** Access the underlying better-sqlite3 instance */
49
+ async withTransaction(fn) {
50
+ this.db.exec('BEGIN');
51
+ try {
52
+ const result = await fn();
53
+ this.db.exec('COMMIT');
54
+ return result;
55
+ }
56
+ catch (e) {
57
+ try {
58
+ this.db.exec('ROLLBACK');
59
+ }
60
+ catch { }
61
+ throw e;
62
+ }
63
+ }
28
64
  get raw() {
29
65
  return this.db;
30
66
  }
31
67
  }
68
+ // ── PostgreSQL implementation ─────────────────────────────────────────────
69
+ // AsyncLocalStorage stores the current transactional pg client so queries
70
+ // within withTransaction() use the same connection.
71
+ const pgTxClient = new AsyncLocalStorage();
72
+ class LumenDbPg extends LumenDb {
73
+ constructor(connectionString) {
74
+ super();
75
+ this.isPg = true;
76
+ // Use createRequire for CJS compatibility in ESM context
77
+ const { Pool } = _require('pg');
78
+ this.pool = new Pool({ connectionString });
79
+ this.pool.on('error', (err) => {
80
+ console.error('[LumenJS PG] Pool error:', err.message);
81
+ });
82
+ }
83
+ /** Convert SQLite-flavoured SQL to PostgreSQL */
84
+ convertSql(sql) {
85
+ // INSERT OR IGNORE → INSERT ... ON CONFLICT DO NOTHING
86
+ const isInsertOrIgnore = /^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/i.test(sql);
87
+ let result = isInsertOrIgnore
88
+ ? sql.replace(/INSERT\s+OR\s+IGNORE\s+INTO\b/i, 'INSERT INTO')
89
+ : sql;
90
+ // Replace ? with $1, $2, ... positional parameters
91
+ let i = 0;
92
+ result = result.replace(/\?/g, () => `$${++i}`);
93
+ // datetime('now', '+N unit') / datetime('now', '-N unit')
94
+ result = result.replace(/datetime\s*\(\s*'now'\s*,\s*'([+-]\d+)\s+(\w+)'\s*\)/gi, (_m, offset, unit) => {
95
+ const op = offset.startsWith('-') ? '-' : '+';
96
+ const amount = offset.replace(/^[+-]/, '');
97
+ return `NOW() ${op} INTERVAL '${amount} ${unit}'`;
98
+ });
99
+ // datetime('now')
100
+ result = result.replace(/datetime\s*\(\s*'now'\s*\)/gi, 'NOW()');
101
+ // json_each(expr) alias → jsonb_array_elements_text(expr::jsonb) AS alias
102
+ result = result.replace(/\bjson_each\s*\(([^)]+)\)\s+(\w+)/gi, (_m, expr, alias) => `jsonb_array_elements_text(${expr.trim()}::jsonb) AS ${alias}`);
103
+ // alias.value (from json_each) → alias (jsonb_array_elements_text column is the alias itself)
104
+ result = result.replace(/\b(\w+)\.value\b/g, '$1');
105
+ if (isInsertOrIgnore) {
106
+ result = result.trimEnd() + ' ON CONFLICT DO NOTHING';
107
+ }
108
+ return result;
109
+ }
110
+ getClient() {
111
+ // Return transactional client if inside withTransaction, otherwise use pool
112
+ return pgTxClient.getStore() ?? this.pool;
113
+ }
114
+ async all(sql, ...params) {
115
+ const pgSql = this.convertSql(sql);
116
+ const res = await this.getClient().query(pgSql, params);
117
+ return res.rows;
118
+ }
119
+ async get(sql, ...params) {
120
+ const pgSql = this.convertSql(sql);
121
+ const res = await this.getClient().query(pgSql, params);
122
+ return res.rows[0];
123
+ }
124
+ async run(sql, ...params) {
125
+ const pgSql = this.convertSql(sql);
126
+ const res = await this.getClient().query(pgSql, params);
127
+ const lastInsertRowid = res.rows?.[0]?.id ?? 0;
128
+ return { changes: res.rowCount ?? 0, lastInsertRowid };
129
+ }
130
+ async exec(sql) {
131
+ // exec is used for DDL / multi-statement — skip placeholder conversion
132
+ // (DDL has no ? params), just normalize datetime() calls
133
+ const pgSql = this.convertSql(sql);
134
+ await this.getClient().query(pgSql);
135
+ }
136
+ async withTransaction(fn) {
137
+ const client = await this.pool.connect();
138
+ try {
139
+ await client.query('BEGIN');
140
+ // All db calls inside fn() will use this client via AsyncLocalStorage
141
+ const result = await pgTxClient.run(client, fn);
142
+ await client.query('COMMIT');
143
+ return result;
144
+ }
145
+ catch (e) {
146
+ try {
147
+ await client.query('ROLLBACK');
148
+ }
149
+ catch { }
150
+ throw e;
151
+ }
152
+ finally {
153
+ client.release();
154
+ }
155
+ }
156
+ }
157
+ // ── Singleton management ──────────────────────────────────────────────────
32
158
  let _instance = null;
159
+ let _migrationPromise = null;
33
160
  export function useDb() {
34
161
  if (_instance)
35
162
  return _instance;
163
+ const databaseUrl = process.env.DATABASE_URL;
164
+ if (databaseUrl && (databaseUrl.startsWith('postgres') || databaseUrl.startsWith('postgresql'))) {
165
+ _instance = new LumenDbPg(databaseUrl);
166
+ const projectDir = (() => {
167
+ try {
168
+ return getProjectDir();
169
+ }
170
+ catch {
171
+ return process.env.LUMENJS_PROJECT_DIR || process.cwd();
172
+ }
173
+ })();
174
+ _migrationPromise = runPgMigrations(_instance, projectDir);
175
+ _migrationPromise.catch(err => console.error('[LumenJS] PG migration error:', err.message));
176
+ return _instance;
177
+ }
178
+ // SQLite mode
36
179
  const projectDir = getProjectDir();
37
- const config = readProjectConfig(projectDir);
38
- const dbRelPath = config.db?.path || 'data/db.sqlite';
180
+ const dbRelPath = (() => {
181
+ try {
182
+ const c = fs.readFileSync(path.join(projectDir, 'lumenjs.config.ts'), 'utf-8');
183
+ const m = c.match(/db\s*:\s*\{[^}]*path\s*:\s*['"]([^'"]+)['"]/);
184
+ return m ? m[1] : 'data/db.sqlite';
185
+ }
186
+ catch {
187
+ return 'data/db.sqlite';
188
+ }
189
+ })();
39
190
  const dbPath = path.resolve(projectDir, dbRelPath);
40
- // Auto-create directory
41
191
  const dbDir = path.dirname(dbPath);
42
- if (!fs.existsSync(dbDir)) {
192
+ if (!fs.existsSync(dbDir))
43
193
  fs.mkdirSync(dbDir, { recursive: true });
44
- }
45
- const db = new Database(dbPath);
46
- db.pragma('journal_mode = WAL');
47
- db.pragma('foreign_keys = ON');
48
- _instance = new LumenDb(db);
49
- // Run pending migrations
50
- runMigrations(db, projectDir);
194
+ const rawDb = new Database(dbPath);
195
+ rawDb.pragma('journal_mode = WAL');
196
+ rawDb.pragma('foreign_keys = ON');
197
+ _instance = new LumenDbSqlite(rawDb);
198
+ autoMigrate(rawDb, projectDir, getRegisteredTables());
199
+ runSqliteMigrations(rawDb, projectDir);
200
+ autoSeed(rawDb, projectDir);
51
201
  return _instance;
52
202
  }
53
- function runMigrations(db, projectDir) {
203
+ /**
204
+ * Returns a promise that resolves when the initial DB migrations have completed.
205
+ * Useful for waiting before serving the first request in PG mode.
206
+ */
207
+ export function waitForMigrations() {
208
+ return _migrationPromise ?? Promise.resolve();
209
+ }
210
+ // ── SQLite migration runner ───────────────────────────────────────────────
211
+ function runSqliteMigrations(db, projectDir) {
54
212
  const migrationsDir = path.join(projectDir, 'data', 'migrations');
55
213
  if (!fs.existsSync(migrationsDir))
56
214
  return;
57
- // Ensure tracking table exists
58
215
  db.exec(`CREATE TABLE IF NOT EXISTS _lumen_migrations (
59
216
  id INTEGER PRIMARY KEY AUTOINCREMENT,
60
217
  name TEXT NOT NULL UNIQUE,
61
218
  applied_at TEXT NOT NULL DEFAULT (datetime('now'))
62
219
  )`);
63
- const applied = new Set(db.prepare('SELECT name FROM _lumen_migrations').all()
64
- .map((row) => row.name));
220
+ const applied = new Set(db.prepare('SELECT name FROM _lumen_migrations').all().map((r) => r.name));
65
221
  const files = fs.readdirSync(migrationsDir)
66
222
  .filter(f => f.endsWith('.sql'))
67
223
  .sort();
@@ -77,3 +233,26 @@ function runMigrations(db, projectDir) {
77
233
  console.log(`[LumenJS] Applied migration: ${file}`);
78
234
  }
79
235
  }
236
+ // ── PostgreSQL migration runner ───────────────────────────────────────────
237
+ async function runPgMigrations(db, projectDir) {
238
+ const migrationsDir = path.join(projectDir, 'data', 'migrations', 'postgres');
239
+ if (!fs.existsSync(migrationsDir))
240
+ return;
241
+ await db.exec(`CREATE TABLE IF NOT EXISTS _lumen_migrations (
242
+ id BIGSERIAL PRIMARY KEY,
243
+ name TEXT NOT NULL UNIQUE,
244
+ applied_at TIMESTAMP NOT NULL DEFAULT NOW()
245
+ )`);
246
+ const applied = new Set((await db.all('SELECT name FROM _lumen_migrations')).map(r => r.name));
247
+ const files = fs.readdirSync(migrationsDir)
248
+ .filter(f => f.endsWith('.sql'))
249
+ .sort();
250
+ for (const file of files) {
251
+ if (applied.has(file))
252
+ continue;
253
+ const sql = fs.readFileSync(path.join(migrationsDir, file), 'utf-8');
254
+ await db.exec(sql);
255
+ await db.run('INSERT INTO _lumen_migrations (name) VALUES (?)', file);
256
+ console.log(`[LumenJS] Applied PG migration: ${file}`);
257
+ }
258
+ }
@@ -0,0 +1,12 @@
1
+ import Database from 'better-sqlite3';
2
+ /**
3
+ * Auto-seed on first DB creation. Called from useDb() after migrations.
4
+ * Only runs in SQLite mode (PG seeds are handled separately).
5
+ */
6
+ export declare function autoSeed(db: Database.Database, projectDir: string): void;
7
+ /**
8
+ * Returns a promise that resolves when any in-progress seed completes.
9
+ */
10
+ export declare function waitForSeed(): Promise<void>;
11
+ /** Run seed via CLI. If force=true, re-run even if already applied. */
12
+ export declare function runSeed(projectDir: string, force?: boolean): Promise<void>;
@@ -0,0 +1,88 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ const SEED_TABLE = '_lumen_seed_applied';
4
+ const SEED_NAME = 'data/seed.ts';
5
+ function ensureSeedTableSqlite(db) {
6
+ db.exec(`CREATE TABLE IF NOT EXISTS ${SEED_TABLE} (
7
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
8
+ name TEXT NOT NULL UNIQUE,
9
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
10
+ )`);
11
+ }
12
+ function isSeedAppliedSqlite(db) {
13
+ ensureSeedTableSqlite(db);
14
+ const row = db.prepare(`SELECT 1 FROM ${SEED_TABLE} WHERE name = ?`).get(SEED_NAME);
15
+ return !!row;
16
+ }
17
+ function markSeedAppliedSqlite(db) {
18
+ db.prepare(`INSERT OR IGNORE INTO ${SEED_TABLE} (name) VALUES (?)`).run(SEED_NAME);
19
+ }
20
+ async function loadAndRunSeed(projectDir) {
21
+ const seedPath = path.join(projectDir, 'data', 'seed.ts');
22
+ const mod = await import(/* @vite-ignore */ seedPath);
23
+ const seedFn = mod.default || mod;
24
+ if (typeof seedFn === 'function') {
25
+ await seedFn();
26
+ }
27
+ }
28
+ let _seedPromise = null;
29
+ /**
30
+ * Auto-seed on first DB creation. Called from useDb() after migrations.
31
+ * Only runs in SQLite mode (PG seeds are handled separately).
32
+ */
33
+ export function autoSeed(db, projectDir) {
34
+ if (isSeedAppliedSqlite(db))
35
+ return;
36
+ const seedPath = path.join(projectDir, 'data', 'seed.ts');
37
+ if (!fs.existsSync(seedPath))
38
+ return;
39
+ console.log('[LumenJS] Running seed file...');
40
+ markSeedAppliedSqlite(db);
41
+ _seedPromise = loadAndRunSeed(projectDir)
42
+ .then(() => {
43
+ console.log('[LumenJS] Seed applied.');
44
+ })
45
+ .catch(err => {
46
+ console.error('[LumenJS] Failed to run seed:', err);
47
+ db.prepare(`DELETE FROM ${SEED_TABLE} WHERE name = ?`).run(SEED_NAME);
48
+ })
49
+ .finally(() => {
50
+ _seedPromise = null;
51
+ });
52
+ }
53
+ /**
54
+ * Returns a promise that resolves when any in-progress seed completes.
55
+ */
56
+ export function waitForSeed() {
57
+ return _seedPromise ?? Promise.resolve();
58
+ }
59
+ /** Run seed via CLI. If force=true, re-run even if already applied. */
60
+ export async function runSeed(projectDir, force = false) {
61
+ const seedPath = path.join(projectDir, 'data', 'seed.ts');
62
+ if (!fs.existsSync(seedPath)) {
63
+ console.error(`[LumenJS] Seed file not found: ${seedPath}`);
64
+ process.exit(1);
65
+ }
66
+ const { useDb } = await import('./index.js');
67
+ const lumenDb = useDb();
68
+ // PG mode: seeds are not tracked via SQLite mechanism
69
+ if (lumenDb.isPg) {
70
+ if (!force) {
71
+ console.log('[LumenJS] PG mode: skipping seed tracking check. Use --force to run anyway.');
72
+ return;
73
+ }
74
+ await loadAndRunSeed(projectDir);
75
+ console.log('[LumenJS] Seed applied.');
76
+ return;
77
+ }
78
+ const db = lumenDb.raw;
79
+ ensureSeedTableSqlite(db);
80
+ if (!force && isSeedAppliedSqlite(db)) {
81
+ console.log('[LumenJS] Seed already applied. Use --force to re-run.');
82
+ return;
83
+ }
84
+ console.log('[LumenJS] Running seed file...');
85
+ await loadAndRunSeed(projectDir);
86
+ markSeedAppliedSqlite(db);
87
+ console.log('[LumenJS] Seed applied.');
88
+ }
@@ -0,0 +1,10 @@
1
+ export interface TableColumn {
2
+ name: string;
3
+ type: string;
4
+ }
5
+ export interface TableDefinition {
6
+ name: string;
7
+ columns: TableColumn[];
8
+ }
9
+ export declare function defineTable<T extends Record<string, string>>(name: string, columns: T): T;
10
+ export declare function getRegisteredTables(): Map<string, TableDefinition>;
@@ -0,0 +1,12 @@
1
+ const _registry = new Map();
2
+ export function defineTable(name, columns) {
3
+ const parsed = Object.entries(columns).map(([colName, colType]) => ({
4
+ name: colName,
5
+ type: colType,
6
+ }));
7
+ _registry.set(name, { name, columns: parsed });
8
+ return columns;
9
+ }
10
+ export function getRegisteredTables() {
11
+ return _registry;
12
+ }
@@ -1,8 +1,11 @@
1
+ import type { SecurityHeadersConfig } from '../shared/security-headers.js';
2
+ import type { RateLimitConfig } from '../shared/rate-limit.js';
1
3
  export interface I18nConfig {
2
4
  locales: string[];
3
5
  defaultLocale: string;
4
6
  prefixDefault: boolean;
5
7
  }
8
+ export type PrefetchStrategy = 'hover' | 'viewport' | 'none';
6
9
  export interface ProjectConfig {
7
10
  title: string;
8
11
  integrations: string[];
@@ -10,6 +13,14 @@ export interface ProjectConfig {
10
13
  db?: {
11
14
  path?: string;
12
15
  };
16
+ prefetch: PrefetchStrategy;
17
+ prerender?: boolean;
18
+ /** App version for health check. Default: reads from package.json. */
19
+ version?: string;
20
+ /** Security headers config. Applied in production. */
21
+ securityHeaders?: SecurityHeadersConfig;
22
+ /** Rate limiting config. Applied in production. */
23
+ rateLimit?: RateLimitConfig;
13
24
  }
14
25
  /**
15
26
  * Reads the project config from lumenjs.config.ts.
@@ -9,9 +9,14 @@ const __dirname = path.dirname(__filename);
9
9
  export function readProjectConfig(projectDir) {
10
10
  let title = 'LumenJS App';
11
11
  let integrations = [];
12
+ let prefetch = 'viewport';
13
+ let prerender;
14
+ let i18n;
15
+ let securityHeaders;
12
16
  const configPath = path.join(projectDir, 'lumenjs.config.ts');
13
17
  if (fs.existsSync(configPath)) {
14
18
  try {
19
+ // Read the config file once and parse all fields from the same content
15
20
  const configContent = fs.readFileSync(configPath, 'utf-8');
16
21
  const titleMatch = configContent.match(/title\s*:\s*['"]([^'"]+)['"]/);
17
22
  if (titleMatch)
@@ -23,14 +28,33 @@ export function readProjectConfig(projectDir) {
23
28
  .map(s => s.trim().replace(/^['"]|['"]$/g, ''))
24
29
  .filter(Boolean);
25
30
  }
26
- }
27
- catch { /* use defaults */ }
28
- }
29
- // Parse i18n config (reuse the same file read)
30
- let i18n;
31
- if (fs.existsSync(configPath)) {
32
- try {
33
- const configContent = fs.readFileSync(configPath, 'utf-8');
31
+ const prefetchMatch = configContent.match(/prefetch\s*:\s*['"]([^'"]+)['"]/);
32
+ if (prefetchMatch) {
33
+ const val = prefetchMatch[1];
34
+ if (val === 'hover' || val === 'viewport' || val === 'none') {
35
+ prefetch = val;
36
+ }
37
+ }
38
+ const prerenderMatch = configContent.match(/prerender\s*:\s*(true|false)/);
39
+ if (prerenderMatch) {
40
+ prerender = prerenderMatch[1] === 'true';
41
+ }
42
+ const secHeadersMatch = configContent.match(/securityHeaders\s*:\s*\{([\s\S]*?)\}/);
43
+ if (secHeadersMatch) {
44
+ const block = secHeadersMatch[1];
45
+ const cspMatch = block.match(/contentSecurityPolicy\s*:\s*"([^"]+)"/)
46
+ || block.match(/contentSecurityPolicy\s*:\s*'([^']+)'/)
47
+ || block.match(/contentSecurityPolicy\s*:\s*`([^`]+)`/);
48
+ const ppMatch = block.match(/permissionsPolicy\s*:\s*'([^']+)'/)
49
+ || block.match(/permissionsPolicy\s*:\s*"([^"]+)"/)
50
+ || block.match(/permissionsPolicy\s*:\s*`([^`]+)`/);
51
+ if (cspMatch || ppMatch) {
52
+ securityHeaders = {
53
+ ...(cspMatch ? { contentSecurityPolicy: cspMatch[1] } : {}),
54
+ ...(ppMatch ? { permissionsPolicy: ppMatch[1] } : {}),
55
+ };
56
+ }
57
+ }
34
58
  const i18nMatch = configContent.match(/i18n\s*:\s*\{([\s\S]*?)\}/);
35
59
  if (i18nMatch) {
36
60
  const block = i18nMatch[1];
@@ -50,23 +74,19 @@ export function readProjectConfig(projectDir) {
50
74
  }
51
75
  }
52
76
  }
53
- catch { /* ignore */ }
77
+ catch { /* use defaults */ }
54
78
  }
55
- // Parse db config
56
- let db;
57
- if (fs.existsSync(configPath)) {
79
+ // Read version from project's package.json
80
+ let version;
81
+ const pkgPath = path.join(projectDir, 'package.json');
82
+ if (fs.existsSync(pkgPath)) {
58
83
  try {
59
- const configContent = fs.readFileSync(configPath, 'utf-8');
60
- const dbMatch = configContent.match(/db\s*:\s*\{([\s\S]*?)\}/);
61
- if (dbMatch) {
62
- const block = dbMatch[1];
63
- const pathMatch = block.match(/path\s*:\s*['"]([^'"]+)['"]/);
64
- db = pathMatch ? { path: pathMatch[1] } : {};
65
- }
84
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
85
+ version = pkg.version;
66
86
  }
67
87
  catch { /* ignore */ }
68
88
  }
69
- return { title, integrations, ...(i18n ? { i18n } : {}), ...(db ? { db } : {}) };
89
+ return { title, integrations, prefetch, version, ...(i18n ? { i18n } : {}), ...(prerender ? { prerender } : {}), ...(securityHeaders ? { securityHeaders } : {}) };
70
90
  }
71
91
  /**
72
92
  * Reads the project title from lumenjs.config.ts (or returns default).
@@ -15,6 +15,10 @@ export interface IndexHtmlOptions {
15
15
  prefixDefault: boolean;
16
16
  };
17
17
  translations?: Record<string, string>;
18
+ prefetch?: string;
19
+ authUser?: any;
20
+ headContent?: string;
21
+ base?: string;
18
22
  }
19
23
  /**
20
24
  * Generates the index.html shell that loads the LumenJS app.
@@ -4,6 +4,9 @@ import { escapeHtml } from '../shared/utils.js';
4
4
  * Includes the router, app shell, and optionally the editor bridge.
5
5
  */
6
6
  export function generateIndexHtml(options) {
7
+ // Note: script src uses /@lumenjs/ (no base prefix) because Vite's
8
+ // transformIndexHtml already prepends config.base to absolute URLs.
9
+ // Including base here would double it when base != '/'.
7
10
  const editorScript = options.editorMode
8
11
  ? `<script type="module" src="/@lumenjs/editor-bridge"></script>`
9
12
  : '';
@@ -29,18 +32,29 @@ export function generateIndexHtml(options) {
29
32
  };
30
33
  i18nScript = `<script type="application/json" id="__nk_i18n__">${JSON.stringify(i18nData).replace(/</g, '\\u003c')}</script>`;
31
34
  }
35
+ // Auth: inline user data for client hydration
36
+ let authScript = '';
37
+ if (options.authUser) {
38
+ authScript = `<script type="application/json" id="__nk_auth__">${JSON.stringify(options.authUser).replace(/</g, '\\u003c')}</script>`;
39
+ }
40
+ // Prefetch strategy inline
41
+ let prefetchScript = '';
42
+ if (options.prefetch && options.prefetch !== 'hover') {
43
+ prefetchScript = `<script type="application/json" id="__nk_prefetch__">${options.prefetch}</script>`;
44
+ }
32
45
  // i18n module is loaded via imports from router-hydration, no separate script needed
33
- const hydrateScript = isSSR
34
- ? `<script type="module">import '@lit-labs/ssr-client/lit-element-hydrate-support.js';</script>`
35
- : '';
46
+ // Hydrate support is always loaded via the app-shell virtual module (first import)
47
+ // to avoid Lit module duplication from separate script tags.
36
48
  const htmlLang = options.locale || 'en';
37
49
  return `<!DOCTYPE html>
38
50
  <html lang="${htmlLang}">
39
51
  <head>
40
52
  <meta charset="UTF-8" />
41
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
53
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content" />
42
54
  <title>${escapeHtml(options.title)}</title>
43
- ${options.integrations?.includes('nuralyui') ? '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@nuralyui/themes@latest/dist/default.css">' : ''}${options.integrations?.includes('tailwind') ? '\n <script type="module">import "/styles/tailwind.css";</script>' : ''}
55
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
56
+ ${options.integrations?.includes('tailwind') ? '<script type="module">import "/styles/tailwind.css";</script>' : ''}
57
+ ${options.headContent || ''}
44
58
  <style>
45
59
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
46
60
  body { font-family: system-ui, -apple-system, sans-serif; min-height: 100vh; }
@@ -48,10 +62,11 @@ export function generateIndexHtml(options) {
48
62
  </style>
49
63
  </head>
50
64
  <body>
65
+ ${prefetchScript}
51
66
  ${i18nScript}
67
+ ${authScript}
52
68
  ${loaderDataScript}
53
69
  ${appTag}
54
- ${hydrateScript}
55
70
  <script type="module" src="/@lumenjs/app-shell"></script>
56
71
  ${editorScript}
57
72
  </body>
@@ -1,9 +1,5 @@
1
1
  export declare const tagToPackage: Record<string, string>;
2
2
  export declare const implicitDeps: Record<string, string[]>;
3
- /**
4
- * NuralyUI component alias map — mirrors the studio astro.config.mjs aliases.
5
- * Points to the component source directories within the studio service.
6
- */
7
3
  export declare function getNuralyUIAliases(nuralyUIPath: string, nuralyCommonPath: string): Record<string, string>;
8
4
  /**
9
5
  * Resolves the NuralyUI source path dynamically.