Merge remote-tracking branch 'origin/main' into standalone

; Conflicts:
;	apps/client/src/layouts/mobile_layout.tsx
;	apps/client/src/services/promoted_attribute_definition_parser.ts
;	apps/server/package.json
;	apps/server/src/becca/entities/bnote.ts
;	apps/server/src/etapi/etapi_utils.ts
;	apps/server/src/etapi/notes.ts
;	apps/server/src/routes/api/clipper.ts
;	apps/server/src/routes/api/export.ts
;	apps/server/src/routes/api/files.ts
;	apps/server/src/routes/api/image.ts
;	apps/server/src/routes/api/import.ts
;	apps/server/src/routes/api/note_map.ts
;	apps/server/src/routes/api/search.ts
;	apps/server/src/routes/api/similar_notes.ts
;	apps/server/src/routes/api/sync.ts
;	apps/server/src/routes/error_handlers.ts
;	apps/server/src/routes/index.ts
;	apps/server/src/routes/route_api.ts
;	apps/server/src/routes/routes.ts
;	apps/server/src/services/anonymization.ts
;	apps/server/src/services/app_info.ts
;	apps/server/src/services/builtin_attributes.ts
;	apps/server/src/services/export/zip.ts
;	apps/server/src/services/hidden_subtree.ts
;	apps/server/src/services/llm/ai_service_manager.ts
;	apps/server/src/services/llm/context/modules/context_formatter.ts
;	apps/server/src/services/llm/context/note_content.ts
;	apps/server/src/services/llm/formatters/base_formatter.ts
;	apps/server/src/services/llm/formatters/ollama_formatter.ts
;	apps/server/src/services/llm/formatters/openai_formatter.ts
;	apps/server/src/services/llm/tools/read_note_tool.ts
;	apps/server/src/services/note_types.ts
;	apps/server/src/services/notes.ts
;	apps/server/src/services/options.ts
;	apps/server/src/services/options_init.ts
;	apps/server/src/services/search/expressions/note_content_fulltext.ts
;	apps/server/src/services/utils.ts
;	apps/server/src/services/ws.ts
;	apps/server/src/share/content_renderer.ts
;	packages/commons/src/lib/builtin_attributes.ts
;	packages/commons/src/lib/rows.ts
;	packages/trilium-core/src/routes/api/attachments.ts
;	packages/trilium-core/src/routes/api/attributes.ts
;	packages/trilium-core/src/routes/api/branches.ts
;	packages/trilium-core/src/routes/api/notes.ts
;	packages/trilium-core/src/routes/api/recent_changes.ts
;	packages/trilium-core/src/routes/api/revisions.ts
;	packages/trilium-core/src/routes/api/sql.ts
;	packages/trilium-core/src/routes/api/stats.ts
;	packages/trilium-core/src/services/attributes.ts
;	packages/trilium-core/src/services/builtin_attributes.ts
;	packages/trilium-core/src/services/promoted_attribute_definition_parser.ts
;	pnpm-lock.yaml
This commit is contained in:
Elian Doran
2026-03-22 12:56:14 +02:00
805 changed files with 30506 additions and 50300 deletions

View File

@@ -1,14 +1,15 @@
import type { AttachmentRow } from "@triliumnext/commons";
import type { Router } from "express";
import becca from "../becca/becca.js";
import utils from "../services/utils.js";
import eu from "./etapi_utils.js";
import type { ValidatorMap } from "./etapi-interface.js";
import mappers from "./mappers.js";
import v from "./validators.js";
import utils from "../services/utils.js";
import type { Router } from "express";
import type { AttachmentRow } from "@triliumnext/commons";
import type { ValidatorMap } from "./etapi-interface.js";
function register(router: Router) {
eu.route(router, "get", "/etapi/notes/:noteId/attachments", (req, res, next) => {
eu.route<{ noteId: string }>(router, "get", "/etapi/notes/:noteId/attachments", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const attachments = note.getAttachments();
res.json(attachments.map((attachment) => mappers.mapAttachmentToPojo(attachment)));
@@ -41,7 +42,7 @@ function register(router: Router) {
}
});
eu.route(router, "get", "/etapi/attachments/:attachmentId", (req, res, next) => {
eu.route<{ attachmentId: string }>(router, "get", "/etapi/attachments/:attachmentId", (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
res.json(mappers.mapAttachmentToPojo(attachment));
@@ -54,7 +55,7 @@ function register(router: Router) {
position: [v.notNull, v.isInteger]
};
eu.route(router, "patch", "/etapi/attachments/:attachmentId", (req, res, next) => {
eu.route<{ attachmentId: string }>(router, "patch", "/etapi/attachments/:attachmentId", (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
if (attachment.isProtected) {
@@ -67,7 +68,7 @@ function register(router: Router) {
res.json(mappers.mapAttachmentToPojo(attachment));
});
eu.route(router, "get", "/etapi/attachments/:attachmentId/content", (req, res, next) => {
eu.route<{ attachmentId: string }>(router, "get", "/etapi/attachments/:attachmentId/content", (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
if (attachment.isProtected) {
@@ -84,7 +85,7 @@ function register(router: Router) {
res.send(attachment.getContent());
});
eu.route(router, "put", "/etapi/attachments/:attachmentId/content", (req, res, next) => {
eu.route<{ attachmentId: string }>(router, "put", "/etapi/attachments/:attachmentId/content", (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
if (attachment.isProtected) {
@@ -96,7 +97,7 @@ function register(router: Router) {
return res.sendStatus(204);
});
eu.route(router, "delete", "/etapi/attachments/:attachmentId", (req, res, next) => {
eu.route<{ attachmentId: string }>(router, "delete", "/etapi/attachments/:attachmentId", (req, res, next) => {
const attachment = becca.getAttachment(req.params.attachmentId);
if (!attachment) {

View File

@@ -1,14 +1,15 @@
import becca from "../becca/becca.js";
import eu from "./etapi_utils.js";
import mappers from "./mappers.js";
import attributeService from "../services/attributes.js";
import v from "./validators.js";
import type { Router } from "express";
import type { AttributeRow } from "@triliumnext/commons";
import type { Router } from "express";
import becca from "../becca/becca.js";
import attributeService from "../services/attributes.js";
import eu from "./etapi_utils.js";
import type { ValidatorMap } from "./etapi-interface.js";
import mappers from "./mappers.js";
import v from "./validators.js";
function register(router: Router) {
eu.route(router, "get", "/etapi/attributes/:attributeId", (req, res, next) => {
eu.route<{ attributeId: string }>(router, "get", "/etapi/attributes/:attributeId", (req, res, next) => {
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
res.json(mappers.mapAttributeToPojo(attribute));
@@ -51,7 +52,7 @@ function register(router: Router) {
position: [v.notNull, v.isInteger]
};
eu.route(router, "patch", "/etapi/attributes/:attributeId", (req, res, next) => {
eu.route<{ attributeId: string }>(router, "patch", "/etapi/attributes/:attributeId", (req, res, next) => {
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
if (attribute.type === "label") {
@@ -67,7 +68,7 @@ function register(router: Router) {
res.json(mappers.mapAttributeToPojo(attribute));
});
eu.route(router, "delete", "/etapi/attributes/:attributeId", (req, res, next) => {
eu.route<{ attributeId: string }>(router, "delete", "/etapi/attributes/:attributeId", (req, res, next) => {
const attribute = becca.getAttribute(req.params.attributeId);
if (!attribute) {

View File

@@ -1,10 +1,10 @@
import type { Router } from "express";
import eu from "./etapi_utils.js";
import backupService from "../services/backup.js";
import eu from "./etapi_utils.js";
function register(router: Router) {
eu.route(router, "put", "/etapi/backup/:backupName", (req, res, next) => {
eu.route<{ backupName: string }>(router, "put", "/etapi/backup/:backupName", (req, res, next) => {
backupService.backupNow(req.params.backupName)
.then(() => res.sendStatus(204))
.catch(() => res.sendStatus(500));

View File

@@ -1,15 +1,15 @@
import type { BranchRow } from "@triliumnext/commons";
import type { Router } from "express";
import becca from "../becca/becca.js";
import eu from "./etapi_utils.js";
import mappers from "./mappers.js";
import BBranch from "../becca/entities/bbranch.js";
import entityChangesService from "../services/entity_changes.js";
import eu from "./etapi_utils.js";
import mappers from "./mappers.js";
import v from "./validators.js";
import type { BranchRow } from "@triliumnext/commons";
function register(router: Router) {
eu.route(router, "get", "/etapi/branches/:branchId", (req, res, next) => {
eu.route<{ branchId: string }>(router, "get", "/etapi/branches/:branchId", (req, res, next) => {
const branch = eu.getAndCheckBranch(req.params.branchId);
res.json(mappers.mapBranchToPojo(branch));
@@ -37,15 +37,15 @@ function register(router: Router) {
existing.save();
return res.status(200).json(mappers.mapBranchToPojo(existing));
} else {
try {
const branch = new BBranch(params).save();
}
try {
const branch = new BBranch(params).save();
res.status(201).json(mappers.mapBranchToPojo(branch));
} catch (e: any) {
throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message);
}
res.status(201).json(mappers.mapBranchToPojo(branch));
} catch (e: any) {
throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message);
}
});
const ALLOWED_PROPERTIES_FOR_PATCH = {
@@ -54,7 +54,7 @@ function register(router: Router) {
isExpanded: [v.notNull, v.isBoolean]
};
eu.route(router, "patch", "/etapi/branches/:branchId", (req, res, next) => {
eu.route<{ branchId: string }>(router, "patch", "/etapi/branches/:branchId", (req, res, next) => {
const branch = eu.getAndCheckBranch(req.params.branchId);
eu.validateAndPatch(branch, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
@@ -63,7 +63,7 @@ function register(router: Router) {
res.json(mappers.mapBranchToPojo(branch));
});
eu.route(router, "delete", "/etapi/branches/:branchId", (req, res, next) => {
eu.route<{ branchId: string }>(router, "delete", "/etapi/branches/:branchId", (req, res, next) => {
const branch = becca.getBranch(req.params.branchId);
if (!branch) {
@@ -75,7 +75,7 @@ function register(router: Router) {
res.sendStatus(204);
});
eu.route(router, "post", "/etapi/refresh-note-ordering/:parentNoteId", (req, res, next) => {
eu.route<{ parentNoteId: string }>(router, "post", "/etapi/refresh-note-ordering/:parentNoteId", (req, res, next) => {
eu.getAndCheckNote(req.params.parentNoteId);
entityChangesService.putNoteReorderingEntityChange(req.params.parentNoteId, "etapi");

View File

@@ -1,4 +1,5 @@
import type { NextFunction, Request, RequestHandler, Response, Router } from "express";
import type { ParamsDictionary } from "express-serve-static-core";
import becca from "../becca/becca.js";
import { namespace } from "../cls_provider.js";
@@ -51,7 +52,7 @@ function checkEtapiAuth(req: Request, res: Response, next: NextFunction) {
}
}
function processRequest(req: Request, res: Response, routeHandler: ApiRequestHandler, next: NextFunction, method: string, path: string) {
function processRequest<P extends ParamsDictionary>(req: Request<P>, res: Response, routeHandler: ApiRequestHandler<P>, next: NextFunction, method: string, path: string) {
try {
namespace.bindEmitter(req);
namespace.bindEmitter(res);
@@ -75,12 +76,12 @@ function processRequest(req: Request, res: Response, routeHandler: ApiRequestHan
}
}
function route(router: Router, method: HttpMethod, path: string, routeHandler: SyncRouteRequestHandler) {
router[method](path, checkEtapiAuth, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path));
function route<P extends ParamsDictionary>(router: Router, method: HttpMethod, path: string, routeHandler: SyncRouteRequestHandler<P>) {
router[method](path, checkEtapiAuth, (req: Request<P>, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path));
}
function NOT_AUTHENTICATED_ROUTE(router: Router, method: HttpMethod, path: string, middleware: RequestHandler[], routeHandler: SyncRouteRequestHandler) {
router[method](path, ...middleware, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path));
function NOT_AUTHENTICATED_ROUTE<P extends ParamsDictionary>(router: Router, method: HttpMethod, path: string, middleware: RequestHandler[], routeHandler: SyncRouteRequestHandler<P>) {
router[method](path, ...middleware, (req: Request<P>, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path));
}
function getAndCheckNote(noteId: string) {
@@ -88,9 +89,9 @@ function getAndCheckNote(noteId: string) {
if (note) {
return note;
}
}
throw new EtapiError(404, "NOTE_NOT_FOUND", `Note '${noteId}' not found.`);
}
function getAndCheckAttachment(attachmentId: string) {
@@ -98,9 +99,9 @@ function getAndCheckAttachment(attachmentId: string) {
if (attachment) {
return attachment;
}
}
throw new EtapiError(404, "ATTACHMENT_NOT_FOUND", `Attachment '${attachmentId}' not found.`);
}
function getAndCheckBranch(branchId: string) {
@@ -108,9 +109,9 @@ function getAndCheckBranch(branchId: string) {
if (branch) {
return branch;
}
}
throw new EtapiError(404, "BRANCH_NOT_FOUND", `Branch '${branchId}' not found.`);
}
function getAndCheckAttribute(attributeId: string) {
@@ -118,9 +119,9 @@ function getAndCheckAttribute(attributeId: string) {
if (attribute) {
return attribute;
}
}
throw new EtapiError(404, "ATTRIBUTE_NOT_FOUND", `Attribute '${attributeId}' not found.`);
}
function getAndCheckRevision(revisionId: string) {
@@ -128,9 +129,8 @@ function getAndCheckRevision(revisionId: string) {
if (revision) {
return revision;
} else {
throw new EtapiError(404, "REVISION_NOT_FOUND", `Revision '${revisionId}' not found.`);
}
throw new EtapiError(404, "REVISION_NOT_FOUND", `Revision '${revisionId}' not found.`);
}
function validateAndPatch(target: any, source: any, allowedProperties: ValidatorMap) {

View File

@@ -42,7 +42,7 @@ function register(router: Router) {
res.json(resp);
});
eu.route(router, "get", "/etapi/notes/:noteId", (req, res, next) => {
eu.route<{ noteId: string }>(router, "get", "/etapi/notes/:noteId", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
res.json(mappers.mapNoteToPojo(note));
@@ -87,7 +87,7 @@ function register(router: Router) {
utcDateCreated: [v.notNull, v.isString, v.isUtcDateTime]
};
eu.route(router, "patch", "/etapi/notes/:noteId", (req, res, next) => {
eu.route<{ noteId: string }>(router, "patch", "/etapi/notes/:noteId", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
if (note.isProtected) {
@@ -101,7 +101,7 @@ function register(router: Router) {
res.json(mappers.mapNoteToPojo(note));
});
eu.route(router, "delete", "/etapi/notes/:noteId", (req, res, next) => {
eu.route<{ noteId: string }>(router, "delete", "/etapi/notes/:noteId", (req, res, next) => {
const { noteId } = req.params;
const note = becca.getNote(noteId);
@@ -115,7 +115,7 @@ function register(router: Router) {
res.sendStatus(204);
});
eu.route(router, "get", "/etapi/notes/:noteId/content", (req, res, next) => {
eu.route<{ noteId: string }>(router, "get", "/etapi/notes/:noteId/content", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
if (note.isProtected) {
@@ -132,7 +132,7 @@ function register(router: Router) {
res.send(note.getContent());
});
eu.route(router, "put", "/etapi/notes/:noteId/content", (req, res, next) => {
eu.route<{ noteId: string }>(router, "put", "/etapi/notes/:noteId/content", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
if (note.isProtected) {
@@ -147,7 +147,7 @@ function register(router: Router) {
return res.sendStatus(204);
});
eu.route(router, "get", "/etapi/notes/:noteId/export", (req, res, next) => {
eu.route<{ noteId: string }>(router, "get", "/etapi/notes/:noteId/export", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const format = req.query.format || "html";
@@ -164,7 +164,7 @@ function register(router: Router) {
zipExportService.exportToZip(taskContext, branch, format as ExportFormat, res);
});
eu.route(router, "post", "/etapi/notes/:noteId/import", (req, res, next) => {
eu.route<{ noteId: string }>(router, "post", "/etapi/notes/:noteId/import", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const taskContext = new TaskContext("no-progress-reporting", "importNotes", null);
@@ -176,7 +176,7 @@ function register(router: Router) {
}); // we need better error handling here, async errors won't be properly processed.
});
eu.route(router, "post", "/etapi/notes/:noteId/revision", (req, res, next) => {
eu.route<{ noteId: string }>(router, "post", "/etapi/notes/:noteId/revision", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
note.saveRevision();
@@ -184,7 +184,7 @@ function register(router: Router) {
return res.sendStatus(204);
});
eu.route(router, "get", "/etapi/notes/:noteId/attachments", (req, res, next) => {
eu.route<{ noteId: string }>(router, "get", "/etapi/notes/:noteId/attachments", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const attachments = note.getAttachments();

View File

@@ -1,13 +1,14 @@
import type { NoteRow, RecentChangeRow } from "@triliumnext/commons";
import type { Router } from "express";
import becca from "../becca/becca.js";
import noteService from "../services/notes.js";
import protectedSessionService from "../services/protected_session.js";
import sql from "../services/sql.js";
import TaskContext from "../services/task_context.js";
import utils from "../services/utils.js";
import eu from "./etapi_utils.js";
import mappers from "./mappers.js";
import noteService from "../services/notes.js";
import TaskContext from "../services/task_context.js";
import protectedSessionService from "../services/protected_session.js";
import utils from "../services/utils.js";
import type { Router } from "express";
import type { NoteRow, RecentChangeRow } from "@triliumnext/commons";
function register(router: Router) {
// GET /etapi/notes/history - must be registered before /etapi/notes/:noteId routes
@@ -130,7 +131,7 @@ function register(router: Router) {
});
// GET /etapi/notes/:noteId/revisions - List all revisions for a note
eu.route(router, "get", "/etapi/notes/:noteId/revisions", (req, res, next) => {
eu.route<{ noteId: string }>(router, "get", "/etapi/notes/:noteId/revisions", (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const revisions = becca.getRevisionsFromQuery(
@@ -146,7 +147,7 @@ function register(router: Router) {
});
// POST /etapi/notes/:noteId/undelete - Restore a deleted note
eu.route(router, "post", "/etapi/notes/:noteId/undelete", (req, res, next) => {
eu.route<{ noteId: string }>(router, "post", "/etapi/notes/:noteId/undelete", (req, res, next) => {
const { noteId } = req.params;
const noteRow = sql.getRow<NoteRow | null>("SELECT * FROM notes WHERE noteId = ?", [noteId]);
@@ -172,7 +173,7 @@ function register(router: Router) {
});
// GET /etapi/revisions/:revisionId - Get revision metadata
eu.route(router, "get", "/etapi/revisions/:revisionId", (req, res, next) => {
eu.route<{ revisionId: string }>(router, "get", "/etapi/revisions/:revisionId", (req, res, next) => {
const revision = eu.getAndCheckRevision(req.params.revisionId);
if (revision.isProtected) {
@@ -183,7 +184,7 @@ function register(router: Router) {
});
// GET /etapi/revisions/:revisionId/content - Get revision content
eu.route(router, "get", "/etapi/revisions/:revisionId/content", (req, res, next) => {
eu.route<{ revisionId: string }>(router, "get", "/etapi/revisions/:revisionId/content", (req, res, next) => {
const revision = eu.getAndCheckRevision(req.params.revisionId);
if (revision.isProtected) {

View File

@@ -1,8 +1,9 @@
import specialNotesService from "../services/special_notes.js";
import type { Router } from "express";
import dateNotesService from "../services/date_notes.js";
import specialNotesService from "../services/special_notes.js";
import eu from "./etapi_utils.js";
import mappers from "./mappers.js";
import type { Router } from "express";
const getDateInvalidError = (date: string) => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
const getWeekInvalidError = (week: string) => new eu.EtapiError(400, "WEEK_INVALID", `Week "${week}" is not valid.`);
@@ -15,7 +16,7 @@ function isValidDate(date: string) {
}
function register(router: Router) {
eu.route(router, "get", "/etapi/inbox/:date", (req, res, next) => {
eu.route<{ date: string }>(router, "get", "/etapi/inbox/:date", (req, res, next) => {
const { date } = req.params;
if (!isValidDate(date)) {
@@ -25,7 +26,7 @@ function register(router: Router) {
res.json(mappers.mapNoteToPojo(note));
});
eu.route(router, "get", "/etapi/calendar/days/:date", (req, res, next) => {
eu.route<{ date: string }>(router, "get", "/etapi/calendar/days/:date", (req, res, next) => {
const { date } = req.params;
if (!isValidDate(date)) {
@@ -36,7 +37,7 @@ function register(router: Router) {
res.json(mappers.mapNoteToPojo(note));
});
eu.route(router, "get", "/etapi/calendar/week-first-day/:date", (req, res, next) => {
eu.route<{ date: string }>(router, "get", "/etapi/calendar/week-first-day/:date", (req, res, next) => {
const { date } = req.params;
if (!isValidDate(date)) {
@@ -47,7 +48,7 @@ function register(router: Router) {
res.json(mappers.mapNoteToPojo(note));
});
eu.route(router, "get", "/etapi/calendar/weeks/:week", (req, res, next) => {
eu.route<{ week: string }>(router, "get", "/etapi/calendar/weeks/:week", (req, res, next) => {
const { week } = req.params;
if (!/[0-9]{4}-W[0-9]{2}/.test(week)) {
@@ -63,7 +64,7 @@ function register(router: Router) {
res.json(mappers.mapNoteToPojo(note));
});
eu.route(router, "get", "/etapi/calendar/months/:month", (req, res, next) => {
eu.route<{ month: string }>(router, "get", "/etapi/calendar/months/:month", (req, res, next) => {
const { month } = req.params;
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
@@ -74,7 +75,7 @@ function register(router: Router) {
res.json(mappers.mapNoteToPojo(note));
});
eu.route(router, "get", "/etapi/calendar/years/:year", (req, res, next) => {
eu.route<{ year: string }>(router, "get", "/etapi/calendar/years/:year", (req, res, next) => {
const { year } = req.params;
if (!/[0-9]{4}/.test(year)) {