diff --git a/apps/server/package.json b/apps/server/package.json index 70705ec6f..97380f2e1 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -2,7 +2,6 @@ "name": "@triliumnext/server", "version": "0.0.1", "private": true, - "type": "module", "dependencies": { "express": "4.21.2", "express-openid-connect": "^2.17.1", diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index e79607ed1..b408dae14 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -20,120 +20,121 @@ import openID from "./services/open_id.js"; import { t } from "i18next"; import eventService from "./services/events.js"; import log from "./services/log.js"; +import "./services/handlers.js"; +import "./becca/becca_loader.js"; -await import("./services/handlers.js"); -await import("./becca/becca_loader.js"); +export default async function buildApp() { + const app = express(); -const app = express(); + const scriptDir = dirname(fileURLToPath(import.meta.url)); -const scriptDir = dirname(fileURLToPath(import.meta.url)); + // Initialize DB + sql_init.initializeDb(); -// Initialize DB -sql_init.initializeDb(); + // Listen for database initialization event + eventService.subscribe(eventService.DB_INITIALIZED, async () => { + try { + log.info("Database initialized, setting up LLM features"); -// Listen for database initialization event -eventService.subscribe(eventService.DB_INITIALIZED, async () => { - try { - log.info("Database initialized, setting up LLM features"); + // Initialize embedding providers + const { initializeEmbeddings } = await import("./services/llm/embeddings/init.js"); + await initializeEmbeddings(); - // Initialize embedding providers - const { initializeEmbeddings } = await import("./services/llm/embeddings/init.js"); - await initializeEmbeddings(); + // Initialize the index service for LLM functionality + const { default: indexService } = await import("./services/llm/index_service.js"); + await indexService.initialize().catch(e => console.error("Failed to initialize index service:", e)); - // Initialize the index service for LLM functionality - const { default: indexService } = await import("./services/llm/index_service.js"); - await indexService.initialize().catch(e => console.error("Failed to initialize index service:", e)); + log.info("LLM features initialized successfully"); + } catch (error) { + console.error("Error initializing LLM features:", error); + } + }); - log.info("LLM features initialized successfully"); - } catch (error) { - console.error("Error initializing LLM features:", error); + // Initialize LLM features only if database is already initialized + if (sql_init.isDbInitialized()) { + try { + // Initialize embedding providers + const { initializeEmbeddings } = await import("./services/llm/embeddings/init.js"); + await initializeEmbeddings(); + + // Initialize the index service for LLM functionality + const { default: indexService } = await import("./services/llm/index_service.js"); + await indexService.initialize().catch(e => console.error("Failed to initialize index service:", e)); + } catch (error) { + console.error("Error initializing LLM features:", error); + } + } else { + console.log("Database not initialized yet. LLM features will be initialized after setup."); } -}); -// Initialize LLM features only if database is already initialized -if (sql_init.isDbInitialized()) { - try { - // Initialize embedding providers - const { initializeEmbeddings } = await import("./services/llm/embeddings/init.js"); - await initializeEmbeddings(); + // view engine setup + app.set("views", path.join(scriptDir, "views")); + app.set("view engine", "ejs"); - // Initialize the index service for LLM functionality - const { default: indexService } = await import("./services/llm/index_service.js"); - await indexService.initialize().catch(e => console.error("Failed to initialize index service:", e)); - } catch (error) { - console.error("Error initializing LLM features:", error); + app.use((req, res, next) => { + // set CORS header + if (config["Network"]["corsAllowOrigin"]) { + res.header("Access-Control-Allow-Origin", config["Network"]["corsAllowOrigin"]); + } + if (config["Network"]["corsAllowMethods"]) { + res.header("Access-Control-Allow-Methods", config["Network"]["corsAllowMethods"]); + } + if (config["Network"]["corsAllowHeaders"]) { + res.header("Access-Control-Allow-Headers", config["Network"]["corsAllowHeaders"]); + } + + res.locals.t = t; + return next(); + }); + + if (!utils.isElectron) { + app.use(compression()); // HTTP compression } -} else { - console.log("Database not initialized yet. LLM features will be initialized after setup."); + + app.use( + helmet({ + hidePoweredBy: false, // errors out in electron + contentSecurityPolicy: false, + crossOriginEmbedderPolicy: false + }) + ); + + app.use(express.text({ limit: "500mb" })); + app.use(express.json({ limit: "500mb" })); + app.use(express.raw({ limit: "500mb" })); + app.use(express.urlencoded({ extended: false })); + app.use(cookieParser()); + app.use(express.static(path.join(scriptDir, "public/root"))); + app.use(`/manifest.webmanifest`, express.static(path.join(scriptDir, "public/manifest.webmanifest"))); + app.use(`/robots.txt`, express.static(path.join(scriptDir, "public/robots.txt"))); + app.use(`/icon.png`, express.static(path.join(scriptDir, "public/icon.png"))); + app.use(sessionParser); + app.use(favicon(`${scriptDir}/../assets/icon.ico`)); + + if (openID.isOpenIDEnabled()) + app.use(auth(openID.generateOAuthConfig())); + + await assets.register(app); + routes.register(app); + custom.register(app); + error_handlers.register(app); + + // triggers sync timer + await import("./services/sync.js"); + + // triggers backup timer + await import("./services/backup.js"); + + // trigger consistency checks timer + await import("./services/consistency_checks.js"); + + await import("./services/scheduler.js"); + + startScheduledCleanup(); + + if (utils.isElectron) { + (await import("@electron/remote/main/index.js")).initialize(); + } + + return app; } - -// view engine setup -app.set("views", path.join(scriptDir, "views")); -app.set("view engine", "ejs"); - -app.use((req, res, next) => { - // set CORS header - if (config["Network"]["corsAllowOrigin"]) { - res.header("Access-Control-Allow-Origin", config["Network"]["corsAllowOrigin"]); - } - if (config["Network"]["corsAllowMethods"]) { - res.header("Access-Control-Allow-Methods", config["Network"]["corsAllowMethods"]); - } - if (config["Network"]["corsAllowHeaders"]) { - res.header("Access-Control-Allow-Headers", config["Network"]["corsAllowHeaders"]); - } - - res.locals.t = t; - return next(); -}); - -if (!utils.isElectron) { - app.use(compression()); // HTTP compression -} - -app.use( - helmet({ - hidePoweredBy: false, // errors out in electron - contentSecurityPolicy: false, - crossOriginEmbedderPolicy: false - }) -); - -app.use(express.text({ limit: "500mb" })); -app.use(express.json({ limit: "500mb" })); -app.use(express.raw({ limit: "500mb" })); -app.use(express.urlencoded({ extended: false })); -app.use(cookieParser()); -app.use(express.static(path.join(scriptDir, "public/root"))); -app.use(`/manifest.webmanifest`, express.static(path.join(scriptDir, "public/manifest.webmanifest"))); -app.use(`/robots.txt`, express.static(path.join(scriptDir, "public/robots.txt"))); -app.use(`/icon.png`, express.static(path.join(scriptDir, "public/icon.png"))); -app.use(sessionParser); -app.use(favicon(`${scriptDir}/../assets/icon.ico`)); - -if (openID.isOpenIDEnabled()) - app.use(auth(openID.generateOAuthConfig())); - -await assets.register(app); -routes.register(app); -custom.register(app); -error_handlers.register(app); - -// triggers sync timer -await import("./services/sync.js"); - -// triggers backup timer -await import("./services/backup.js"); - -// trigger consistency checks timer -await import("./services/consistency_checks.js"); - -await import("./services/scheduler.js"); - -startScheduledCleanup(); - -if (utils.isElectron) { - (await import("@electron/remote/main/index.js")).initialize(); -} - -export default app; diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 45ee3fd19..b334c148d 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -6,8 +6,8 @@ import { initializeTranslations } from "./services/i18n.js"; async function startApplication() { + await initializeTranslations(); await import("./www.js"); } -await initializeTranslations(); -await startApplication(); +startApplication(); diff --git a/apps/server/src/routes/api_docs.ts b/apps/server/src/routes/api_docs.ts index df069a24f..78d790bb5 100644 --- a/apps/server/src/routes/api_docs.ts +++ b/apps/server/src/routes/api_docs.ts @@ -1,16 +1,17 @@ import type { Application } from "express"; import swaggerUi from "swagger-ui-express"; -import { readFile } from "fs/promises"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; import yaml from "js-yaml"; import type { JsonObject } from "swagger-ui-express"; +import { readFileSync } from "fs"; const __dirname = dirname(fileURLToPath(import.meta.url)); -const etapiDocument = yaml.load(await readFile(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8")) as JsonObject; -const apiDocument = JSON.parse(await readFile(join(__dirname, "api", "openapi.json"), "utf-8")); -function register(app: Application) { +export default function register(app: Application) { + const etapiDocument = yaml.load(readFileSync(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8")) as JsonObject; + const apiDocument = JSON.parse(readFileSync(join(__dirname, "api", "openapi.json"), "utf-8")); + app.use( "/etapi/docs/", swaggerUi.serveFiles(etapiDocument), @@ -29,7 +30,3 @@ function register(app: Application) { }) ); } - -export default { - register -}; diff --git a/apps/server/src/routes/electron.ts b/apps/server/src/routes/electron.ts index 05e21e77b..eac07e171 100644 --- a/apps/server/src/routes/electron.ts +++ b/apps/server/src/routes/electron.ts @@ -1,5 +1,4 @@ import { ipcMain } from "electron"; -import type { Application } from "express"; interface Response { statusCode: number; @@ -10,7 +9,7 @@ interface Response { send: (obj: {}) => void; // eslint-disable-line @typescript-eslint/no-empty-object-type } -function init(app: Application) { +function init(app: Express.Application) { ipcMain.on("server-request", (event, arg) => { const req = { url: arg.url, diff --git a/apps/server/src/services/ws.ts b/apps/server/src/services/ws.ts index d02db8a40..8dde51639 100644 --- a/apps/server/src/services/ws.ts +++ b/apps/server/src/services/ws.ts @@ -12,19 +12,6 @@ import AbstractBeccaEntity from "../becca/entities/abstract_becca_entity.js"; import type { IncomingMessage, Server as HttpServer } from "http"; import type { EntityChange } from "./entity_changes_interface.js"; -if (isDev) { - const chokidar = (await import("chokidar")).default; - const debounce = (await import("debounce")).default; - const debouncedReloadFrontend = debounce(() => reloadFrontend("source code change"), 200); - chokidar - .watch("src/public", { - ignored: "src/public/app/doc_notes/en/User Guide" - }) - .on("add", debouncedReloadFrontend) - .on("change", debouncedReloadFrontend) - .on("unlink", debouncedReloadFrontend); -} - let webSocketServer!: WebSocketServer; let lastSyncedPush: number | null = null; diff --git a/apps/server/src/www.ts b/apps/server/src/www.ts index b62157931..3261a80e3 100644 --- a/apps/server/src/www.ts +++ b/apps/server/src/www.ts @@ -1,6 +1,5 @@ #!/usr/bin/env node -import app from "./app.js"; import sessionParser from "./routes/session_parser.js"; import fs from "fs"; import http from "http"; @@ -13,6 +12,8 @@ import ws from "./services/ws.js"; import utils from "./services/utils.js"; import port from "./services/port.js"; import host from "./services/host.js"; +import buildApp from "./app.js"; +import type { Express } from "express"; const MINIMUM_NODE_VERSION = "20.0.0"; @@ -47,6 +48,8 @@ tmp.setGracefulCleanup(); startTrilium(); async function startTrilium() { + const app = await buildApp(); + /** * The intended behavior is to detect when a second instance is running, in that case open the old instance * instead of the new one. This is complicated by the fact that it is possible to run multiple instances of Trilium @@ -74,7 +77,7 @@ async function startTrilium() { log.info(`CPU model: ${cpuModel}, logical cores: ${cpuInfos.length}, freq: ${cpuInfos[0].speed} Mhz`); } - const httpServer = startHttpServer(); + const httpServer = startHttpServer(app); ws.init(httpServer, sessionParser as any); // TODO: Not sure why session parser is incompatible. @@ -84,7 +87,7 @@ async function startTrilium() { } } -function startHttpServer() { +function startHttpServer(app: Express) { app.set("port", port); app.set("host", host); diff --git a/apps/server/tsconfig.app.json b/apps/server/tsconfig.app.json index 0957022f5..bfdaf3cbf 100644 --- a/apps/server/tsconfig.app.json +++ b/apps/server/tsconfig.app.json @@ -1,9 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "module": "ESNext", - "target": "ESNext", - "moduleResolution": "bundler", + "module": "NodeNext", + "moduleResolution": "nodenext", "outDir": "dist", "types": [ "node", diff --git a/apps/server/webpack.config.cjs b/apps/server/webpack.config.cjs index c877a8f39..3a50b8494 100644 --- a/apps/server/webpack.config.cjs +++ b/apps/server/webpack.config.cjs @@ -3,8 +3,7 @@ const { join } = require('path'); module.exports = { output: { - path: join(__dirname, 'dist'), - libraryTarget: "module" + path: join(__dirname, 'dist') }, plugins: [ new NxAppWebpackPlugin({ @@ -17,8 +16,5 @@ module.exports = { outputHashing: 'none', generatePackageJson: true, }) - ], - experiments: { - outputModule: true - } + ] };