diff --git a/.vscode/settings.json b/.vscode/settings.json index 144a7ec7a..9c9430d51 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "editor.formatOnSave": true, - "files.eol": "\n" + "files.eol": "\n", + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/_check_ts_progress.sh b/_check_ts_progress.sh new file mode 100755 index 000000000..85b71add8 --- /dev/null +++ b/_check_ts_progress.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +cloc HEAD \ + --git --md \ + --include-lang=javascript,typescript \ + --found=filelist.txt \ + --exclude-dir=public,libraries \ No newline at end of file diff --git a/db/migrations/0216__move_content_into_blobs.js b/db/migrations/0216__move_content_into_blobs.js index d2f7a9f2d..eea287b21 100644 --- a/db/migrations/0216__move_content_into_blobs.js +++ b/db/migrations/0216__move_content_into_blobs.js @@ -1,6 +1,6 @@ module.exports = () => { - const sql = require('../../src/services/sql.js'); - const utils = require('../../src/services/utils.js'); + const sql = require('../../src/services/sql'); + const utils = require('../../src/services/utils'); const existingBlobIds = new Set(); diff --git a/db/migrations/0220__migrate_images_to_attachments.js b/db/migrations/0220__migrate_images_to_attachments.js index 74e743613..a9b2bfdbf 100644 --- a/db/migrations/0220__migrate_images_to_attachments.js +++ b/db/migrations/0220__migrate_images_to_attachments.js @@ -1,9 +1,9 @@ module.exports = () => { const beccaLoader = require('../../src/becca/becca_loader.js'); - const becca = require('../../src/becca/becca.js'); - const cls = require('../../src/services/cls.js'); - const log = require('../../src/services/log.js'); - const sql = require('../../src/services/sql.js'); + const becca = require('../../src/becca/becca'); + const cls = require('../../src/services/cls'); + const log = require('../../src/services/log'); + const sql = require('../../src/services/sql'); cls.init(() => { // emergency disabling of image compression since it appears to make problems in migration to 0.61 @@ -13,7 +13,7 @@ module.exports = () => { for (const note of Object.values(becca.notes)) { try { - const attachment = note.convertToParentAttachment({autoConversion: true}); + const attachment = note.convertToParentAttachment({ autoConversion: true }); if (attachment) { log.info(`Auto-converted note '${note.noteId}' into attachment '${attachment.attachmentId}'.`); diff --git a/docker_healthcheck.js b/docker_healthcheck.js index f483d7a87..4c06a2627 100755 --- a/docker_healthcheck.js +++ b/docker_healthcheck.js @@ -1,7 +1,7 @@ const http = require("http"); const ini = require("ini"); const fs = require("fs"); -const dataDir = require('./src/services/data_dir.js'); +const dataDir = require('./src/services/data_dir'); const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8')); if (config.Network.https) { diff --git a/dump-db/inc/data_key.js b/dump-db/inc/data_key.js index 58d3dd850..1dfc0dacf 100644 --- a/dump-db/inc/data_key.js +++ b/dump-db/inc/data_key.js @@ -1,5 +1,5 @@ const crypto = require("crypto"); -const sql = require('./sql.js'); +const sql = require('./sql'); const decryptService = require('./decrypt.js'); function getDataKey(password) { diff --git a/dump-db/inc/dump.js b/dump-db/inc/dump.js index 96fa60f94..35191ded6 100644 --- a/dump-db/inc/dump.js +++ b/dump-db/inc/dump.js @@ -74,7 +74,7 @@ function dumpDocument(documentPath, targetPath, options) { return; } - let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]); + let { content } = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]); if (content !== null && noteRow.isProtected && dataKey) { content = decryptService.decrypt(dataKey, content); @@ -108,7 +108,7 @@ function dumpDocument(documentPath, targetPath, options) { } try { - fs.mkdirSync(childTargetPath, {recursive: true}); + fs.mkdirSync(childTargetPath, { recursive: true }); } catch (e) { console.error(`DUMPERROR: Creating directory ${childTargetPath} failed with error '${e.message}'`); @@ -157,7 +157,7 @@ function validatePaths(documentPath, targetPath) { } if (!fs.existsSync(targetPath)) { - const ret = fs.mkdirSync(targetPath, {recursive: true}); + const ret = fs.mkdirSync(targetPath, { recursive: true }); if (!ret) { console.error(`Target path '${targetPath}' could not be created. Run with --help to see usage.`); diff --git a/nodemon.json b/nodemon.json index df14c4a84..86e1c2ca4 100644 --- a/nodemon.json +++ b/nodemon.json @@ -2,12 +2,11 @@ "restartable": "rs", "ignore": [".git", "node_modules/**/node_modules", "src/public/"], "verbose": false, - "execMap": { - "js": "node --harmony" - }, + "exec": "ts-node", "watch": ["src/"], + "signal": "SIGTERM", "env": { "NODE_ENV": "development" }, - "ext": "js,json" + "ext": "ts,js,json" } diff --git a/package-lock.json b/package-lock.json index c8c24644e..283832065 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.63.3", + "version": "0.63.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.63.3", + "version": "0.63.5", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { @@ -88,6 +88,14 @@ "trilium": "src/www.js" }, "devDependencies": { + "@types/better-sqlite3": "^7.6.9", + "@types/cls-hooked": "^4.3.8", + "@types/escape-html": "^1.0.4", + "@types/express": "^4.17.21", + "@types/ini": "^4.1.0", + "@types/mime-types": "^2.1.4", + "@types/node": "^20.11.19", + "@types/ws": "^8.5.10", "cross-env": "7.0.3", "electron": "25.9.8", "electron-builder": "24.13.3", @@ -99,6 +107,9 @@ "lorem-ipsum": "2.0.8", "nodemon": "3.1.0", "rcedit": "4.0.1", + "ts-node": "^10.9.2", + "tslib": "^2.6.2", + "typescript": "^5.3.3", "webpack": "5.90.3", "webpack-cli": "5.1.4" }, @@ -139,6 +150,28 @@ "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -1105,11 +1138,54 @@ "node": ">= 10" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "node_modules/@tweenjs/tween.js": { "version": "21.0.0", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-21.0.0.tgz", "integrity": "sha512-qVfOiFh0U8ZSkLgA6tf7kj2MciqRbSCWaJZRwftVO7UbtVDNsZAXpWXqvCDtIefvjC83UJB+vHTDOGm5ibXjEA==" }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.9", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.9.tgz", + "integrity": "sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -1121,6 +1197,24 @@ "@types/responselike": "*" } }, + "node_modules/@types/cls-hooked": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@types/cls-hooked/-/cls-hooked-4.3.8.tgz", + "integrity": "sha512-tf/7H883gFA6MPlWI15EQtfNZ+oPL0gLKkOlx9UHFrun1fC/FkuyNBpTKq1B5E3T4fbvjId6WifHUdSGsMMuPg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3-scale": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", @@ -1147,6 +1241,12 @@ "@types/ms": "*" } }, + "node_modules/@types/escape-html": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", + "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -1173,6 +1273,30 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -1197,6 +1321,18 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/ini": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.0.tgz", + "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -1241,6 +1377,18 @@ "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true + }, "node_modules/@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1253,9 +1401,12 @@ "integrity": "sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==" }, "node_modules/@types/node": { - "version": "18.16.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz", - "integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==" + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/plist": { "version": "3.0.5", @@ -1268,6 +1419,18 @@ "xmlbuilder": ">=11.0.1" } }, + "node_modules/@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -1276,6 +1439,27 @@ "@types/node": "*" } }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", @@ -1288,6 +1472,15 @@ "dev": true, "optional": true }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -1568,6 +1761,15 @@ "acorn": "^8" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -2028,6 +2230,12 @@ "streamx": "^2.15.0" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3626,6 +3834,12 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -5449,6 +5663,14 @@ "node": ">=8.0.0" } }, + "node_modules/electron/node_modules/@types/node": { + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/elkjs": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.2.tgz", @@ -8140,6 +8362,12 @@ "node": ">= 6" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/make-fetch-happen": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.0.tgz", @@ -11925,10 +12153,62 @@ "node": ">=6.10" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -12041,6 +12321,11 @@ "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", "dev": true }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/unescape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unescape/-/unescape-1.0.1.tgz", @@ -12217,6 +12502,12 @@ "node": ">=8" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -12782,6 +13073,15 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/zip-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.0.tgz", @@ -12888,6 +13188,27 @@ "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -13608,11 +13929,54 @@ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "@tweenjs/tween.js": { "version": "21.0.0", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-21.0.0.tgz", "integrity": "sha512-qVfOiFh0U8ZSkLgA6tf7kj2MciqRbSCWaJZRwftVO7UbtVDNsZAXpWXqvCDtIefvjC83UJB+vHTDOGm5ibXjEA==" }, + "@types/better-sqlite3": { + "version": "7.6.9", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.9.tgz", + "integrity": "sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -13624,6 +13988,24 @@ "@types/responselike": "*" } }, + "@types/cls-hooked": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@types/cls-hooked/-/cls-hooked-4.3.8.tgz", + "integrity": "sha512-tf/7H883gFA6MPlWI15EQtfNZ+oPL0gLKkOlx9UHFrun1fC/FkuyNBpTKq1B5E3T4fbvjId6WifHUdSGsMMuPg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/d3-scale": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", @@ -13650,6 +14032,12 @@ "@types/ms": "*" } }, + "@types/escape-html": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", + "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", + "dev": true + }, "@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -13676,6 +14064,30 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -13700,6 +14112,18 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "@types/ini": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.0.tgz", + "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", + "dev": true + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -13744,6 +14168,18 @@ "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -13756,9 +14192,12 @@ "integrity": "sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==" }, "@types/node": { - "version": "18.16.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz", - "integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==" + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "requires": { + "undici-types": "~5.26.4" + } }, "@types/plist": { "version": "3.0.5", @@ -13771,6 +14210,18 @@ "xmlbuilder": ">=11.0.1" } }, + "@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -13779,6 +14230,27 @@ "@types/node": "*" } }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", @@ -13791,6 +14263,15 @@ "dev": true, "optional": true }, + "@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -14031,6 +14512,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -14381,6 +14868,12 @@ } } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -15586,6 +16079,12 @@ } } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -16410,6 +16909,16 @@ "@electron/get": "^2.0.0", "@types/node": "^18.11.18", "extract-zip": "^2.0.1" + }, + "dependencies": { + "@types/node": { + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", + "requires": { + "undici-types": "~5.26.4" + } + } } }, "electron-builder": { @@ -19033,6 +19542,12 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "make-fetch-happen": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.0.tgz", @@ -21840,10 +22355,39 @@ "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==" }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "tsscmp": { "version": "1.0.6", @@ -21931,6 +22475,11 @@ "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", "dev": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "unescape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unescape/-/unescape-1.0.1.tgz", @@ -22057,6 +22606,12 @@ "sade": "^1.7.3" } }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -22463,6 +23018,12 @@ "pend": "~1.2.0" } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "zip-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.0.tgz", diff --git a/package.json b/package.json index 648ea6974..5466a2e90 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,14 @@ "yauzl": "3.1.2" }, "devDependencies": { + "@types/better-sqlite3": "^7.6.9", + "@types/cls-hooked": "^4.3.8", + "@types/escape-html": "^1.0.4", + "@types/express": "^4.17.21", + "@types/ini": "^4.1.0", + "@types/mime-types": "^2.1.4", + "@types/node": "^20.11.19", + "@types/ws": "^8.5.10", "cross-env": "7.0.3", "electron": "25.9.8", "electron-builder": "24.13.3", @@ -120,10 +128,13 @@ "lorem-ipsum": "2.0.8", "nodemon": "3.1.0", "rcedit": "4.0.1", + "ts-node": "^10.9.2", + "tslib": "^2.6.2", + "typescript": "^5.3.3", "webpack": "5.90.3", "webpack-cli": "5.1.4" }, "optionalDependencies": { "electron-installer-debian": "3.2.0" } -} +} \ No newline at end of file diff --git a/spec/search/search.spec.js b/spec/search/search.spec.js index 97b607afd..14714cb6e 100644 --- a/spec/search/search.spec.js +++ b/spec/search/search.spec.js @@ -2,7 +2,7 @@ const searchService = require('../../src/services/search/services/search.js'); const BNote = require('../../src/becca/entities/bnote.js'); const BBranch = require('../../src/becca/entities/bbranch.js'); const SearchContext = require('../../src/services/search/search_context.js'); -const dateUtils = require('../../src/services/date_utils.js'); +const dateUtils = require('../../src/services/date_utils'); const becca = require('../../src/becca/becca.js'); const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking.js'); diff --git a/src/app.js b/src/app.js index 45451f0cc..a2e7b4f31 100644 --- a/src/app.js +++ b/src/app.js @@ -5,7 +5,7 @@ const cookieParser = require('cookie-parser'); const helmet = require('helmet'); const compression = require('compression'); const sessionParser = require('./routes/session_parser.js'); -const utils = require('./services/utils.js'); +const utils = require('./services/utils'); require('./services/handlers.js'); require('./becca/becca_loader.js'); diff --git a/src/becca/becca.js b/src/becca/becca-interface.ts similarity index 57% rename from src/becca/becca.js rename to src/becca/becca-interface.ts index d1e7c333e..10495fc7a 100644 --- a/src/becca/becca.js +++ b/src/becca/becca-interface.ts @@ -1,32 +1,52 @@ -"use strict"; +import sql = require('../services/sql'); +import NoteSet = require('../services/search/note_set'); +import NotFoundError = require('../errors/not_found_error'); +import BOption = require('./entities/boption'); +import BNote = require('./entities/bnote'); +import BEtapiToken = require('./entities/betapi_token'); +import BAttribute = require('./entities/battribute'); +import BBranch = require('./entities/bbranch'); +import BRevision = require('./entities/brevision'); +import BAttachment = require('./entities/battachment'); +import { AttachmentRow, RevisionRow } from './entities/rows'; +import BBlob = require('./entities/bblob'); +import BRecentNote = require('./entities/brecent_note'); +import AbstractBeccaEntity = require('./entities/abstract_becca_entity'); -const sql = require('../services/sql.js'); -const NoteSet = require('../services/search/note_set.js'); -const NotFoundError = require('../errors/not_found_error.js'); +interface AttachmentOpts { + includeContentLength?: boolean; +} /** * Becca is a backend cache of all notes, branches, and attributes. * There's a similar frontend cache Froca, and share cache Shaca. */ class Becca { + loaded!: boolean; + + notes!: Record; + branches!: Record; + childParentToBranch!: Record; + attributes!: Record; + /** Points from attribute type-name to list of attributes */ + attributeIndex!: Record; + options!: Record; + etapiTokens!: Record; + + allNoteSetCache: NoteSet | null; + constructor() { this.reset(); + this.allNoteSetCache = null; } reset() { - /** @type {Object.} */ this.notes = {}; - /** @type {Object.} */ this.branches = {}; - /** @type {Object.} */ this.childParentToBranch = {}; - /** @type {Object.} */ - this.attributes = {}; - /** @type {Object.} Points from attribute type-name to list of attributes */ + this.attributes = {}; this.attributeIndex = {}; - /** @type {Object.} */ this.options = {}; - /** @type {Object.} */ this.etapiTokens = {}; this.dirtyNoteSetCache(); @@ -38,8 +58,7 @@ class Becca { return this.getNote('root'); } - /** @returns {BAttribute[]} */ - findAttributes(type, name) { + findAttributes(type: string, name: string): BAttribute[] { name = name.trim().toLowerCase(); if (name.startsWith('#') || name.startsWith('~')) { @@ -49,9 +68,8 @@ class Becca { return this.attributeIndex[`${type}-${name}`] || []; } - /** @returns {BAttribute[]} */ - findAttributesWithPrefix(type, name) { - const resArr = []; + findAttributesWithPrefix(type: string, name: string): BAttribute[] { + const resArr: BAttribute[][] = []; const key = `${type}-${name}`; for (const idx in this.attributeIndex) { @@ -69,18 +87,16 @@ class Becca { } } - addNote(noteId, note) { + addNote(noteId: string, note: BNote) { this.notes[noteId] = note; this.dirtyNoteSetCache(); } - /** @returns {BNote|null} */ - getNote(noteId) { + getNote(noteId: string): BNote | null { return this.notes[noteId]; } - /** @returns {BNote|null} */ - getNoteOrThrow(noteId) { + getNoteOrThrow(noteId: string): BNote { const note = this.notes[noteId]; if (!note) { throw new NotFoundError(`Note '${noteId}' doesn't exist.`); @@ -89,9 +105,8 @@ class Becca { return note; } - /** @returns {BNote[]} */ - getNotes(noteIds, ignoreMissing = false) { - const filteredNotes = []; + getNotes(noteIds: string[], ignoreMissing: boolean = false): BNote[] { + const filteredNotes: BNote[] = []; for (const noteId of noteIds) { const note = this.notes[noteId]; @@ -110,13 +125,11 @@ class Becca { return filteredNotes; } - /** @returns {BBranch|null} */ - getBranch(branchId) { + getBranch(branchId: string): BBranch | null { return this.branches[branchId]; } - /** @returns {BBranch|null} */ - getBranchOrThrow(branchId) { + getBranchOrThrow(branchId: string): BBranch | null { const branch = this.getBranch(branchId); if (!branch) { throw new NotFoundError(`Branch '${branchId}' was not found in becca.`); @@ -124,13 +137,11 @@ class Becca { return branch; } - /** @returns {BAttribute|null} */ - getAttribute(attributeId) { + getAttribute(attributeId: string): BAttribute | null { return this.attributes[attributeId]; } - /** @returns {BAttribute} */ - getAttributeOrThrow(attributeId) { + getAttributeOrThrow(attributeId: string): BAttribute { const attribute = this.getAttribute(attributeId); if (!attribute) { throw new NotFoundError(`Attribute '${attributeId}' does not exist.`); @@ -139,21 +150,18 @@ class Becca { return attribute; } - /** @returns {BBranch|null} */ - getBranchFromChildAndParent(childNoteId, parentNoteId) { + getBranchFromChildAndParent(childNoteId: string, parentNoteId: string): BBranch | null { return this.childParentToBranch[`${childNoteId}-${parentNoteId}`]; } - /** @returns {BRevision|null} */ - getRevision(revisionId) { + getRevision(revisionId: string): BRevision | null { const row = sql.getRow("SELECT * FROM revisions WHERE revisionId = ?", [revisionId]); - const BRevision = require('./entities/brevision.js'); // avoiding circular dependency problems + const BRevision = require('./entities/brevision'); // avoiding circular dependency problems return row ? new BRevision(row) : null; } - /** @returns {BAttachment|null} */ - getAttachment(attachmentId, opts = {}) { + getAttachment(attachmentId: string, opts: AttachmentOpts = {}): BAttachment | null { opts.includeContentLength = !!opts.includeContentLength; const query = opts.includeContentLength @@ -163,14 +171,13 @@ class Becca { WHERE attachmentId = ? AND isDeleted = 0` : `SELECT * FROM attachments WHERE attachmentId = ? AND isDeleted = 0`; - const BAttachment = require('./entities/battachment.js'); // avoiding circular dependency problems + const BAttachment = require('./entities/battachment'); // avoiding circular dependency problems return sql.getRows(query, [attachmentId]) .map(row => new BAttachment(row))[0]; } - /** @returns {BAttachment} */ - getAttachmentOrThrow(attachmentId, opts = {}) { + getAttachmentOrThrow(attachmentId: string, opts: AttachmentOpts = {}): BAttachment { const attachment = this.getAttachment(attachmentId, opts); if (!attachment) { throw new NotFoundError(`Attachment '${attachmentId}' has not been found.`); @@ -178,38 +185,36 @@ class Becca { return attachment; } - /** @returns {BAttachment[]} */ - getAttachments(attachmentIds) { - const BAttachment = require('./entities/battachment.js'); // avoiding circular dependency problems - return sql.getManyRows("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds) + getAttachments(attachmentIds: string[]): BAttachment[] { + const BAttachment = require('./entities/battachment'); // avoiding circular dependency problems + return sql.getManyRows("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds) .map(row => new BAttachment(row)); } - /** @returns {BBlob|null} */ - getBlob(entity) { + getBlob(entity: { blobId?: string }): BBlob | null { + if (!entity.blobId) { + return null; + } + const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [entity.blobId]); - const BBlob = require('./entities/bblob.js'); // avoiding circular dependency problems + const BBlob = require('./entities/bblob'); // avoiding circular dependency problems return row ? new BBlob(row) : null; } - /** @returns {BOption|null} */ - getOption(name) { + getOption(name: string): BOption | null { return this.options[name]; } - /** @returns {BEtapiToken[]} */ - getEtapiTokens() { + getEtapiTokens(): BEtapiToken[] { return Object.values(this.etapiTokens); } - /** @returns {BEtapiToken|null} */ - getEtapiToken(etapiTokenId) { + getEtapiToken(etapiTokenId: string): BEtapiToken | null { return this.etapiTokens[etapiTokenId]; } - /** @returns {AbstractBeccaEntity|null} */ - getEntity(entityName, entityId) { + getEntity>(entityName: string, entityId: string): AbstractBeccaEntity | null { if (!entityName || !entityId) { return null; } @@ -231,22 +236,20 @@ class Becca { throw new Error(`Unknown entity name '${camelCaseEntityName}' (original argument '${entityName}')`); } - return this[camelCaseEntityName][entityId]; + return (this as any)[camelCaseEntityName][entityId]; } - /** @returns {BRecentNote[]} */ - getRecentNotesFromQuery(query, params = []) { + getRecentNotesFromQuery(query: string, params = []): BRecentNote[] { const rows = sql.getRows(query, params); - const BRecentNote = require('./entities/brecent_note.js'); // avoiding circular dependency problems + const BRecentNote = require('./entities/brecent_note'); // avoiding circular dependency problems return rows.map(row => new BRecentNote(row)); } - /** @returns {BRevision[]} */ - getRevisionsFromQuery(query, params = []) { - const rows = sql.getRows(query, params); + getRevisionsFromQuery(query: string, params = []): BRevision[] { + const rows = sql.getRows(query, params); - const BRevision = require('./entities/brevision.js'); // avoiding circular dependency problems + const BRevision = require('./entities/brevision'); // avoiding circular dependency problems return rows.map(row => new BRevision(row)); } @@ -260,8 +263,8 @@ class Becca { if (!this.allNoteSetCache) { const allNotes = []; - for (const noteId in becca.notes) { - const note = becca.notes[noteId]; + for (const noteId in this.notes) { + const note = this.notes[noteId]; // in the process of loading data sometimes we create "skeleton" note instances which are expected to be filled later // in case of inconsistent data this might not work and search will then crash on these @@ -277,6 +280,4 @@ class Becca { } } -const becca = new Becca(); - -module.exports = becca; +export = Becca; \ No newline at end of file diff --git a/src/becca/becca.ts b/src/becca/becca.ts new file mode 100644 index 000000000..8ea1a6575 --- /dev/null +++ b/src/becca/becca.ts @@ -0,0 +1,7 @@ +"use strict"; + +import Becca = require("./becca-interface"); + +const becca = new Becca(); + +export = becca; diff --git a/src/becca/becca_loader.js b/src/becca/becca_loader.js index 5d6ce6f06..a3004d9b7 100644 --- a/src/becca/becca_loader.js +++ b/src/becca/becca_loader.js @@ -1,17 +1,17 @@ "use strict"; -const sql = require('../services/sql.js'); -const eventService = require('../services/events.js'); -const becca = require('./becca.js'); -const sqlInit = require('../services/sql_init.js'); -const log = require('../services/log.js'); -const BNote = require('./entities/bnote.js'); -const BBranch = require('./entities/bbranch.js'); -const BAttribute = require('./entities/battribute.js'); -const BOption = require('./entities/boption.js'); -const BEtapiToken = require('./entities/betapi_token.js'); -const cls = require('../services/cls.js'); -const entityConstructor = require('../becca/entity_constructor.js'); +const sql = require('../services/sql'); +const eventService = require('../services/events'); +const becca = require('./becca'); +const sqlInit = require('../services/sql_init'); +const log = require('../services/log'); +const BNote = require('./entities/bnote'); +const BBranch = require('./entities/bbranch'); +const BAttribute = require('./entities/battribute'); +const BOption = require('./entities/boption'); +const BEtapiToken = require('./entities/betapi_token'); +const cls = require('../services/cls'); +const entityConstructor = require('../becca/entity_constructor'); const beccaLoaded = new Promise((res, rej) => { sqlInit.dbReady.then(() => { @@ -71,10 +71,10 @@ function load() { function reload(reason) { load(); - require('../services/ws.js').reloadFrontend(reason || "becca reloaded"); + require('../services/ws').reloadFrontend(reason || "becca reloaded"); } -eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => { +eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({ entityName, entityRow }) => { if (!becca.loaded) { return; } @@ -97,7 +97,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity postProcessEntityUpdate(entityName, entityRow); }); -eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, entity}) => { +eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({ entityName, entity }) => { if (!becca.loaded) { return; } @@ -124,7 +124,7 @@ function postProcessEntityUpdate(entityName, entityRow) { } } -eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => { +eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({ entityName, entityId }) => { if (!becca.loaded) { return; } diff --git a/src/becca/becca_service.js b/src/becca/becca_service.js index 0ac5c2591..15a1c07cc 100644 --- a/src/becca/becca_service.js +++ b/src/becca/becca_service.js @@ -1,8 +1,8 @@ "use strict"; -const becca = require('./becca.js'); -const cls = require('../services/cls.js'); -const log = require('../services/log.js'); +const becca = require('./becca'); +const cls = require('../services/cls'); +const log = require('../services/log'); function isNotePathArchived(notePath) { const noteId = notePath[notePath.length - 1]; diff --git a/src/becca/entities/abstract_becca_entity.js b/src/becca/entities/abstract_becca_entity.ts similarity index 56% rename from src/becca/entities/abstract_becca_entity.js rename to src/becca/entities/abstract_becca_entity.ts index 3b2e6331b..1c775b51e 100644 --- a/src/becca/entities/abstract_becca_entity.js +++ b/src/becca/entities/abstract_becca_entity.ts @@ -1,66 +1,86 @@ "use strict"; -const utils = require('../../services/utils.js'); -const sql = require('../../services/sql.js'); -const entityChangesService = require('../../services/entity_changes.js'); -const eventService = require('../../services/events.js'); -const dateUtils = require('../../services/date_utils.js'); -const cls = require('../../services/cls.js'); -const log = require('../../services/log.js'); -const protectedSessionService = require('../../services/protected_session.js'); -const blobService = require('../../services/blob.js'); +import utils = require('../../services/utils'); +import sql = require('../../services/sql'); +import entityChangesService = require('../../services/entity_changes'); +import eventService = require('../../services/events'); +import dateUtils = require('../../services/date_utils'); +import cls = require('../../services/cls'); +import log = require('../../services/log'); +import protectedSessionService = require('../../services/protected_session'); +import blobService = require('../../services/blob'); +import Becca = require('../becca-interface'); -let becca = null; +let becca: Becca | null = null; + +interface ContentOpts { + forceSave?: boolean; + forceFrontendReload?: boolean; +} + +/** + * This interface contains the data that is shared across all the objects of a given derived class of {@link AbstractBeccaEntity}. + * For example, all BAttributes will share their content, but all BBranches will have another set of this data. + */ +interface ConstructorData> { + primaryKeyName: string; + entityName: string; + hashedProperties: (keyof T)[]; +} /** * Base class for all backend entities. + * + * @type T the same entity type needed for self-reference in {@link ConstructorData}. */ -class AbstractBeccaEntity { - /** @protected */ - beforeSaving() { - if (!this[this.constructor.primaryKeyName]) { - this[this.constructor.primaryKeyName] = utils.newEntityId(); +abstract class AbstractBeccaEntity> { + + protected utcDateCreated?: string; + protected utcDateModified?: string; + protected dateCreated?: string; + protected dateModified?: string; + + isProtected?: boolean; + isSynced?: boolean; + blobId?: string; + + protected beforeSaving() { + const constructorData = (this.constructor as unknown as ConstructorData); + if (!(this as any)[constructorData.primaryKeyName]) { + (this as any)[constructorData.primaryKeyName] = utils.newEntityId(); } } - /** @protected */ getUtcDateChanged() { return this.utcDateModified || this.utcDateCreated; } - /** - * @protected - * @returns {Becca} - */ - get becca() { + protected get becca(): Becca { if (!becca) { - becca = require('../becca.js'); + becca = require('../becca'); } - return becca; + return becca as Becca; } - /** @protected */ - putEntityChange(isDeleted) { + protected putEntityChange(isDeleted: boolean) { + const constructorData = (this.constructor as unknown as ConstructorData); entityChangesService.putEntityChange({ - entityName: this.constructor.entityName, - entityId: this[this.constructor.primaryKeyName], + entityName: constructorData.entityName, + entityId: (this as any)[constructorData.primaryKeyName], hash: this.generateHash(isDeleted), isErased: false, utcDateChanged: this.getUtcDateChanged(), - isSynced: this.constructor.entityName !== 'options' || !!this.isSynced + isSynced: constructorData.entityName !== 'options' || !!this.isSynced }); } - /** - * @protected - * @returns {string} - */ - generateHash(isDeleted) { + generateHash(isDeleted?: boolean): string { + const constructorData = (this.constructor as unknown as ConstructorData); let contentToHash = ""; - for (const propertyName of this.constructor.hashedProperties) { - contentToHash += `|${this[propertyName]}`; + for (const propertyName of constructorData.hashedProperties) { + contentToHash += `|${(this as any)[propertyName]}`; } if (isDeleted) { @@ -70,31 +90,33 @@ class AbstractBeccaEntity { return utils.hash(contentToHash).substr(0, 10); } - /** @protected */ - getPojoToSave() { + protected getPojoToSave() { return this.getPojo(); } - /** - * @protected - * @abstract - */ - getPojo() { - throw new Error(`Unimplemented getPojo() for entity '${this.constructor.name}'`) + hasStringContent(): boolean { + // TODO: Not sure why some entities don't implement it. + return true; + } + + abstract getPojo(): {}; + + get isDeleted(): boolean { + // TODO: Not sure why some entities don't implement it. + return false; } /** * Saves entity - executes SQL, but doesn't commit the transaction on its own - * - * @returns {this} */ - save(opts = {}) { - const entityName = this.constructor.entityName; - const primaryKeyName = this.constructor.primaryKeyName; + save(): this { + const constructorData = (this.constructor as unknown as ConstructorData); + const entityName = constructorData.entityName; + const primaryKeyName = constructorData.primaryKeyName; - const isNewEntity = !this[primaryKeyName]; + const isNewEntity = !(this as any)[primaryKeyName]; - this.beforeSaving(opts); + this.beforeSaving(); const pojo = this.getPojoToSave(); @@ -124,14 +146,14 @@ class AbstractBeccaEntity { return this; } - /** @protected */ - _setContent(content, opts = {}) { + protected _setContent(content: string | Buffer, opts: ContentOpts = {}) { // client code asks to save entity even if blobId didn't change (something else was changed) opts.forceSave = !!opts.forceSave; opts.forceFrontendReload = !!opts.forceFrontendReload; if (content === null || content === undefined) { - throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`); + const constructorData = (this.constructor as unknown as ConstructorData); + throw new Error(`Cannot set null content to ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}'`); } if (this.hasStringContent()) { @@ -140,32 +162,36 @@ class AbstractBeccaEntity { content = Buffer.isBuffer(content) ? content : Buffer.from(content); } - const unencryptedContentForHashCalculation = this.#getUnencryptedContentForHashCalculation(content); + const unencryptedContentForHashCalculation = this.getUnencryptedContentForHashCalculation(content); if (this.isProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { - content = protectedSessionService.encrypt(content); + const encryptedContent = protectedSessionService.encrypt(content); + if (!encryptedContent) { + throw new Error(`Unable to encrypt the content of the entity.`); + } + content = encryptedContent; } else { throw new Error(`Cannot update content of blob since protected session is not available.`); } } sql.transactional(() => { - const newBlobId = this.#saveBlob(content, unencryptedContentForHashCalculation, opts); + const newBlobId = this.saveBlob(content, unencryptedContentForHashCalculation, opts); const oldBlobId = this.blobId; if (newBlobId !== oldBlobId || opts.forceSave) { this.blobId = newBlobId; this.save(); - if (newBlobId !== oldBlobId) { - this.#deleteBlobIfNotUsed(oldBlobId); + if (oldBlobId && newBlobId !== oldBlobId) { + this.deleteBlobIfNotUsed(oldBlobId); } } }); } - #deleteBlobIfNotUsed(oldBlobId) { + private deleteBlobIfNotUsed(oldBlobId: string) { if (sql.getValue("SELECT 1 FROM notes WHERE blobId = ? LIMIT 1", [oldBlobId])) { return; } @@ -184,7 +210,7 @@ class AbstractBeccaEntity { sql.execute("DELETE FROM entity_changes WHERE entityName = 'blobs' AND entityId = ?", [oldBlobId]); } - #getUnencryptedContentForHashCalculation(unencryptedContent) { + private getUnencryptedContentForHashCalculation(unencryptedContent: Buffer | string) { if (this.isProtected) { // a "random" prefix makes sure that the calculated hash/blobId is different for a decrypted/encrypted content const encryptedPrefixSuffix = "t$[nvQg7q)&_ENCRYPTED_?M:Bf&j3jr_"; @@ -196,7 +222,7 @@ class AbstractBeccaEntity { } } - #saveBlob(content, unencryptedContentForHashCalculation, opts = {}) { + private saveBlob(content: string | Buffer, unencryptedContentForHashCalculation: string | Buffer, opts: ContentOpts = {}) { /* * We're using the unencrypted blob for the hash calculation, because otherwise the random IV would * cause every content blob to be unique which would balloon the database size (esp. with revisioning). @@ -243,41 +269,37 @@ class AbstractBeccaEntity { return newBlobId; } - /** - * @protected - * @returns {string|Buffer} - */ - _getContent() { - const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); + protected _getContent(): string | Buffer { + const row = sql.getRow<{ content: string | Buffer }>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); if (!row) { - throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`); + const constructorData = (this.constructor as unknown as ConstructorData); + throw new Error(`Cannot find content for ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}', blobId '${this.blobId}'`); } - return blobService.processContent(row.content, this.isProtected, this.hasStringContent()); + return blobService.processContent(row.content, this.isProtected || false, this.hasStringContent()); } /** * Mark the entity as (soft) deleted. It will be completely erased later. * * This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead. - * - * @param [deleteId=null] */ - markAsDeleted(deleteId = null) { - const entityId = this[this.constructor.primaryKeyName]; - const entityName = this.constructor.entityName; + markAsDeleted(deleteId: string | null = null) { + const constructorData = (this.constructor as unknown as ConstructorData); + const entityId = (this as any)[constructorData.primaryKeyName]; + const entityName = constructorData.entityName; this.utcDateModified = dateUtils.utcNowDateTime(); sql.execute(`UPDATE ${entityName} SET isDeleted = 1, deleteId = ?, utcDateModified = ? - WHERE ${this.constructor.primaryKeyName} = ?`, + WHERE ${constructorData.primaryKeyName} = ?`, [deleteId, this.utcDateModified, entityId]); if (this.dateModified) { this.dateModified = dateUtils.localNowDateTime(); - sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${this.constructor.primaryKeyName} = ?`, + sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${constructorData.primaryKeyName} = ?`, [this.dateModified, entityId]); } @@ -289,13 +311,14 @@ class AbstractBeccaEntity { } markAsDeletedSimple() { - const entityId = this[this.constructor.primaryKeyName]; - const entityName = this.constructor.entityName; + const constructorData = (this.constructor as unknown as ConstructorData); + const entityId = (this as any)[constructorData.primaryKeyName]; + const entityName = constructorData.entityName; this.utcDateModified = dateUtils.utcNowDateTime(); sql.execute(`UPDATE ${entityName} SET isDeleted = 1, utcDateModified = ? - WHERE ${this.constructor.primaryKeyName} = ?`, + WHERE ${constructorData.primaryKeyName} = ?`, [this.utcDateModified, entityId]); log.info(`Marking ${entityName} ${entityId} as deleted`); @@ -306,4 +329,4 @@ class AbstractBeccaEntity { } } -module.exports = AbstractBeccaEntity; +export = AbstractBeccaEntity; diff --git a/src/becca/entities/battachment.js b/src/becca/entities/battachment.ts similarity index 71% rename from src/becca/entities/battachment.js rename to src/becca/entities/battachment.ts index faeb131e7..206c03286 100644 --- a/src/becca/entities/battachment.js +++ b/src/becca/entities/battachment.ts @@ -1,29 +1,57 @@ "use strict"; -const utils = require('../../services/utils.js'); -const dateUtils = require('../../services/date_utils.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); -const sql = require('../../services/sql.js'); -const protectedSessionService = require('../../services/protected_session.js'); -const log = require('../../services/log.js'); +import utils = require('../../services/utils'); +import dateUtils = require('../../services/date_utils'); +import AbstractBeccaEntity = require('./abstract_becca_entity'); +import sql = require('../../services/sql'); +import protectedSessionService = require('../../services/protected_session'); +import log = require('../../services/log'); +import { AttachmentRow } from './rows'; +import BNote = require('./bnote'); +import BBranch = require('./bbranch'); const attachmentRoleToNoteTypeMapping = { 'image': 'image', 'file': 'file' }; +interface ContentOpts { + // TODO: Found in bnote.ts, to check if it's actually used and not a typo. + forceSave?: boolean; + + /** will also save this BAttachment entity */ + forceFullSave?: boolean; + /** override frontend heuristics on when to reload, instruct to reload */ + forceFrontendReload?: boolean; +} + /** * Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for * larger amounts of data and generally not accessible to the user. - * - * @extends AbstractBeccaEntity */ -class BAttachment extends AbstractBeccaEntity { +class BAttachment extends AbstractBeccaEntity { static get entityName() { return "attachments"; } static get primaryKeyName() { return "attachmentId"; } static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; } - constructor(row) { + noteId?: number; + attachmentId?: string; + /** either noteId or revisionId to which this attachment belongs */ + ownerId: string; + role: string; + mime: string; + title: string; + type?: keyof typeof attachmentRoleToNoteTypeMapping; + position?: number; + blobId?: string; + isProtected?: boolean; + dateModified?: string; + utcDateScheduledForErasureSince?: string; + /** optionally added to the entity */ + contentLength?: number; + isDecrypted?: boolean; + + constructor(row: AttachmentRow) { super(); if (!row.ownerId?.trim()) { @@ -36,43 +64,23 @@ class BAttachment extends AbstractBeccaEntity { throw new Error("'title' must be given to initialize a Attachment entity"); } - /** @type {string} */ this.attachmentId = row.attachmentId; - /** - * either noteId or revisionId to which this attachment belongs - * @type {string} - */ this.ownerId = row.ownerId; - /** @type {string} */ this.role = row.role; - /** @type {string} */ this.mime = row.mime; - /** @type {string} */ this.title = row.title; - /** @type {int} */ this.position = row.position; - /** @type {string} */ this.blobId = row.blobId; - /** @type {boolean} */ this.isProtected = !!row.isProtected; - /** @type {string} */ this.dateModified = row.dateModified; - /** @type {string} */ this.utcDateModified = row.utcDateModified; - /** @type {string} */ this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince; - - /** - * optionally added to the entity - * @type {int} - */ this.contentLength = row.contentLength; this.decrypt(); } - /** @returns {BAttachment} */ - copy() { + copy(): BAttachment { return new BAttachment({ ownerId: this.ownerId, role: this.role, @@ -83,14 +91,13 @@ class BAttachment extends AbstractBeccaEntity { }); } - /** @returns {BNote} */ - getNote() { + getNote(): BNote { return this.becca.notes[this.ownerId]; } - /** @returns {boolean} true if the note has string content (not binary) */ - hasStringContent() { - return utils.isStringNote(this.type, this.mime); + /** @returns true if the note has string content (not binary) */ + hasStringContent(): boolean { + return this.type !== undefined && utils.isStringNote(this.type, this.mime); } isContentAvailable() { @@ -111,33 +118,26 @@ class BAttachment extends AbstractBeccaEntity { if (!this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { try { - this.title = protectedSessionService.decryptString(this.title); + this.title = protectedSessionService.decryptString(this.title) || ""; this.isDecrypted = true; } - catch (e) { + catch (e: any) { log.error(`Could not decrypt attachment ${this.attachmentId}: ${e.message} ${e.stack}`); } } } - /** @returns {string|Buffer} */ - getContent() { + getContent(): string | Buffer { return this._getContent(); } - /** - * @param content - * @param {object} [opts] - * @param {object} [opts.forceSave=false] - will also save this BAttachment entity - * @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload - */ - setContent(content, opts) { + setContent(content: string | Buffer, opts: ContentOpts) { this._setContent(content, opts); } - /** @returns {{note: BNote, branch: BBranch}} */ - convertToNote() { - if (this.type === 'search') { + convertToNote(): { note: BNote, branch: BBranch } { + // TODO: can this ever be "search"? + if (this.type as string === 'search') { throw new Error(`Note of type search cannot have child notes`); } @@ -154,12 +154,12 @@ class BAttachment extends AbstractBeccaEntity { throw new Error(`Cannot convert protected attachment outside of protected session`); } - const noteService = require('../../services/notes.js'); + const noteService = require('../../services/notes'); const { note, branch } = noteService.createNewNote({ parentNoteId: this.ownerId, title: this.title, - type: attachmentRoleToNoteTypeMapping[this.role], + type: (attachmentRoleToNoteTypeMapping as any)[this.role], mime: this.mime, content: this.getContent(), isProtected: this.isProtected @@ -196,9 +196,9 @@ class BAttachment extends AbstractBeccaEntity { super.beforeSaving(); if (this.position === undefined || this.position === null) { - this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0) - FROM attachments - WHERE ownerId = ?`, [this.noteId]); + this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0) + FROM attachments + WHERE ownerId = ?`, [this.noteId]); } this.dateModified = dateUtils.localNowDateTime(); @@ -211,7 +211,7 @@ class BAttachment extends AbstractBeccaEntity { ownerId: this.ownerId, role: this.role, mime: this.mime, - title: this.title, + title: this.title || undefined, position: this.position, blobId: this.blobId, isProtected: !!this.isProtected, @@ -229,7 +229,7 @@ class BAttachment extends AbstractBeccaEntity { if (pojo.isProtected) { if (this.isDecrypted) { - pojo.title = protectedSessionService.encrypt(pojo.title); + pojo.title = protectedSessionService.encrypt(pojo.title || "") || undefined; } else { // updating protected note outside of protected session means we will keep original ciphertexts @@ -241,4 +241,4 @@ class BAttachment extends AbstractBeccaEntity { } } -module.exports = BAttachment; +export = BAttachment; diff --git a/src/becca/entities/battribute.js b/src/becca/entities/battribute.ts similarity index 84% rename from src/becca/entities/battribute.js rename to src/becca/entities/battribute.ts index 55e7db66d..c3d0115fc 100644 --- a/src/becca/entities/battribute.js +++ b/src/becca/entities/battribute.ts @@ -1,30 +1,34 @@ "use strict"; -const BNote = require('./bnote.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); -const sql = require('../../services/sql.js'); -const dateUtils = require('../../services/date_utils.js'); -const promotedAttributeDefinitionParser = require('../../services/promoted_attribute_definition_parser.js'); -const {sanitizeAttributeName} = require('../../services/sanitize_attribute_name.js'); +import BNote = require('./bnote'); +import AbstractBeccaEntity = require('./abstract_becca_entity'); +import dateUtils = require('../../services/date_utils'); +import promotedAttributeDefinitionParser = require('../../services/promoted_attribute_definition_parser'); +import sanitizeAttributeName = require('../../services/sanitize_attribute_name'); +import { AttributeRow, AttributeType } from './rows'; - -/** - * There are currently only two types of attributes, labels or relations. - * @typedef {"label" | "relation"} AttributeType - */ +interface SavingOpts { + skipValidation?: boolean; +} /** * Attribute is an abstract concept which has two real uses - label (key - value pair) * and relation (representing named relationship between source and target note) - * - * @extends AbstractBeccaEntity */ -class BAttribute extends AbstractBeccaEntity { +class BAttribute extends AbstractBeccaEntity { static get entityName() { return "attributes"; } static get primaryKeyName() { return "attributeId"; } static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable"]; } - constructor(row) { + attributeId!: string; + noteId!: string; + type!: AttributeType; + name!: string; + position!: number; + value!: string; + isInheritable!: boolean; + + constructor(row: AttributeRow) { super(); if (!row) { @@ -35,7 +39,7 @@ class BAttribute extends AbstractBeccaEntity { this.init(); } - updateFromRow(row) { + updateFromRow(row: AttributeRow) { this.update([ row.attributeId, row.noteId, @@ -48,22 +52,14 @@ class BAttribute extends AbstractBeccaEntity { ]); } - update([attributeId, noteId, type, name, value, isInheritable, position, utcDateModified]) { - /** @type {string} */ + update([attributeId, noteId, type, name, value, isInheritable, position, utcDateModified]: any[]) { this.attributeId = attributeId; - /** @type {string} */ this.noteId = noteId; - /** @type {AttributeType} */ this.type = type; - /** @type {string} */ this.name = name; - /** @type {int} */ this.position = position; - /** @type {string} */ this.value = value || ""; - /** @type {boolean} */ this.isInheritable = !!isInheritable; - /** @type {string} */ this.utcDateModified = utcDateModified; return this; @@ -182,12 +178,12 @@ class BAttribute extends AbstractBeccaEntity { return !(this.attributeId in this.becca.attributes); } - beforeSaving(opts = {}) { + beforeSaving(opts: SavingOpts = {}) { if (!opts.skipValidation) { this.validate(); } - this.name = sanitizeAttributeName(this.name); + this.name = sanitizeAttributeName.sanitizeAttributeName(this.name); if (!this.value) { // null value isn't allowed @@ -226,7 +222,7 @@ class BAttribute extends AbstractBeccaEntity { }; } - createClone(type, name, value, isInheritable) { + createClone(type: AttributeType, name: string, value: string, isInheritable: boolean) { return new BAttribute({ noteId: this.noteId, type: type, @@ -239,4 +235,4 @@ class BAttribute extends AbstractBeccaEntity { } } -module.exports = BAttribute; +export = BAttribute; diff --git a/src/becca/entities/bblob.js b/src/becca/entities/bblob.ts similarity index 66% rename from src/becca/entities/bblob.js rename to src/becca/entities/bblob.ts index def5888ee..149b9070a 100644 --- a/src/becca/entities/bblob.js +++ b/src/becca/entities/bblob.ts @@ -1,25 +1,29 @@ +import { BlobRow } from "./rows"; + +// TODO: Why this does not extend the abstract becca? class BBlob { static get entityName() { return "blobs"; } static get primaryKeyName() { return "blobId"; } static get hashedProperties() { return ["blobId", "content"]; } - constructor(row) { - /** @type {string} */ + blobId: string; + content: string | Buffer; + contentLength: number; + dateModified: string; + utcDateModified: string; + + constructor(row: BlobRow) { this.blobId = row.blobId; - /** @type {string|Buffer} */ this.content = row.content; - /** @type {int} */ this.contentLength = row.contentLength; - /** @type {string} */ this.dateModified = row.dateModified; - /** @type {string} */ this.utcDateModified = row.utcDateModified; } getPojo() { return { blobId: this.blobId, - content: this.content, + content: this.content || null, contentLength: this.contentLength, dateModified: this.dateModified, utcDateModified: this.utcDateModified @@ -27,4 +31,4 @@ class BBlob { } } -module.exports = BBlob; +export = BBlob; diff --git a/src/becca/entities/bbranch.js b/src/becca/entities/bbranch.ts similarity index 75% rename from src/becca/entities/bbranch.js rename to src/becca/entities/bbranch.ts index 94cc1b802..bf64d2ac6 100644 --- a/src/becca/entities/bbranch.js +++ b/src/becca/entities/bbranch.ts @@ -1,12 +1,13 @@ "use strict"; -const BNote = require('./bnote.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); -const dateUtils = require('../../services/date_utils.js'); -const utils = require('../../services/utils.js'); -const TaskContext = require('../../services/task_context.js'); -const cls = require('../../services/cls.js'); -const log = require('../../services/log.js'); +import BNote = require('./bnote'); +import AbstractBeccaEntity = require('./abstract_becca_entity'); +import dateUtils = require('../../services/date_utils'); +import utils = require('../../services/utils'); +import TaskContext = require('../../services/task_context'); +import cls = require('../../services/cls'); +import log = require('../../services/log'); +import { BranchRow } from './rows'; /** * Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple @@ -14,16 +15,22 @@ const log = require('../../services/log.js'); * * Note that you should not rely on the branch's identity, since it can change easily with a note's move. * Always check noteId instead. - * - * @extends AbstractBeccaEntity */ -class BBranch extends AbstractBeccaEntity { +class BBranch extends AbstractBeccaEntity { static get entityName() { return "branches"; } static get primaryKeyName() { return "branchId"; } // notePosition is not part of hash because it would produce a lot of updates in case of reordering static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "prefix"]; } - constructor(row) { + branchId?: string; + noteId!: string; + parentNoteId!: string; + prefix!: string | null; + notePosition!: number; + isExpanded!: boolean; + utcDateModified?: string; + + constructor(row: BranchRow) { super(); if (!row) { @@ -34,7 +41,7 @@ class BBranch extends AbstractBeccaEntity { this.init(); } - updateFromRow(row) { + updateFromRow(row: BranchRow) { this.update([ row.branchId, row.noteId, @@ -46,20 +53,13 @@ class BBranch extends AbstractBeccaEntity { ]); } - update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]) { - /** @type {string} */ + update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]: any) { this.branchId = branchId; - /** @type {string} */ this.noteId = noteId; - /** @type {string} */ this.parentNoteId = parentNoteId; - /** @type {string|null} */ this.prefix = prefix; - /** @type {int} */ this.notePosition = notePosition; - /** @type {boolean} */ this.isExpanded = !!isExpanded; - /** @type {string} */ this.utcDateModified = utcDateModified; return this; @@ -83,18 +83,18 @@ class BBranch extends AbstractBeccaEntity { } const parentNote = this.parentNote; - - if (!childNote.parents.includes(parentNote)) { - childNote.parents.push(parentNote); - } - - if (!parentNote.children.includes(childNote)) { - parentNote.children.push(childNote); + if (parentNote) { + if (!childNote.parents.includes(parentNote)) { + childNote.parents.push(parentNote); + } + + if (!parentNote.children.includes(childNote)) { + parentNote.children.push(childNote); + } } } - /** @returns {BNote} */ - get childNote() { + get childNote(): BNote { if (!(this.noteId in this.becca.notes)) { // entities can come out of order in sync/import, create skeleton which will be filled later this.becca.addNote(this.noteId, new BNote({noteId: this.noteId})); @@ -103,13 +103,12 @@ class BBranch extends AbstractBeccaEntity { return this.becca.notes[this.noteId]; } - /** @returns {BNote} */ - getNote() { + getNote(): BNote { return this.childNote; } - /** @returns {BNote|undefined} - root branch will have undefined parent, all other branches have to have a parent note */ - get parentNote() { + /** @returns root branch will have undefined parent, all other branches have to have a parent note */ + get parentNote(): BNote | undefined { if (!(this.parentNoteId in this.becca.notes) && this.parentNoteId !== 'none') { // entities can come out of order in sync/import, create skeleton which will be filled later this.becca.addNote(this.parentNoteId, new BNote({noteId: this.parentNoteId})); @@ -119,7 +118,7 @@ class BBranch extends AbstractBeccaEntity { } get isDeleted() { - return !(this.branchId in this.becca.branches); + return (this.branchId == undefined || !(this.branchId in this.becca.branches)); } /** @@ -138,12 +137,11 @@ class BBranch extends AbstractBeccaEntity { /** * Delete a branch. If this is a last note's branch, delete the note as well. * - * @param {string} [deleteId] - optional delete identified - * @param {TaskContext} [taskContext] + * @param deleteId - optional delete identified * - * @returns {boolean} - true if note has been deleted, false otherwise + * @returns true if note has been deleted, false otherwise */ - deleteBranch(deleteId, taskContext) { + deleteBranch(deleteId: string, taskContext: TaskContext): boolean { if (!deleteId) { deleteId = utils.randomString(10); } @@ -161,7 +159,7 @@ class BBranch extends AbstractBeccaEntity { if (parentBranches.length === 1 && parentBranches[0] === this) { // needs to be run before branches and attributes are deleted and thus attached relations disappear - const handlers = require('../../services/handlers.js'); + const handlers = require('../../services/handlers'); handlers.runAttachedRelations(note, 'runOnNoteDeletion', note); } } @@ -182,7 +180,9 @@ class BBranch extends AbstractBeccaEntity { } for (const childBranch of note.getChildBranches()) { - childBranch.deleteBranch(deleteId, taskContext); + if (childBranch) { + childBranch.deleteBranch(deleteId, taskContext); + } } // first delete children and then parent - this will show up better in recent changes @@ -222,11 +222,17 @@ class BBranch extends AbstractBeccaEntity { if (this.notePosition === undefined || this.notePosition === null) { let maxNotePos = 0; - for (const childBranch of this.parentNote.getChildBranches()) { - if (maxNotePos < childBranch.notePosition - && childBranch.noteId !== '_hidden' // hidden has a very large notePosition to always stay last - ) { - maxNotePos = childBranch.notePosition; + if (this.parentNote) { + for (const childBranch of this.parentNote.getChildBranches()) { + if (!childBranch) { + continue; + } + + if (maxNotePos < childBranch.notePosition + && childBranch.noteId !== '_hidden' // hidden has a very large notePosition to always stay last + ) { + maxNotePos = childBranch.notePosition; + } } } @@ -261,7 +267,7 @@ class BBranch extends AbstractBeccaEntity { }; } - createClone(parentNoteId, notePosition) { + createClone(parentNoteId: string, notePosition: number) { const existingBranch = this.becca.getBranchFromChildAndParent(this.noteId, parentNoteId); if (existingBranch) { @@ -279,4 +285,4 @@ class BBranch extends AbstractBeccaEntity { } } -module.exports = BBranch; +export = BBranch; diff --git a/src/becca/entities/betapi_token.js b/src/becca/entities/betapi_token.ts similarity index 76% rename from src/becca/entities/betapi_token.js rename to src/becca/entities/betapi_token.ts index 0fded9e63..d128c1beb 100644 --- a/src/becca/entities/betapi_token.js +++ b/src/becca/entities/betapi_token.ts @@ -1,7 +1,9 @@ "use strict"; -const dateUtils = require('../../services/date_utils.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); +import { EtapiTokenRow } from "./rows"; + +import dateUtils = require('../../services/date_utils'); +import AbstractBeccaEntity = require('./abstract_becca_entity'); /** * EtapiToken is an entity representing token used to authenticate against Trilium REST API from client applications. @@ -11,15 +13,18 @@ const AbstractBeccaEntity = require('./abstract_becca_entity.js'); * * The format user is presented with is "_". This is also called "authToken" to distinguish it * from tokenHash and token. - * - * @extends AbstractBeccaEntity */ -class BEtapiToken extends AbstractBeccaEntity { +class BEtapiToken extends AbstractBeccaEntity { static get entityName() { return "etapi_tokens"; } static get primaryKeyName() { return "etapiTokenId"; } static get hashedProperties() { return ["etapiTokenId", "name", "tokenHash", "utcDateCreated", "utcDateModified", "isDeleted"]; } - constructor(row) { + etapiTokenId!: string; + name!: string; + tokenHash!: string; + private _isDeleted!: boolean; + + constructor(row: EtapiTokenRow) { super(); if (!row) { @@ -30,19 +35,17 @@ class BEtapiToken extends AbstractBeccaEntity { this.init(); } - updateFromRow(row) { - /** @type {string} */ + get isDeleted() { + return this._isDeleted; + } + + updateFromRow(row: EtapiTokenRow) { this.etapiTokenId = row.etapiTokenId; - /** @type {string} */ this.name = row.name; - /** @type {string} */ this.tokenHash = row.tokenHash; - /** @type {string} */ this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime(); - /** @type {string} */ this.utcDateModified = row.utcDateModified || this.utcDateCreated; - /** @type {boolean} */ - this.isDeleted = !!row.isDeleted; + this._isDeleted = !!row.isDeleted; if (this.etapiTokenId) { this.becca.etapiTokens[this.etapiTokenId] = this; @@ -75,4 +78,4 @@ class BEtapiToken extends AbstractBeccaEntity { } } -module.exports = BEtapiToken; +export = BEtapiToken; diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.ts similarity index 70% rename from src/becca/entities/bnote.js rename to src/becca/entities/bnote.ts index 36f8ed3d4..821e10443 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.ts @@ -1,48 +1,92 @@ "use strict"; -const protectedSessionService = require('../../services/protected_session.js'); -const log = require('../../services/log.js'); -const sql = require('../../services/sql.js'); -const utils = require('../../services/utils.js'); -const dateUtils = require('../../services/date_utils.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); -const BRevision = require('./brevision.js'); -const BAttachment = require('./battachment.js'); -const TaskContext = require('../../services/task_context.js'); -const dayjs = require("dayjs"); -const utc = require('dayjs/plugin/utc'); -const eventService = require('../../services/events.js'); +import protectedSessionService = require('../../services/protected_session'); +import log = require('../../services/log'); +import sql = require('../../services/sql'); +import utils = require('../../services/utils'); +import dateUtils = require('../../services/date_utils'); +import AbstractBeccaEntity = require('./abstract_becca_entity'); +import BRevision = require('./brevision'); +import BAttachment = require('./battachment'); +import TaskContext = require('../../services/task_context'); +import dayjs = require("dayjs"); +import utc = require('dayjs/plugin/utc'); +import eventService = require('../../services/events'); +import { AttachmentRow, NoteRow, NoteType, RevisionRow } from './rows'; +import BBranch = require('./bbranch'); +import BAttribute = require('./battribute'); dayjs.extend(utc); const LABEL = 'label'; const RELATION = 'relation'; -/** - * There are many different Note types, some of which are entirely opaque to the - * end user. Those types should be used only for checking against, they are - * not for direct use. - * @typedef {"file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code"} NoteType - */ +interface NotePathRecord { + isArchived: boolean; + isInHoistedSubTree: boolean; + notePath: string[]; + isHidden: boolean; +} -/** - * @typedef {Object} NotePathRecord - * @property {boolean} isArchived - * @property {boolean} isInHoistedSubTree - * @property {Array} notePath - * @property {boolean} isHidden - */ +interface ContentOpts { + /** will also save this BNote entity */ + forceSave?: boolean; + /** override frontend heuristics on when to reload, instruct to reload */ + forceFrontendReload?: boolean; +} + +interface AttachmentOpts { + includeContentLength?: boolean; +} + +interface Relationship { + parentNoteId: string; + childNoteId: string +} + +interface ConvertOpts { + /** if true, the action is not triggered by user, but e.g. by migration, and only perfect candidates will be migrated */ + autoConversion?: boolean; +} /** * Trilium's main entity, which can represent text note, image, code note, file attachment etc. - * - * @extends AbstractBeccaEntity */ -class BNote extends AbstractBeccaEntity { +class BNote extends AbstractBeccaEntity { static get entityName() { return "notes"; } static get primaryKeyName() { return "noteId"; } static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime", "blobId"]; } - constructor(row) { + noteId!: string; + title!: string; + type!: NoteType; + mime!: string; + /** set during the deletion operation, before it is completed (removed from becca completely). */ + isBeingDeleted!: boolean; + isDecrypted!: boolean; + + ownedAttributes!: BAttribute[]; + parentBranches!: BBranch[]; + parents!: BNote[]; + children!: BNote[]; + targetRelations!: BAttribute[]; + + private __flatTextCache!: string | null; + + private __attributeCache!: BAttribute[] | null; + private __inheritableAttributeCache!: BAttribute[] | null; + private __ancestorCache!: BNote[] | null; + + // following attributes are filled during searching in the database + /** size of the content in bytes */ + private contentSize!: number | null; + /** size of the note content, attachment contents in bytes */ + private contentAndAttachmentsSize!: number | null; + /** size of the note content, attachment contents and revision contents in bytes */ + private contentAndAttachmentsAndRevisionsSize!: number | null; + /** number of note revisions for this note */ + private revisionCount!: number | null; + + constructor(row: Partial) { super(); if (!row) { @@ -53,7 +97,7 @@ class BNote extends AbstractBeccaEntity { this.init(); } - updateFromRow(row) { + updateFromRow(row: Partial) { this.update([ row.noteId, row.title, @@ -68,104 +112,47 @@ class BNote extends AbstractBeccaEntity { ]); } - update([noteId, title, type, mime, isProtected, blobId, dateCreated, dateModified, utcDateCreated, utcDateModified]) { + update([noteId, title, type, mime, isProtected, blobId, dateCreated, dateModified, utcDateCreated, utcDateModified]: any) { // ------ Database persisted attributes ------ - /** @type {string} */ this.noteId = noteId; - /** @type {string} */ this.title = title; - /** @type {NoteType} */ this.type = type; - /** @type {string} */ this.mime = mime; - /** @type {boolean} */ this.isProtected = !!isProtected; - /** @type {string} */ this.blobId = blobId; - /** @type {string} */ this.dateCreated = dateCreated || dateUtils.localNowDateTime(); - /** @type {string} */ this.dateModified = dateModified; - /** @type {string} */ this.utcDateCreated = utcDateCreated || dateUtils.utcNowDateTime(); - /** @type {string} */ this.utcDateModified = utcDateModified; - /** - * set during the deletion operation, before it is completed (removed from becca completely) - * @type {boolean} - */ this.isBeingDeleted = false; // ------ Derived attributes ------ - /** @type {boolean} */ this.isDecrypted = !this.noteId || !this.isProtected; this.decrypt(); - /** @type {string|null} */ this.__flatTextCache = null; return this; } init() { - /** @type {BBranch[]} - * @private */ this.parentBranches = []; - /** @type {BNote[]} - * @private */ this.parents = []; - /** @type {BNote[]} - * @private */ this.children = []; - /** @type {BAttribute[]} - * @private */ this.ownedAttributes = []; - - /** @type {BAttribute[]|null} - * @private */ this.__attributeCache = null; - /** @type {BAttribute[]|null} - * @private */ this.__inheritableAttributeCache = null; - - /** @type {BAttribute[]} - * @private */ this.targetRelations = []; this.becca.addNote(this.noteId, this); - - /** @type {BNote[]|null} - * @private */ this.__ancestorCache = null; - // following attributes are filled during searching in the database - - /** - * size of the content in bytes - * @type {int|null} - * @private - */ this.contentSize = null; - /** - * size of the note content, attachment contents in bytes - * @type {int|null} - * @private - */ this.contentAndAttachmentsSize = null; - /** - * size of the note content, attachment contents and revision contents in bytes - * @type {int|null} - * @private - */ this.contentAndAttachmentsAndRevisionsSize = null; - /** - * number of note revisions for this note - * @type {int|null} - * @private - */ this.revisionCount = null; } @@ -216,9 +203,9 @@ class BNote extends AbstractBeccaEntity { return this.children && this.children.length > 0; } - /** @returns {BBranch[]} */ - getChildBranches() { - return this.children.map(childNote => this.becca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); + getChildBranches(): (BBranch | null)[] { + return this.children + .map(childNote => this.becca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); } /* @@ -229,16 +216,14 @@ class BNote extends AbstractBeccaEntity { * - changes in the note metadata or title should not trigger note content sync (so we keep separate utcDateModified and entity changes records) * - but to the user note content and title changes are one and the same - single dateModified (so all changes must go through Note and content is not a separate entity) */ - - /** @returns {string|Buffer} */ - getContent() { - return this._getContent(); + // TODO: original declaration was (string | Buffer), but everywhere it's used as a string. + getContent(): string { + return this._getContent() as string; } /** - * @returns {*} * @throws Error in case of invalid JSON */ - getJsonContent() { + getJsonContent(): {} | null { const content = this.getContent(); if (!content || !content.trim()) { @@ -258,19 +243,13 @@ class BNote extends AbstractBeccaEntity { } } - /** - * @param content - * @param {object} [opts] - * @param {object} [opts.forceSave=false] - will also save this BNote entity - * @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload - */ - setContent(content, opts) { + setContent(content: string, opts: ContentOpts = {}) { this._setContent(content, opts); eventService.emit(eventService.NOTE_CONTENT_CHANGE, { entity: this }); } - setJsonContent(content) { + setJsonContent(content: {}) { this.setContent(JSON.stringify(content, null, '\t')); } @@ -351,14 +330,18 @@ class BNote extends AbstractBeccaEntity { * Beware that the method must not create a copy of the array, but actually returns its internal array * (for performance reasons) * - * @param {string} [type] - (optional) attribute type to filter - * @param {string} [name] - (optional) attribute name to filter - * @returns {BAttribute[]} all note's attributes, including inherited ones + * @param type - (optional) attribute type to filter + * @param name - (optional) attribute name to filter + * @returns all note's attributes, including inherited ones */ - getAttributes(type, name) { + getAttributes(type?: string, name?: string): BAttribute[] { this.__validateTypeName(type, name); this.__ensureAttributeCacheIsAvailable(); + if (!this.__attributeCache) { + throw new Error("Attribute cache not available."); + } + if (type && name) { return this.__attributeCache.filter(attr => attr.name === name && attr.type === type); } @@ -373,15 +356,13 @@ class BNote extends AbstractBeccaEntity { } } - /** @private */ - __ensureAttributeCacheIsAvailable() { + private __ensureAttributeCacheIsAvailable() { if (!this.__attributeCache) { this.__getAttributes([]); } } - /** @private */ - __getAttributes(path) { + private __getAttributes(path: string[]) { if (path.includes(this.noteId)) { return []; } @@ -437,11 +418,7 @@ class BNote extends AbstractBeccaEntity { return this.__attributeCache; } - /** - * @private - * @returns {BAttribute[]} - */ - __getInheritableAttributes(path) { + private __getInheritableAttributes(path: string[]): BAttribute[] { if (path.includes(this.noteId)) { return []; } @@ -450,10 +427,10 @@ class BNote extends AbstractBeccaEntity { this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache } - return this.__inheritableAttributeCache; + return this.__inheritableAttributeCache || []; } - __validateTypeName(type, name) { + __validateTypeName(type?: string | null, name?: string | null) { if (type && type !== 'label' && type !== 'relation') { throw new Error(`Unrecognized attribute type '${type}'. Only 'label' and 'relation' are possible values.`); } @@ -466,13 +443,7 @@ class BNote extends AbstractBeccaEntity { } } - /** - * @param type - * @param name - * @param [value] - * @returns {boolean} - */ - hasAttribute(type, name, value = null) { + hasAttribute(type: string, name: string, value: string | null = null): boolean { return !!this.getAttributes().find(attr => attr.name === name && (value === undefined || value === null || attr.value === value) @@ -480,7 +451,7 @@ class BNote extends AbstractBeccaEntity { ); } - getAttributeCaseInsensitive(type, name, value) { + getAttributeCaseInsensitive(type: string, name: string, value: string | null) { name = name.toLowerCase(); value = value ? value.toLowerCase() : null; @@ -490,24 +461,26 @@ class BNote extends AbstractBeccaEntity { && attr.type === type); } - getRelationTarget(name) { + getRelationTarget(name: string) { const relation = this.getAttributes().find(attr => attr.name === name && attr.type === 'relation'); return relation ? relation.targetNote : null; } /** - * @param {string} name - label name - * @param {string} [value] - label value - * @returns {boolean} true if label exists (including inherited) + * @param name - label name + * @param value - label value + * @returns true if label exists (including inherited) */ - hasLabel(name, value) { return this.hasAttribute(LABEL, name, value); } + hasLabel(name: string, value?: string): boolean { + return this.hasAttribute(LABEL, name, value); + } /** - * @param {string} name - label name - * @returns {boolean} true if label exists (including inherited) and does not have "false" value. + * @param name - label name + * @returns true if label exists (including inherited) and does not have "false" value. */ - isLabelTruthy(name) { + isLabelTruthy(name: string): boolean { const label = this.getLabel(name); if (!label) { @@ -518,163 +491,185 @@ class BNote extends AbstractBeccaEntity { } /** - * @param {string} name - label name - * @param {string} [value] - label value - * @returns {boolean} true if label exists (excluding inherited) + * @param name - label name + * @param value - label value + * @returns true if label exists (excluding inherited) */ - hasOwnedLabel(name, value) { return this.hasOwnedAttribute(LABEL, name, value); } + hasOwnedLabel(name: string, value?: string): boolean { + return this.hasOwnedAttribute(LABEL, name, value); + } /** - * @param {string} name - relation name - * @param {string} [value] - relation value - * @returns {boolean} true if relation exists (including inherited) + * @param name - relation name + * @param value - relation value + * @returns true if relation exists (including inherited) */ - hasRelation(name, value) { return this.hasAttribute(RELATION, name, value); } + hasRelation(name: string, value?: string): boolean { + return this.hasAttribute(RELATION, name, value); + } /** - * @param {string} name - relation name - * @param {string} [value] - relation value - * @returns {boolean} true if relation exists (excluding inherited) + * @param name - relation name + * @param value - relation value + * @returns true if relation exists (excluding inherited) */ - hasOwnedRelation(name, value) { return this.hasOwnedAttribute(RELATION, name, value); } + hasOwnedRelation(name: string, value?: string): boolean { + return this.hasOwnedAttribute(RELATION, name, value); + } /** * @param {string} name - label name * @returns {BAttribute|null} label if it exists, null otherwise */ - getLabel(name) { return this.getAttribute(LABEL, name); } + getLabel(name: string): BAttribute | null { + return this.getAttribute(LABEL, name); + } /** - * @param {string} name - label name - * @returns {BAttribute|null} label if it exists, null otherwise + * @param name - label name + * @returns label if it exists, null otherwise */ - getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); } + getOwnedLabel(name: string): BAttribute | null { + return this.getOwnedAttribute(LABEL, name); + } /** - * @param {string} name - relation name - * @returns {BAttribute|null} relation if it exists, null otherwise + * @param name - relation name + * @returns relation if it exists, null otherwise */ - getRelation(name) { return this.getAttribute(RELATION, name); } + getRelation(name: string): BAttribute | null { + return this.getAttribute(RELATION, name); + } /** - * @param {string} name - relation name - * @returns {BAttribute|null} relation if it exists, null otherwise + * @param name - relation name + * @returns relation if it exists, null otherwise */ - getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); } + getOwnedRelation(name: string): BAttribute | null { + return this.getOwnedAttribute(RELATION, name); + } /** - * @param {string} name - label name - * @returns {string|null} label value if label exists, null otherwise + * @param name - label name + * @returns label value if label exists, null otherwise */ - getLabelValue(name) { return this.getAttributeValue(LABEL, name); } + getLabelValue(name: string): string | null { + return this.getAttributeValue(LABEL, name); + } /** - * @param {string} name - label name - * @returns {string|null} label value if label exists, null otherwise + * @param name - label name + * @returns label value if label exists, null otherwise */ - getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); } + getOwnedLabelValue(name: string): string | null { + return this.getOwnedAttributeValue(LABEL, name); + } /** - * @param {string} name - relation name - * @returns {string|null} relation value if relation exists, null otherwise + * @param name - relation name + * @returns relation value if relation exists, null otherwise */ - getRelationValue(name) { return this.getAttributeValue(RELATION, name); } + getRelationValue(name: string): string | null { + return this.getAttributeValue(RELATION, name); + } /** - * @param {string} name - relation name - * @returns {string|null} relation value if relation exists, null otherwise + * @param name - relation name + * @returns relation value if relation exists, null otherwise */ - getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); } + getOwnedRelationValue(name: string): string | null { + return this.getOwnedAttributeValue(RELATION, name); + } /** - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name - * @param {string} [value] - attribute value - * @returns {boolean} true if note has an attribute with given type and name (excluding inherited) + * @param attribute type (label, relation, etc.) + * @param name - attribute name + * @param value - attribute value + * @returns true if note has an attribute with given type and name (excluding inherited) */ - hasOwnedAttribute(type, name, value) { + hasOwnedAttribute(type: string, name: string, value?: string): boolean { return !!this.getOwnedAttribute(type, name, value); } /** - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name - * @returns {BAttribute} attribute of the given type and name. If there are more such attributes, first is returned. - * Returns null if there's no such attribute belonging to this note. + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name + * @returns attribute of the given type and name. If there are more such attributes, first is returned. + * Returns null if there's no such attribute belonging to this note. */ - getAttribute(type, name) { + getAttribute(type: string, name: string): BAttribute | null { const attributes = this.getAttributes(); - return attributes.find(attr => attr.name === name && attr.type === type); + return attributes.find(attr => attr.name === name && attr.type === type) || null; } /** - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name - * @returns {string|null} attribute value of given type and name or null if no such attribute exists. + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name + * @returns attribute value of given type and name or null if no such attribute exists. */ - getAttributeValue(type, name) { + getAttributeValue(type: string, name: string): string | null { const attr = this.getAttribute(type, name); return attr ? attr.value : null; } /** - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name - * @returns {string|null} attribute value of given type and name or null if no such attribute exists. + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name + * @returns attribute value of given type and name or null if no such attribute exists. */ - getOwnedAttributeValue(type, name) { + getOwnedAttributeValue(type: string, name: string): string | null { const attr = this.getOwnedAttribute(type, name); return attr ? attr.value : null; } /** - * @param {string} [name] - label name to filter - * @returns {BAttribute[]} all note's labels (attributes with type label), including inherited ones + * @param name - label name to filter + * @returns all note's labels (attributes with type label), including inherited ones */ - getLabels(name) { + getLabels(name?: string): BAttribute[] { return this.getAttributes(LABEL, name); } /** - * @param {string} [name] - label name to filter - * @returns {string[]} all note's label values, including inherited ones + * @param name - label name to filter + * @returns all note's label values, including inherited ones */ - getLabelValues(name) { + getLabelValues(name: string): string[] { return this.getLabels(name).map(l => l.value); } /** - * @param {string} [name] - label name to filter - * @returns {BAttribute[]} all note's labels (attributes with type label), excluding inherited ones + * @param name - label name to filter + * @returns all note's labels (attributes with type label), excluding inherited ones */ - getOwnedLabels(name) { + getOwnedLabels(name: string): BAttribute[] { return this.getOwnedAttributes(LABEL, name); } /** - * @param {string} [name] - label name to filter - * @returns {string[]} all note's label values, excluding inherited ones + * @param name - label name to filter + * @returns all note's label values, excluding inherited ones */ - getOwnedLabelValues(name) { + getOwnedLabelValues(name: string): string[] { return this.getOwnedAttributes(LABEL, name).map(l => l.value); } /** - * @param {string} [name] - relation name to filter - * @returns {BAttribute[]} all note's relations (attributes with type relation), including inherited ones + * @param name - relation name to filter + * @returns all note's relations (attributes with type relation), including inherited ones */ - getRelations(name) { + getRelations(name: string): BAttribute[] { return this.getAttributes(RELATION, name); } /** - * @param {string} [name] - relation name to filter - * @returns {BAttribute[]} all note's relations (attributes with type relation), excluding inherited ones + * @param name - relation name to filter + * @returns all note's relations (attributes with type relation), excluding inherited ones */ - getOwnedRelations(name) { + getOwnedRelations(name: string): BAttribute[] { return this.getOwnedAttributes(RELATION, name); } @@ -682,12 +677,12 @@ class BNote extends AbstractBeccaEntity { * Beware that the method must not create a copy of the array, but actually returns its internal array * (for performance reasons) * - * @param {string|null} [type] - (optional) attribute type to filter - * @param {string|null} [name] - (optional) attribute name to filter - * @param {string|null} [value] - (optional) attribute value to filter + * @param type - (optional) attribute type to filter + * @param name - (optional) attribute name to filter + * @param value - (optional) attribute value to filter * @returns {BAttribute[]} note's "owned" attributes - excluding inherited ones */ - getOwnedAttributes(type = null, name = null, value = null) { + getOwnedAttributes(type: string | null = null, name: string | null = null, value: string | null = null) { this.__validateTypeName(type, name); if (type && name && value !== undefined && value !== null) { @@ -712,7 +707,7 @@ class BNote extends AbstractBeccaEntity { * * This method can be significantly faster than the getAttribute() */ - getOwnedAttribute(type, name, value = null) { + getOwnedAttribute(type: string, name: string, value: string | null = null) { const attrs = this.getOwnedAttributes(type, name, value); return attrs.length > 0 ? attrs[0] : null; @@ -762,7 +757,7 @@ class BNote extends AbstractBeccaEntity { this.parents = this.parentBranches .map(branch => branch.parentNote) - .filter(note => !!note); + .filter(note => !!note) as BNote[]; } sortChildren() { @@ -776,7 +771,7 @@ class BNote extends AbstractBeccaEntity { const aBranch = becca.getBranchFromChildAndParent(a.noteId, this.noteId); const bBranch = becca.getBranchFromChildAndParent(b.noteId, this.noteId); - return (aBranch?.notePosition - bBranch?.notePosition) || 0; + return ((aBranch?.notePosition || 0) - (bBranch?.notePosition || 0)) || 0; }); } @@ -824,7 +819,7 @@ class BNote extends AbstractBeccaEntity { this.__ancestorCache = null; } - invalidateSubTree(path = []) { + invalidateSubTree(path: string[] = []) { if (path.includes(this.noteId)) { return; } @@ -864,11 +859,10 @@ class BNote extends AbstractBeccaEntity { return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit'); } - /** @returns {BNote[]} */ - getSubtreeNotesIncludingTemplated() { - const set = new Set(); + getSubtreeNotesIncludingTemplated(): BNote[] { + const set = new Set(); - function inner(note) { + function inner(note: BNote) { // _hidden is not counted as subtree for the purpose of inheritance if (set.has(note) || note.noteId === '_hidden') { return; @@ -896,46 +890,45 @@ class BNote extends AbstractBeccaEntity { return Array.from(set); } - /** @returns {BNote[]} */ - getSearchResultNotes() { + getSearchResultNotes(): BNote[] { if (this.type !== 'search') { return []; } try { - const searchService = require('../../services/search/services/search.js'); + const searchService = require('../../services/search/services/search'); const {searchResultNoteIds} = searchService.searchFromNote(this); const becca = this.becca; - return searchResultNoteIds + return (searchResultNoteIds as string[]) // TODO: remove cast once search is converted .map(resultNoteId => becca.notes[resultNoteId]) .filter(note => !!note); } - catch (e) { + catch (e: any) { log.error(`Could not resolve search note ${this.noteId}: ${e.message}`); return []; } } - /** - * @returns {{notes: BNote[], relationships: Array.<{parentNoteId: string, childNoteId: string}>}} - */ - getSubtree({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) { - const noteSet = new Set(); - const relationships = []; // list of tuples parentNoteId -> childNoteId + getSubtree({includeArchived = true, includeHidden = false, resolveSearch = false} = {}): { + notes: BNote[], + relationships: Relationship[] + } { + const noteSet = new Set(); + const relationships: Relationship[] = []; // list of tuples parentNoteId -> childNoteId - function resolveSearchNote(searchNote) { + function resolveSearchNote(searchNote: BNote) { try { for (const resultNote of searchNote.getSearchResultNotes()) { addSubtreeNotesInner(resultNote, searchNote); } } - catch (e) { + catch (e: any) { log.error(`Could not resolve search note ${searchNote?.noteId}: ${e.message}`); } } - function addSubtreeNotesInner(note, parentNote = null) { + function addSubtreeNotesInner(note: BNote, parentNote: BNote | null = null) { if (note.noteId === '_hidden' && !includeHidden) { return; } @@ -1064,13 +1057,11 @@ class BNote extends AbstractBeccaEntity { return this.__ancestorCache; } - /** @returns {string[]} */ - getAncestorNoteIds() { + getAncestorNoteIds(): string[] { return this.getAncestors().map(note => note.noteId); } - /** @returns {boolean} */ - hasAncestor(ancestorNoteId) { + hasAncestor(ancestorNoteId: string): boolean { for (const ancestorNote of this.getAncestors()) { if (ancestorNote.noteId === ancestorNoteId) { return true; @@ -1089,10 +1080,10 @@ class BNote extends AbstractBeccaEntity { return this.targetRelations; } - /** @returns {BNote[]} - returns only notes which are templated, does not include their subtrees - * in effect returns notes which are influenced by note's non-inheritable attributes */ - getInheritingNotes() { - const arr = [this]; + /** @returns returns only notes which are templated, does not include their subtrees + * in effect returns notes which are influenced by note's non-inheritable attributes */ + getInheritingNotes(): BNote[] { + const arr: BNote[] = [this]; for (const targetRelation of this.targetRelations) { if (targetRelation.name === 'template' || targetRelation.name === 'inherit') { @@ -1107,7 +1098,7 @@ class BNote extends AbstractBeccaEntity { return arr; } - getDistanceToAncestor(ancestorNoteId) { + getDistanceToAncestor(ancestorNoteId: string) { if (this.noteId === ancestorNoteId) { return 0; } @@ -1121,14 +1112,13 @@ class BNote extends AbstractBeccaEntity { return minDistance; } - /** @returns {BRevision[]} */ - getRevisions() { - return sql.getRows("SELECT * FROM revisions WHERE noteId = ?", [this.noteId]) + getRevisions(): BRevision[] { + return sql.getRows("SELECT * FROM revisions WHERE noteId = ?", [this.noteId]) .map(row => new BRevision(row)); } /** @returns {BAttachment[]} */ - getAttachments(opts = {}) { + getAttachments(opts: AttachmentOpts = {}) { opts.includeContentLength = !!opts.includeContentLength; // from testing, it looks like calculating length does not make a difference in performance even on large-ish DB // given that we're always fetching attachments only for a specific note, we might just do it always @@ -1141,12 +1131,12 @@ class BNote extends AbstractBeccaEntity { ORDER BY position` : `SELECT * FROM attachments WHERE ownerId = ? AND isDeleted = 0 ORDER BY position`; - return sql.getRows(query, [this.noteId]) + return sql.getRows(query, [this.noteId]) .map(row => new BAttachment(row)); } /** @returns {BAttachment|null} */ - getAttachmentById(attachmentId, opts = {}) { + getAttachmentById(attachmentId: string, opts: AttachmentOpts = {}) { opts.includeContentLength = !!opts.includeContentLength; const query = opts.includeContentLength @@ -1156,13 +1146,12 @@ class BNote extends AbstractBeccaEntity { WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0` : `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`; - return sql.getRows(query, [this.noteId, attachmentId]) + return sql.getRows(query, [this.noteId, attachmentId]) .map(row => new BAttachment(row))[0]; } - /** @returns {BAttachment[]} */ - getAttachmentsByRole(role) { - return sql.getRows(` + getAttachmentsByRole(role: string): BAttachment[] { + return sql.getRows(` SELECT attachments.* FROM attachments WHERE ownerId = ? @@ -1172,8 +1161,7 @@ class BNote extends AbstractBeccaEntity { .map(row => new BAttachment(row)); } - /** @returns {BAttachment} */ - getAttachmentByTitle(title) { + getAttachmentByTitle(title: string): BAttachment { // cannot use SQL to filter by title since it can be encrypted return this.getAttachments().filter(attachment => attachment.title === title)[0]; } @@ -1181,9 +1169,9 @@ class BNote extends AbstractBeccaEntity { /** * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) * - * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) + * @returns array of notePaths (each represented by array of noteIds constituting the particular note path) */ - getAllNotePaths() { + getAllNotePaths(): string[][] { if (this.noteId === 'root') { return [['root']]; } @@ -1201,11 +1189,7 @@ class BNote extends AbstractBeccaEntity { return notePaths; } - /** - * @param {string} [hoistedNoteId='root'] - * @return {Array} - */ - getSortedNotePathRecords(hoistedNoteId = 'root') { + getSortedNotePathRecords(hoistedNoteId: string = 'root'): NotePathRecord[] { const isHoistedRoot = hoistedNoteId === 'root'; const notePaths = this.getAllNotePaths().map(path => ({ @@ -1233,20 +1217,18 @@ class BNote extends AbstractBeccaEntity { /** * Returns a note path considered to be the "best" * - * @param {string} [hoistedNoteId='root'] - * @return {string[]} array of noteIds constituting the particular note path + * @return array of noteIds constituting the particular note path */ - getBestNotePath(hoistedNoteId = 'root') { + getBestNotePath(hoistedNoteId: string = 'root'): string[] { return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; } /** * Returns a note path considered to be the "best" * - * @param {string} [hoistedNoteId='root'] - * @return {string} serialized note path (e.g. 'root/a1h315/js725h') + * @return serialized note path (e.g. 'root/a1h315/js725h') */ - getBestNotePathString(hoistedNoteId = 'root') { + getBestNotePathString(hoistedNoteId: string = 'root'): string { const notePath = this.getBestNotePath(hoistedNoteId); return notePath?.join("/"); @@ -1274,10 +1256,9 @@ class BNote extends AbstractBeccaEntity { } /** - * @param ancestorNoteId - * @returns {boolean} - true if ancestorNoteId occurs in at least one of the note's paths + * @returns true if ancestorNoteId occurs in at least one of the note's paths */ - isDescendantOfNote(ancestorNoteId) { + isDescendantOfNote(ancestorNoteId: string): boolean { const notePaths = this.getAllNotePaths(); return notePaths.some(path => path.includes(ancestorNoteId)); @@ -1286,11 +1267,11 @@ class BNote extends AbstractBeccaEntity { /** * Update's given attribute's value or creates it if it doesn't exist * - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name - * @param {string} [value] - attribute value (optional) + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name + * @param value - attribute value (optional) */ - setAttribute(type, name, value) { + setAttribute(type: string, name: string, value?: string) { const attributes = this.getOwnedAttributes(); const attr = attributes.find(attr => attr.type === type && attr.name === name); @@ -1303,7 +1284,7 @@ class BNote extends AbstractBeccaEntity { } } else { - const BAttribute = require('./battribute.js'); + const BAttribute = require('./battribute'); new BAttribute({ noteId: this.noteId, @@ -1317,11 +1298,11 @@ class BNote extends AbstractBeccaEntity { /** * Removes given attribute name-value pair if it exists. * - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name - * @param {string} [value] - attribute value (optional) + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name + * @param value - attribute value (optional) */ - removeAttribute(type, name, value) { + removeAttribute(type: string, name: string, value?: string) { const attributes = this.getOwnedAttributes(); for (const attribute of attributes) { @@ -1335,15 +1316,12 @@ class BNote extends AbstractBeccaEntity { * Adds a new attribute to this note. The attribute is saved and returned. * See addLabel, addRelation for more specific methods. * - * @param {string} type - attribute type (label / relation) - * @param {string} name - name of the attribute, not including the leading ~/# - * @param {string} [value] - value of the attribute - text for labels, target note ID for relations; optional. - * @param {boolean} [isInheritable=false] - * @param {int|null} [position] - * @returns {BAttribute} + * @param type - attribute type (label / relation) + * @param name - name of the attribute, not including the leading ~/# + * @param value - value of the attribute - text for labels, target note ID for relations; optional. */ - addAttribute(type, name, value = "", isInheritable = false, position = null) { - const BAttribute = require('./battribute.js'); + addAttribute(type: string, name: string, value: string = "", isInheritable: boolean = false, position: number | null = null): BAttribute { + const BAttribute = require('./battribute'); return new BAttribute({ noteId: this.noteId, @@ -1358,12 +1336,10 @@ class BNote extends AbstractBeccaEntity { /** * Adds a new label to this note. The label attribute is saved and returned. * - * @param {string} name - name of the label, not including the leading # - * @param {string} [value] - text value of the label; optional - * @param {boolean} [isInheritable=false] - * @returns {BAttribute} + * @param name - name of the label, not including the leading # + * @param value - text value of the label; optional */ - addLabel(name, value = "", isInheritable = false) { + addLabel(name: string, value: string = "", isInheritable: boolean = false): BAttribute { return this.addAttribute(LABEL, name, value, isInheritable); } @@ -1371,24 +1347,21 @@ class BNote extends AbstractBeccaEntity { * Adds a new relation to this note. The relation attribute is saved and * returned. * - * @param {string} name - name of the relation, not including the leading ~ - * @param {string} targetNoteId - * @param {boolean} [isInheritable=false] - * @returns {BAttribute} + * @param name - name of the relation, not including the leading ~ */ - addRelation(name, targetNoteId, isInheritable = false) { + addRelation(name: string, targetNoteId: string, isInheritable: boolean = false): BAttribute { return this.addAttribute(RELATION, name, targetNoteId, isInheritable); } /** * Based on enabled, the attribute is either set or removed. * - * @param {string} type - attribute type ('relation', 'label' etc.) - * @param {boolean} enabled - toggle On or Off - * @param {string} name - attribute name - * @param {string} [value] - attribute value (optional) + * @param type - attribute type ('relation', 'label' etc.) + * @param enabled - toggle On or Off + * @param name - attribute name + * @param value - attribute value (optional) */ - toggleAttribute(type, enabled, name, value) { + toggleAttribute(type: string, enabled: boolean, name: string, value?: string) { if (enabled) { this.setAttribute(type, name, value); } @@ -1400,76 +1373,84 @@ class BNote extends AbstractBeccaEntity { /** * Based on enabled, label is either set or removed. * - * @param {boolean} enabled - toggle On or Off - * @param {string} name - label name - * @param {string} [value] - label value (optional) + * @param enabled - toggle On or Off + * @param name - label name + * @param value - label value (optional) */ - toggleLabel(enabled, name, value) { return this.toggleAttribute(LABEL, enabled, name, value); } + toggleLabel(enabled: boolean, name: string, value?: string) { + return this.toggleAttribute(LABEL, enabled, name, value); + } /** * Based on enabled, relation is either set or removed. * - * @param {boolean} enabled - toggle On or Off - * @param {string} name - relation name - * @param {string} [value] - relation value (noteId) + * @param enabled - toggle On or Off + * @param name - relation name + * @param value - relation value (noteId) */ - toggleRelation(enabled, name, value) { return this.toggleAttribute(RELATION, enabled, name, value); } + toggleRelation(enabled: boolean, name: string, value?: string) { + return this.toggleAttribute(RELATION, enabled, name, value); + } /** * Update's given label's value or creates it if it doesn't exist * - * @param {string} name - label name - * @param {string} [value] - label value + * @param name - label name + * @param value label value */ - setLabel(name, value) { return this.setAttribute(LABEL, name, value); } + setLabel(name: string, value?: string) { + return this.setAttribute(LABEL, name, value); + } /** * Update's given relation's value or creates it if it doesn't exist * - * @param {string} name - relation name - * @param {string} value - relation value (noteId) + * @param name - relation name + * @param value - relation value (noteId) */ - setRelation(name, value) { return this.setAttribute(RELATION, name, value); } + setRelation(name: string, value: string) { + return this.setAttribute(RELATION, name, value); + } /** * Remove label name-value pair, if it exists. * - * @param {string} name - label name - * @param {string} [value] - label value + * @param name - label name + * @param value - label value */ - removeLabel(name, value) { return this.removeAttribute(LABEL, name, value); } + removeLabel(name: string, value?: string) { + return this.removeAttribute(LABEL, name, value); + } /** * Remove the relation name-value pair, if it exists. * - * @param {string} name - relation name - * @param {string} [value] - relation value (noteId) + * @param name - relation name + * @param value - relation value (noteId) */ - removeRelation(name, value) { return this.removeAttribute(RELATION, name, value); } + removeRelation(name: string, value?: string) { + return this.removeAttribute(RELATION, name, value); + } - searchNotesInSubtree(searchString) { - const searchService = require('../../services/search/services/search.js'); + searchNotesInSubtree(searchString: string) { + const searchService = require('../../services/search/services/search'); return searchService.searchNotes(searchString); } - searchNoteInSubtree(searchString) { + searchNoteInSubtree(searchString: string) { return this.searchNotesInSubtree(searchString)[0]; } - /** - * @param parentNoteId - * @returns {{success: boolean, message: string, branchId: string, notePath: string}} - */ - cloneTo(parentNoteId) { - const cloningService = require('../../services/cloning.js'); + cloneTo(parentNoteId: string) { + const cloningService = require('../../services/cloning'); - const branch = this.becca.getNote(parentNoteId).getParentBranches()[0]; + const branch = this.becca.getNote(parentNoteId)?.getParentBranches()[0]; - return cloningService.cloneNoteToBranch(this.noteId, branch.branchId); + return cloningService.cloneNoteToBranch(this.noteId, branch?.branchId); } - isEligibleForConversionToAttachment(opts = {autoConversion: false}) { + isEligibleForConversionToAttachment(opts: ConvertOpts = { autoConversion: false }) { if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) { return false; } @@ -1507,13 +1488,9 @@ class BNote extends AbstractBeccaEntity { * * In the future, this functionality might get more generic and some of the requirements relaxed. * - * @params {Object} [opts] - * @params {bolean} [opts.autoConversion=false} if true, the action is not triggered by user, but e.g. by migration, - * and only perfect candidates will be migrated - * - * @returns {BAttachment|null} - null if note is not eligible for conversion + * @returns null if note is not eligible for conversion */ - convertToParentAttachment(opts = {autoConversion: false}) { + convertToParentAttachment(opts: ConvertOpts = { autoConversion: false }): BAttachment | null { if (!this.isEligibleForConversionToAttachment(opts)) { return null; } @@ -1537,7 +1514,7 @@ class BNote extends AbstractBeccaEntity { parentNote.setContent(fixedContent); - const noteService = require('../../services/notes.js'); + const noteService = require('../../services/notes'); noteService.asyncPostProcessContent(parentNote, fixedContent); // to mark an unused attachment for deletion this.deleteNote(); @@ -1548,10 +1525,9 @@ class BNote extends AbstractBeccaEntity { /** * (Soft) delete a note and all its descendants. * - * @param {string} [deleteId=null] - optional delete identified - * @param {TaskContext} [taskContext] + * @param deleteId - optional delete identified */ - deleteNote(deleteId = null, taskContext = null) { + deleteNote(deleteId: string | null = null, taskContext: TaskContext | null = null) { if (this.isDeleted) { return; } @@ -1565,7 +1541,7 @@ class BNote extends AbstractBeccaEntity { } // needs to be run before branches and attributes are deleted and thus attached relations disappear - const handlers = require('../../services/handlers.js'); + const handlers = require('../../services/handlers'); handlers.runAttachedRelations(this, 'runOnNoteDeletion', this); taskContext.noteDeletionHandlerTriggered = true; @@ -1577,12 +1553,12 @@ class BNote extends AbstractBeccaEntity { decrypt() { if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { try { - this.title = protectedSessionService.decryptString(this.title); + this.title = protectedSessionService.decryptString(this.title) || ""; this.__flatTextCache = null; this.isDecrypted = true; } - catch (e) { + catch (e: any) { log.error(`Could not decrypt note ${this.noteId}: ${e.message} ${e.stack}`); } } @@ -1627,8 +1603,13 @@ class BNote extends AbstractBeccaEntity { for (const noteAttachment of this.getAttachments()) { const revisionAttachment = noteAttachment.copy(); + + if (!revision.revisionId) { + throw new Error("Revision ID is missing."); + } + revisionAttachment.ownerId = revision.revisionId; - revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true}); + revisionAttachment.setContent(noteAttachment.getContent(), { forceSave: true }); if (this.type === 'text') { // content is rewritten to point to the revision attachments @@ -1651,14 +1632,14 @@ class BNote extends AbstractBeccaEntity { * Supported values are either 'attachmentId' (default) or 'title' * @returns {BAttachment} */ - saveAttachment({attachmentId, role, mime, title, content, position}, matchBy = 'attachmentId') { + saveAttachment({attachmentId, role, mime, title, content, position}: AttachmentRow, matchBy = 'attachmentId') { if (!['attachmentId', 'title'].includes(matchBy)) { throw new Error(`Unsupported value '${matchBy}' for matchBy param, has to be either 'attachmentId' or 'title'.`); } let attachment; - if (matchBy === 'title') { + if (matchBy === 'title' && title) { attachment = this.getAttachmentByTitle(title); } else if (matchBy === 'attachmentId' && attachmentId) { attachment = this.becca.getAttachmentOrThrow(attachmentId); @@ -1695,7 +1676,7 @@ class BNote extends AbstractBeccaEntity { getPojo() { return { noteId: this.noteId, - title: this.title, + title: this.title || undefined, isProtected: this.isProtected, type: this.type, mime: this.mime, @@ -1712,8 +1693,8 @@ class BNote extends AbstractBeccaEntity { const pojo = this.getPojo(); if (pojo.isProtected) { - if (this.isDecrypted) { - pojo.title = protectedSessionService.encrypt(pojo.title); + if (this.isDecrypted && pojo.title) { + pojo.title = protectedSessionService.encrypt(pojo.title) || undefined; } else { // updating protected note outside of protected session means we will keep original ciphertexts @@ -1725,4 +1706,4 @@ class BNote extends AbstractBeccaEntity { } } -module.exports = BNote; +export = BNote; diff --git a/src/becca/entities/boption.js b/src/becca/entities/boption.ts similarity index 68% rename from src/becca/entities/boption.js rename to src/becca/entities/boption.ts index 6c2c3edab..afbf5320e 100644 --- a/src/becca/entities/boption.js +++ b/src/becca/entities/boption.ts @@ -1,33 +1,32 @@ "use strict"; -const dateUtils = require('../../services/date_utils.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); +import dateUtils = require('../../services/date_utils'); +import AbstractBeccaEntity = require('./abstract_becca_entity'); +import { OptionRow } from './rows'; /** * Option represents a name-value pair, either directly configurable by the user or some system property. - * - * @extends AbstractBeccaEntity */ -class BOption extends AbstractBeccaEntity { +class BOption extends AbstractBeccaEntity { static get entityName() { return "options"; } static get primaryKeyName() { return "name"; } static get hashedProperties() { return ["name", "value"]; } - constructor(row) { + name!: string; + value!: string; + isSynced!: boolean; + + constructor(row: OptionRow) { super(); this.updateFromRow(row); this.becca.options[this.name] = this; } - updateFromRow(row) { - /** @type {string} */ + updateFromRow(row: OptionRow) { this.name = row.name; - /** @type {string} */ this.value = row.value; - /** @type {boolean} */ this.isSynced = !!row.isSynced; - /** @type {string} */ this.utcDateModified = row.utcDateModified; } @@ -47,4 +46,4 @@ class BOption extends AbstractBeccaEntity { } } -module.exports = BOption; +export = BOption; diff --git a/src/becca/entities/brecent_note.js b/src/becca/entities/brecent_note.ts similarity index 59% rename from src/becca/entities/brecent_note.js rename to src/becca/entities/brecent_note.ts index 300945b9a..0771a5e00 100644 --- a/src/becca/entities/brecent_note.js +++ b/src/becca/entities/brecent_note.ts @@ -1,25 +1,26 @@ "use strict"; -const dateUtils = require('../../services/date_utils.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); +import { RecentNoteRow } from "./rows"; + +import dateUtils = require('../../services/date_utils'); +import AbstractBeccaEntity = require('./abstract_becca_entity'); /** * RecentNote represents recently visited note. - * - * @extends AbstractBeccaEntity */ -class BRecentNote extends AbstractBeccaEntity { +class BRecentNote extends AbstractBeccaEntity { static get entityName() { return "recent_notes"; } static get primaryKeyName() { return "noteId"; } - constructor(row) { + noteId: string; + notePath: string; + utcDateCreated: string; + + constructor(row: RecentNoteRow) { super(); - /** @type {string} */ this.noteId = row.noteId; - /** @type {string} */ this.notePath = row.notePath; - /** @type {string} */ this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime(); } @@ -32,4 +33,4 @@ class BRecentNote extends AbstractBeccaEntity { } } -module.exports = BRecentNote; +export = BRecentNote; diff --git a/src/becca/entities/brevision.js b/src/becca/entities/brevision.ts similarity index 69% rename from src/becca/entities/brevision.js rename to src/becca/entities/brevision.ts index 5a79f3d8f..ba7cc00ba 100644 --- a/src/becca/entities/brevision.js +++ b/src/becca/entities/brevision.ts @@ -1,59 +1,67 @@ "use strict"; -const protectedSessionService = require('../../services/protected_session.js'); -const utils = require('../../services/utils.js'); -const dateUtils = require('../../services/date_utils.js'); -const becca = require('../becca.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); -const sql = require('../../services/sql.js'); -const BAttachment = require('./battachment.js'); +import protectedSessionService = require('../../services/protected_session'); +import utils = require('../../services/utils'); +import dateUtils = require('../../services/date_utils'); +import becca = require('../becca'); +import AbstractBeccaEntity = require('./abstract_becca_entity'); +import sql = require('../../services/sql'); +import BAttachment = require('./battachment'); +import { AttachmentRow, RevisionRow } from './rows'; + +interface ContentOpts { + /** will also save this BRevision entity */ + forceSave?: boolean; +} + +interface GetByIdOpts { + includeContentLength?: boolean; +} /** * Revision represents a snapshot of note's title and content at some point in the past. * It's used for seamless note versioning. - * - * @extends AbstractBeccaEntity */ -class BRevision extends AbstractBeccaEntity { +class BRevision extends AbstractBeccaEntity { static get entityName() { return "revisions"; } static get primaryKeyName() { return "revisionId"; } static get hashedProperties() { return ["revisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; } - constructor(row, titleDecrypted = false) { + revisionId?: string; + noteId: string; + type: string; + mime: string; + isProtected: boolean; + title: string; + blobId?: string; + dateLastEdited?: string; + dateCreated: string; + utcDateLastEdited?: string; + utcDateCreated: string; + contentLength?: number; + content?: string; + + constructor(row: RevisionRow, titleDecrypted = false) { super(); - /** @type {string} */ this.revisionId = row.revisionId; - /** @type {string} */ this.noteId = row.noteId; - /** @type {string} */ this.type = row.type; - /** @type {string} */ this.mime = row.mime; - /** @type {boolean} */ this.isProtected = !!row.isProtected; - /** @type {string} */ this.title = row.title; - /** @type {string} */ this.blobId = row.blobId; - /** @type {string} */ this.dateLastEdited = row.dateLastEdited; - /** @type {string} */ this.dateCreated = row.dateCreated; - /** @type {string} */ this.utcDateLastEdited = row.utcDateLastEdited; - /** @type {string} */ this.utcDateCreated = row.utcDateCreated; - /** @type {string} */ this.utcDateModified = row.utcDateModified; - /** @type {int} */ this.contentLength = row.contentLength; if (this.isProtected && !titleDecrypted) { - this.title = protectedSessionService.isProtectedSessionAvailable() - ? protectedSessionService.decryptString(this.title) - : "[protected]"; + const decryptedTitle = protectedSessionService.isProtectedSessionAvailable() ? protectedSessionService.decryptString(this.title) : null; + this.title = decryptedTitle || "[protected]"; } } @@ -61,8 +69,8 @@ class BRevision extends AbstractBeccaEntity { return becca.notes[this.noteId]; } - /** @returns {boolean} true if the note has string content (not binary) */ - hasStringContent() { + /** @returns true if the note has string content (not binary) */ + hasStringContent(): boolean { return utils.isStringNote(this.type, this.mime); } @@ -80,16 +88,14 @@ class BRevision extends AbstractBeccaEntity { * * This is the same approach as is used for Note's content. */ - - /** @returns {string|Buffer} */ - getContent() { - return this._getContent(); + // TODO: initial declaration included Buffer, but everywhere it's treated as a string. + getContent(): string { + return this._getContent() as string; } /** - * @returns {*} * @throws Error in case of invalid JSON */ - getJsonContent() { + getJsonContent(): {} | null { const content = this.getContent(); if (!content || !content.trim()) { @@ -99,8 +105,8 @@ class BRevision extends AbstractBeccaEntity { return JSON.parse(content); } - /** @returns {*|null} valid object or null if the content cannot be parsed as JSON */ - getJsonContentSafely() { + /** @returns valid object or null if the content cannot be parsed as JSON */ + getJsonContentSafely(): {} | null { try { return this.getJsonContent(); } @@ -109,18 +115,12 @@ class BRevision extends AbstractBeccaEntity { } } - /** - * @param content - * @param {object} [opts] - * @param {object} [opts.forceSave=false] - will also save this BRevision entity - */ - setContent(content, opts) { + setContent(content: string | Buffer, opts: ContentOpts = {}) { this._setContent(content, opts); } - /** @returns {BAttachment[]} */ - getAttachments() { - return sql.getRows(` + getAttachments(): BAttachment[] { + return sql.getRows(` SELECT attachments.* FROM attachments WHERE ownerId = ? @@ -128,8 +128,7 @@ class BRevision extends AbstractBeccaEntity { .map(row => new BAttachment(row)); } - /** @returns {BAttachment|null} */ - getAttachmentById(attachmentId, opts = {}) { + getAttachmentById(attachmentId: String, opts: GetByIdOpts = {}): BAttachment | null { opts.includeContentLength = !!opts.includeContentLength; const query = opts.includeContentLength @@ -139,13 +138,12 @@ class BRevision extends AbstractBeccaEntity { WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0` : `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`; - return sql.getRows(query, [this.revisionId, attachmentId]) + return sql.getRows(query, [this.revisionId, attachmentId]) .map(row => new BAttachment(row))[0]; } - /** @returns {BAttachment[]} */ - getAttachmentsByRole(role) { - return sql.getRows(` + getAttachmentsByRole(role: string): BAttachment[] { + return sql.getRows(` SELECT attachments.* FROM attachments WHERE ownerId = ? @@ -155,8 +153,7 @@ class BRevision extends AbstractBeccaEntity { .map(row => new BAttachment(row)); } - /** @returns {BAttachment} */ - getAttachmentByTitle(title) { + getAttachmentByTitle(title: string): BAttachment { // cannot use SQL to filter by title since it can be encrypted return this.getAttachments().filter(attachment => attachment.title === title)[0]; } @@ -181,7 +178,7 @@ class BRevision extends AbstractBeccaEntity { type: this.type, mime: this.mime, isProtected: this.isProtected, - title: this.title, + title: this.title || undefined, blobId: this.blobId, dateLastEdited: this.dateLastEdited, dateCreated: this.dateCreated, @@ -200,7 +197,7 @@ class BRevision extends AbstractBeccaEntity { if (pojo.isProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { - pojo.title = protectedSessionService.encrypt(this.title); + pojo.title = protectedSessionService.encrypt(this.title) || undefined; } else { // updating protected note outside of protected session means we will keep original ciphertexts @@ -212,4 +209,4 @@ class BRevision extends AbstractBeccaEntity { } } -module.exports = BRevision; +export = BRevision; diff --git a/src/becca/entities/rows.ts b/src/becca/entities/rows.ts new file mode 100644 index 000000000..567f14f9f --- /dev/null +++ b/src/becca/entities/rows.ts @@ -0,0 +1,106 @@ +// TODO: Booleans should probably be numbers instead (as SQLite does not have booleans.); + +export interface AttachmentRow { + attachmentId?: string; + ownerId?: string; + role: string; + mime: string; + title?: string; + position?: number; + blobId?: string; + isProtected?: boolean; + dateModified?: string; + utcDateModified?: string; + utcDateScheduledForErasureSince?: string; + contentLength?: number; + content?: string; +} + +export interface RevisionRow { + revisionId?: string; + noteId: string; + type: string; + mime: string; + isProtected?: boolean; + title: string; + blobId?: string; + dateLastEdited?: string; + dateCreated: string; + utcDateLastEdited?: string; + utcDateCreated: string; + utcDateModified: string; + contentLength?: number; +} + +export interface RecentNoteRow { + noteId: string; + notePath: string; + utcDateCreated?: string; +} + +export interface OptionRow { + name: string; + value: string; + isSynced: boolean; + utcDateModified: string; +} + +export interface EtapiTokenRow { + etapiTokenId: string; + name: string; + tokenHash: string; + utcDateCreated?: string; + utcDateModified?: string; + isDeleted: boolean; +} + +export interface BlobRow { + blobId: string; + content: string | Buffer; + contentLength: number; + dateModified: string; + utcDateModified: string; +} + +export type AttributeType = "label" | "relation"; + +export interface AttributeRow { + attributeId?: string; + noteId: string; + type: AttributeType; + name: string; + position: number; + value: string; + isInheritable: boolean; + utcDateModified?: string; +} + +export interface BranchRow { + branchId?: string; + noteId: string; + parentNoteId: string; + prefix: string | null; + notePosition: number; + isExpanded: boolean; + utcDateModified?: string; +} + +/** + * There are many different Note types, some of which are entirely opaque to the + * end user. Those types should be used only for checking against, they are + * not for direct use. + */ +export type NoteType = ("file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code"); + +export interface NoteRow { + noteId: string; + title: string; + type: NoteType; + mime: string; + isProtected: boolean; + blobId: string; + dateCreated: string; + dateModified: string; + utcDateCreated: string; + utcDateModified: string; +} \ No newline at end of file diff --git a/src/becca/entity_constructor.js b/src/becca/entity_constructor.js index 3d66ed4da..c140b8a75 100644 --- a/src/becca/entity_constructor.js +++ b/src/becca/entity_constructor.js @@ -1,12 +1,12 @@ -const BAttachment = require('./entities/battachment.js'); -const BAttribute = require('./entities/battribute.js'); -const BBlob = require('./entities/bblob.js'); -const BBranch = require('./entities/bbranch.js'); -const BEtapiToken = require('./entities/betapi_token.js'); -const BNote = require('./entities/bnote.js'); -const BOption = require('./entities/boption.js'); -const BRecentNote = require('./entities/brecent_note.js'); -const BRevision = require('./entities/brevision.js'); +const BAttachment = require('./entities/battachment'); +const BAttribute = require('./entities/battribute'); +const BBlob = require('./entities/bblob'); +const BBranch = require('./entities/bbranch'); +const BEtapiToken = require('./entities/betapi_token'); +const BNote = require('./entities/bnote'); +const BOption = require('./entities/boption'); +const BRecentNote = require('./entities/brecent_note'); +const BRevision = require('./entities/brevision'); const ENTITY_NAME_TO_ENTITY = { "attachments": BAttachment, diff --git a/src/becca/similarity.js b/src/becca/similarity.js index 5be56804d..07f643a7d 100644 --- a/src/becca/similarity.js +++ b/src/becca/similarity.js @@ -1,7 +1,7 @@ -const becca = require('./becca.js'); -const log = require('../services/log.js'); -const beccaService = require('./becca_service.js'); -const dateUtils = require('../services/date_utils.js'); +const becca = require('./becca'); +const log = require('../services/log'); +const beccaService = require('./becca_service'); +const dateUtils = require('../services/date_utils'); const {JSDOM} = require("jsdom"); const DEBUG = false; diff --git a/src/errors/not_found_error.js b/src/errors/not_found_error.js deleted file mode 100644 index af746b82c..000000000 --- a/src/errors/not_found_error.js +++ /dev/null @@ -1,7 +0,0 @@ -class NotFoundError { - constructor(message) { - this.message = message; - } -} - -module.exports = NotFoundError; \ No newline at end of file diff --git a/src/errors/not_found_error.ts b/src/errors/not_found_error.ts new file mode 100644 index 000000000..f765e11f9 --- /dev/null +++ b/src/errors/not_found_error.ts @@ -0,0 +1,9 @@ +class NotFoundError { + message: string; + + constructor(message: string) { + this.message = message; + } +} + +export = NotFoundError; \ No newline at end of file diff --git a/src/errors/validation_error.js b/src/errors/validation_error.ts similarity index 63% rename from src/errors/validation_error.js rename to src/errors/validation_error.ts index 1c9425669..0cabecc8e 100644 --- a/src/errors/validation_error.js +++ b/src/errors/validation_error.ts @@ -1,5 +1,7 @@ class ValidationError { - constructor(message) { + message: string; + + constructor(message: string) { this.message = message; } } diff --git a/src/etapi/app_info.js b/src/etapi/app_info.js index f82287170..a58850e77 100644 --- a/src/etapi/app_info.js +++ b/src/etapi/app_info.js @@ -1,5 +1,5 @@ const appInfo = require('../services/app_info.js'); -const eu = require('./etapi_utils.js'); +const eu = require('./etapi_utils'); function register(router) { eu.route(router, 'get', '/etapi/app-info', (req, res, next) => { diff --git a/src/etapi/attachments.js b/src/etapi/attachments.js index 586da7a21..8a3802061 100644 --- a/src/etapi/attachments.js +++ b/src/etapi/attachments.js @@ -1,8 +1,8 @@ -const becca = require('../becca/becca.js'); -const eu = require('./etapi_utils.js'); +const becca = require('../becca/becca'); +const eu = require('./etapi_utils'); const mappers = require('./mappers.js'); const v = require('./validators.js'); -const utils = require('../services/utils.js'); +const utils = require('../services/utils'); function register(router) { const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT = { diff --git a/src/etapi/attributes.js b/src/etapi/attributes.js index 131e85ae8..dca82b0ea 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.js @@ -1,5 +1,5 @@ -const becca = require('../becca/becca.js'); -const eu = require('./etapi_utils.js'); +const becca = require('../becca/becca'); +const eu = require('./etapi_utils'); const mappers = require('./mappers.js'); const attributeService = require('../services/attributes.js'); const v = require('./validators.js'); diff --git a/src/etapi/auth.js b/src/etapi/auth.js index 96e4e2fa0..835d016a1 100644 --- a/src/etapi/auth.js +++ b/src/etapi/auth.js @@ -1,6 +1,6 @@ -const becca = require('../becca/becca.js'); -const eu = require('./etapi_utils.js'); -const passwordEncryptionService = require('../services/encryption/password_encryption.js'); +const becca = require('../becca/becca'); +const eu = require('./etapi_utils'); +const passwordEncryptionService = require('../services/encryption/password_encryption'); const etapiTokenService = require('../services/etapi_tokens.js'); function register(router, loginMiddleware) { diff --git a/src/etapi/backup.js b/src/etapi/backup.js index b9ca75204..2897c36b2 100644 --- a/src/etapi/backup.js +++ b/src/etapi/backup.js @@ -1,4 +1,4 @@ -const eu = require('./etapi_utils.js'); +const eu = require('./etapi_utils'); const backupService = require('../services/backup.js'); function register(router) { diff --git a/src/etapi/branches.js b/src/etapi/branches.js index 1d11144f9..e0337e5cb 100644 --- a/src/etapi/branches.js +++ b/src/etapi/branches.js @@ -1,8 +1,8 @@ -const becca = require('../becca/becca.js'); -const eu = require('./etapi_utils.js'); +const becca = require('../becca/becca'); +const eu = require('./etapi_utils'); const mappers = require('./mappers.js'); -const BBranch = require('../becca/entities/bbranch.js'); -const entityChangesService = require('../services/entity_changes.js'); +const BBranch = require('../becca/entities/bbranch'); +const entityChangesService = require('../services/entity_changes'); const v = require('./validators.js'); function register(router) { diff --git a/src/etapi/etapi_utils.js b/src/etapi/etapi_utils.js index d3699b664..0ec013b20 100644 --- a/src/etapi/etapi_utils.js +++ b/src/etapi/etapi_utils.js @@ -1,9 +1,9 @@ -const cls = require('../services/cls.js'); -const sql = require('../services/sql.js'); -const log = require('../services/log.js'); -const becca = require('../becca/becca.js'); +const cls = require('../services/cls'); +const sql = require('../services/sql'); +const log = require('../services/log'); +const becca = require('../becca/becca'); const etapiTokenService = require('../services/etapi_tokens.js'); -const config = require('../services/config.js'); +const config = require('../services/config'); const GENERIC_CODE = "GENERIC"; const noAuthentication = config.General && config.General.noAuthentication === true; diff --git a/src/etapi/notes.js b/src/etapi/notes.js index 69c00e795..76318a89d 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -1,9 +1,9 @@ -const becca = require('../becca/becca.js'); -const utils = require('../services/utils.js'); -const eu = require('./etapi_utils.js'); +const becca = require('../becca/becca'); +const utils = require('../services/utils'); +const eu = require('./etapi_utils'); const mappers = require('./mappers.js'); const noteService = require('../services/notes.js'); -const TaskContext = require('../services/task_context.js'); +const TaskContext = require('../services/task_context'); const v = require('./validators.js'); const searchService = require('../services/search/services/search.js'); const SearchContext = require('../services/search/search_context.js'); diff --git a/src/etapi/special_notes.js b/src/etapi/special_notes.js index 64f97d07b..e6408b251 100644 --- a/src/etapi/special_notes.js +++ b/src/etapi/special_notes.js @@ -1,6 +1,6 @@ const specialNotesService = require('../services/special_notes.js'); const dateNotesService = require('../services/date_notes.js'); -const eu = require('./etapi_utils.js'); +const eu = require('./etapi_utils'); const mappers = require('./mappers.js'); const getDateInvalidError = date => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`); diff --git a/src/etapi/validators.js b/src/etapi/validators.js index 44128b812..59ddc1675 100644 --- a/src/etapi/validators.js +++ b/src/etapi/validators.js @@ -1,5 +1,5 @@ const noteTypeService = require('../services/note_types.js'); -const dateUtils = require('../services/date_utils.js'); +const dateUtils = require('../services/date_utils'); function mandatory(obj) { if (obj === undefined ) { @@ -64,7 +64,7 @@ function isNoteId(obj) { return; } - const becca = require('../becca/becca.js'); + const becca = require('../becca/becca'); if (typeof obj !== 'string') { return `'${obj}' is not a valid noteId`; diff --git a/src/routes/api/attachments.js b/src/routes/api/attachments.js index 7862534a7..09bf09376 100644 --- a/src/routes/api/attachments.js +++ b/src/routes/api/attachments.js @@ -1,6 +1,6 @@ -const becca = require('../../becca/becca.js'); -const blobService = require('../../services/blob.js'); -const ValidationError = require('../../errors/validation_error.js'); +const becca = require('../../becca/becca'); +const blobService = require('../../services/blob'); +const ValidationError = require('../../errors/validation_error'); const imageService = require("../../services/image.js"); function getAttachmentBlob(req) { diff --git a/src/routes/api/attributes.js b/src/routes/api/attributes.js index 0d9b541f1..de906a225 100644 --- a/src/routes/api/attributes.js +++ b/src/routes/api/attributes.js @@ -1,11 +1,11 @@ "use strict"; -const sql = require('../../services/sql.js'); -const log = require('../../services/log.js'); +const sql = require('../../services/sql'); +const log = require('../../services/log'); const attributeService = require('../../services/attributes.js'); -const BAttribute = require('../../becca/entities/battribute.js'); -const becca = require('../../becca/becca.js'); -const ValidationError = require('../../errors/validation_error.js'); +const BAttribute = require('../../becca/entities/battribute'); +const becca = require('../../becca/becca'); +const ValidationError = require('../../errors/validation_error'); function getEffectiveNoteAttributes(req) { const note = becca.getNote(req.params.noteId); diff --git a/src/routes/api/autocomplete.js b/src/routes/api/autocomplete.js index 4fe35a564..a3113ba62 100644 --- a/src/routes/api/autocomplete.js +++ b/src/routes/api/autocomplete.js @@ -2,10 +2,10 @@ const beccaService = require('../../becca/becca_service.js'); const searchService = require('../../services/search/services/search.js'); -const log = require('../../services/log.js'); -const utils = require('../../services/utils.js'); -const cls = require('../../services/cls.js'); -const becca = require('../../becca/becca.js'); +const log = require('../../services/log'); +const utils = require('../../services/utils'); +const cls = require('../../services/cls'); +const becca = require('../../becca/becca'); function getAutocomplete(req) { const query = req.query.query.trim(); diff --git a/src/routes/api/backend_log.js b/src/routes/api/backend_log.js index 51d0cc6a6..4a07a219e 100644 --- a/src/routes/api/backend_log.js +++ b/src/routes/api/backend_log.js @@ -1,8 +1,8 @@ "use strict"; const fs = require('fs'); -const dateUtils = require('../../services/date_utils.js'); -const {LOG_DIR} = require('../../services/data_dir.js'); +const dateUtils = require('../../services/date_utils'); +const {LOG_DIR} = require('../../services/data_dir'); function getBackendLog() { const file = `${LOG_DIR}/trilium-${dateUtils.localNowDate()}.log`; diff --git a/src/routes/api/branches.js b/src/routes/api/branches.js index 17f11cd01..cbc951f1e 100644 --- a/src/routes/api/branches.js +++ b/src/routes/api/branches.js @@ -1,16 +1,16 @@ "use strict"; -const sql = require('../../services/sql.js'); -const utils = require('../../services/utils.js'); -const entityChangesService = require('../../services/entity_changes.js'); +const sql = require('../../services/sql'); +const utils = require('../../services/utils'); +const entityChangesService = require('../../services/entity_changes'); const treeService = require('../../services/tree.js'); const eraseService = require('../../services/erase.js'); -const becca = require('../../becca/becca.js'); -const TaskContext = require('../../services/task_context.js'); +const becca = require('../../becca/becca'); +const TaskContext = require('../../services/task_context'); const branchService = require('../../services/branches.js'); -const log = require('../../services/log.js'); -const ValidationError = require('../../errors/validation_error.js'); -const eventService = require("../../services/events.js"); +const log = require('../../services/log'); +const ValidationError = require('../../errors/validation_error'); +const eventService = require("../../services/events"); /** * Code in this file deals with moving and cloning branches. The relationship between note and parent note is unique diff --git a/src/routes/api/bulk_action.js b/src/routes/api/bulk_action.js index 08f59df49..f04b32d61 100644 --- a/src/routes/api/bulk_action.js +++ b/src/routes/api/bulk_action.js @@ -1,4 +1,4 @@ -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); const bulkActionService = require('../../services/bulk_actions.js'); function execute(req) { diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index 1a7ac2e81..ea4fb64c3 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -4,12 +4,12 @@ const attributeService = require('../../services/attributes.js'); const cloneService = require('../../services/cloning.js'); const noteService = require('../../services/notes.js'); const dateNoteService = require('../../services/date_notes.js'); -const dateUtils = require('../../services/date_utils.js'); +const dateUtils = require('../../services/date_utils'); const imageService = require('../../services/image.js'); const appInfo = require('../../services/app_info.js'); -const ws = require('../../services/ws.js'); -const log = require('../../services/log.js'); -const utils = require('../../services/utils.js'); +const ws = require('../../services/ws'); +const log = require('../../services/log'); +const utils = require('../../services/utils'); const path = require('path'); const htmlSanitizer = require('../../services/html_sanitizer.js'); const {formatAttrForSearch} = require('../../services/attribute_formatter.js'); diff --git a/src/routes/api/database.js b/src/routes/api/database.js index 5b6b4739d..6c8063fe1 100644 --- a/src/routes/api/database.js +++ b/src/routes/api/database.js @@ -1,7 +1,7 @@ "use strict"; -const sql = require('../../services/sql.js'); -const log = require('../../services/log.js'); +const sql = require('../../services/sql'); +const log = require('../../services/log'); const backupService = require('../../services/backup.js'); const anonymizationService = require('../../services/anonymization.js'); const consistencyChecksService = require('../../services/consistency_checks.js'); diff --git a/src/routes/api/export.js b/src/routes/api/export.js index 514fdf861..440eecb8d 100644 --- a/src/routes/api/export.js +++ b/src/routes/api/export.js @@ -3,10 +3,10 @@ const zipExportService = require('../../services/export/zip.js'); const singleExportService = require('../../services/export/single.js'); const opmlExportService = require('../../services/export/opml.js'); -const becca = require('../../becca/becca.js'); -const TaskContext = require('../../services/task_context.js'); -const log = require('../../services/log.js'); -const NotFoundError = require('../../errors/not_found_error.js'); +const becca = require('../../becca/becca'); +const TaskContext = require('../../services/task_context'); +const log = require('../../services/log'); +const NotFoundError = require('../../errors/not_found_error'); function exportBranch(req, res) { const {branchId, type, format, version, taskId} = req.params; diff --git a/src/routes/api/files.js b/src/routes/api/files.js index 72bd0ee11..5e0c391d6 100644 --- a/src/routes/api/files.js +++ b/src/routes/api/files.js @@ -1,16 +1,16 @@ "use strict"; -const protectedSessionService = require('../../services/protected_session.js'); -const utils = require('../../services/utils.js'); -const log = require('../../services/log.js'); +const protectedSessionService = require('../../services/protected_session'); +const utils = require('../../services/utils'); +const log = require('../../services/log'); const noteService = require('../../services/notes.js'); const tmp = require('tmp'); const fs = require('fs'); const { Readable } = require('stream'); const chokidar = require('chokidar'); -const ws = require('../../services/ws.js'); -const becca = require('../../becca/becca.js'); -const ValidationError = require('../../errors/validation_error.js'); +const ws = require('../../services/ws'); +const becca = require('../../becca/becca'); +const ValidationError = require('../../errors/validation_error'); function updateFile(req) { const note = becca.getNoteOrThrow(req.params.noteId); diff --git a/src/routes/api/fonts.js b/src/routes/api/fonts.js index 6e7cce87f..d21db07c8 100644 --- a/src/routes/api/fonts.js +++ b/src/routes/api/fonts.js @@ -1,4 +1,4 @@ -const optionService = require('../../services/options.js'); +const optionService = require('../../services/options'); function getFontCss(req, res) { res.setHeader('Content-Type', 'text/css'); diff --git a/src/routes/api/image.js b/src/routes/api/image.js index 14b034119..d1c702bc7 100644 --- a/src/routes/api/image.js +++ b/src/routes/api/image.js @@ -1,8 +1,8 @@ "use strict"; const imageService = require('../../services/image.js'); -const becca = require('../../becca/becca.js'); -const RESOURCE_DIR = require('../../services/resource_dir.js').RESOURCE_DIR; +const becca = require('../../becca/becca'); +const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; const fs = require('fs'); function returnImageFromNote(req, res) { diff --git a/src/routes/api/import.js b/src/routes/api/import.js index 0e27ed515..115546cae 100644 --- a/src/routes/api/import.js +++ b/src/routes/api/import.js @@ -4,13 +4,13 @@ const enexImportService = require('../../services/import/enex.js'); const opmlImportService = require('../../services/import/opml.js'); const zipImportService = require('../../services/import/zip.js'); const singleImportService = require('../../services/import/single.js'); -const cls = require('../../services/cls.js'); +const cls = require('../../services/cls'); const path = require('path'); -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); const beccaLoader = require('../../becca/becca_loader.js'); -const log = require('../../services/log.js'); -const TaskContext = require('../../services/task_context.js'); -const ValidationError = require('../../errors/validation_error.js'); +const log = require('../../services/log'); +const TaskContext = require('../../services/task_context'); +const ValidationError = require('../../errors/validation_error'); async function importNotesToBranch(req) { const {parentNoteId} = req.params; diff --git a/src/routes/api/keys.js b/src/routes/api/keys.js index 3f75f11a3..a2f2a0b37 100644 --- a/src/routes/api/keys.js +++ b/src/routes/api/keys.js @@ -1,7 +1,7 @@ "use strict"; const keyboardActions = require('../../services/keyboard_actions.js'); -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); function getKeyboardActions() { return keyboardActions.getKeyboardActions(); diff --git a/src/routes/api/login.js b/src/routes/api/login.js index 9dbf058d4..7a5ff5a04 100644 --- a/src/routes/api/login.js +++ b/src/routes/api/login.js @@ -1,16 +1,16 @@ "use strict"; -const options = require('../../services/options.js'); -const utils = require('../../services/utils.js'); -const dateUtils = require('../../services/date_utils.js'); -const instanceId = require('../../services/instance_id.js'); -const passwordEncryptionService = require('../../services/encryption/password_encryption.js'); -const protectedSessionService = require('../../services/protected_session.js'); +const options = require('../../services/options'); +const utils = require('../../services/utils'); +const dateUtils = require('../../services/date_utils'); +const instanceId = require('../../services/instance_id'); +const passwordEncryptionService = require('../../services/encryption/password_encryption'); +const protectedSessionService = require('../../services/protected_session'); const appInfo = require('../../services/app_info.js'); -const eventService = require('../../services/events.js'); +const eventService = require('../../services/events'); const sqlInit = require('../../services/sql_init.js'); -const sql = require('../../services/sql.js'); -const ws = require('../../services/ws.js'); +const sql = require('../../services/sql'); +const ws = require('../../services/ws'); const etapiTokenService = require('../../services/etapi_tokens.js'); function loginSync(req) { diff --git a/src/routes/api/note_map.js b/src/routes/api/note_map.js index 30ad771ca..2e13f6167 100644 --- a/src/routes/api/note_map.js +++ b/src/routes/api/note_map.js @@ -1,6 +1,6 @@ "use strict"; -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); const { JSDOM } = require("jsdom"); function buildDescendantCountMap(noteIdsToCount) { diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 54cd83330..467a089f7 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -3,13 +3,13 @@ const noteService = require('../../services/notes.js'); const eraseService = require('../../services/erase.js'); const treeService = require('../../services/tree.js'); -const sql = require('../../services/sql.js'); -const utils = require('../../services/utils.js'); -const log = require('../../services/log.js'); -const TaskContext = require('../../services/task_context.js'); -const becca = require('../../becca/becca.js'); -const ValidationError = require('../../errors/validation_error.js'); -const blobService = require('../../services/blob.js'); +const sql = require('../../services/sql'); +const utils = require('../../services/utils'); +const log = require('../../services/log'); +const TaskContext = require('../../services/task_context'); +const becca = require('../../becca/becca'); +const ValidationError = require('../../errors/validation_error'); +const blobService = require('../../services/blob'); function getNote(req) { return becca.getNoteOrThrow(req.params.noteId); diff --git a/src/routes/api/options.js b/src/routes/api/options.js index efd70a07e..fd24422dc 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -1,9 +1,9 @@ "use strict"; -const optionService = require('../../services/options.js'); -const log = require('../../services/log.js'); +const optionService = require('../../services/options'); +const log = require('../../services/log'); const searchService = require('../../services/search/services/search.js'); -const ValidationError = require('../../errors/validation_error.js'); +const ValidationError = require('../../errors/validation_error'); // options allowed to be updated directly in the Options dialog const ALLOWED_OPTIONS = new Set([ diff --git a/src/routes/api/other.js b/src/routes/api/other.js index ca92e15c6..a05600a56 100644 --- a/src/routes/api/other.js +++ b/src/routes/api/other.js @@ -1,4 +1,4 @@ -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); const markdownService = require('../../services/import/markdown.js'); function getIconUsage() { diff --git a/src/routes/api/password.js b/src/routes/api/password.js index a80d4151c..42bd8b0e0 100644 --- a/src/routes/api/password.js +++ b/src/routes/api/password.js @@ -1,7 +1,7 @@ "use strict"; -const passwordService = require('../../services/encryption/password.js'); -const ValidationError = require('../../errors/validation_error.js'); +const passwordService = require('../../services/encryption/password'); +const ValidationError = require('../../errors/validation_error'); function changePassword(req) { if (passwordService.isPasswordSet()) { diff --git a/src/routes/api/recent_changes.js b/src/routes/api/recent_changes.js index 567abfb3a..6fea4a105 100644 --- a/src/routes/api/recent_changes.js +++ b/src/routes/api/recent_changes.js @@ -1,9 +1,9 @@ "use strict"; -const sql = require('../../services/sql.js'); -const protectedSessionService = require('../../services/protected_session.js'); +const sql = require('../../services/sql'); +const protectedSessionService = require('../../services/protected_session'); const noteService = require('../../services/notes.js'); -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); function getRecentChanges(req) { const {ancestorNoteId} = req.params; diff --git a/src/routes/api/recent_notes.js b/src/routes/api/recent_notes.js index 2ba5c5020..40139477a 100644 --- a/src/routes/api/recent_notes.js +++ b/src/routes/api/recent_notes.js @@ -1,8 +1,8 @@ "use strict"; -const BRecentNote = require('../../becca/entities/brecent_note.js'); -const sql = require('../../services/sql.js'); -const dateUtils = require('../../services/date_utils.js'); +const BRecentNote = require('../../becca/entities/brecent_note'); +const sql = require('../../services/sql'); +const dateUtils = require('../../services/date_utils'); function addRecentNote(req) { new BRecentNote({ diff --git a/src/routes/api/relation-map.js b/src/routes/api/relation-map.js index 0c3db4e6d..280ed7f67 100644 --- a/src/routes/api/relation-map.js +++ b/src/routes/api/relation-map.js @@ -1,5 +1,5 @@ -const becca = require('../../becca/becca.js'); -const sql = require('../../services/sql.js'); +const becca = require('../../becca/becca'); +const sql = require('../../services/sql'); function getRelationMap(req) { const {relationMapNoteId, noteIds} = req.body; diff --git a/src/routes/api/revisions.js b/src/routes/api/revisions.js index 72ab1ea03..e4a843016 100644 --- a/src/routes/api/revisions.js +++ b/src/routes/api/revisions.js @@ -2,12 +2,12 @@ const beccaService = require('../../becca/becca_service.js'); const revisionService = require('../../services/revisions.js'); -const utils = require('../../services/utils.js'); -const sql = require('../../services/sql.js'); -const cls = require('../../services/cls.js'); +const utils = require('../../services/utils'); +const sql = require('../../services/sql'); +const cls = require('../../services/cls'); const path = require('path'); -const becca = require('../../becca/becca.js'); -const blobService = require('../../services/blob.js'); +const becca = require('../../becca/becca'); +const blobService = require('../../services/blob'); const eraseService = require("../../services/erase.js"); function getRevisionBlob(req) { diff --git a/src/routes/api/script.js b/src/routes/api/script.js index 833a57b9b..0dbb72f2c 100644 --- a/src/routes/api/script.js +++ b/src/routes/api/script.js @@ -2,9 +2,9 @@ const scriptService = require('../../services/script.js'); const attributeService = require('../../services/attributes.js'); -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); const syncService = require('../../services/sync.js'); -const sql = require('../../services/sql.js'); +const sql = require('../../services/sql'); // The async/await here is very confusing, because the body.script may, but may not be async. If it is async, then we // need to await it and make the complete response including metadata available in a Promise, so that the route detects diff --git a/src/routes/api/search.js b/src/routes/api/search.js index cf4887523..b98e08517 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -1,12 +1,12 @@ "use strict"; -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); const SearchContext = require('../../services/search/search_context.js'); const searchService = require('../../services/search/services/search.js'); const bulkActionService = require('../../services/bulk_actions.js'); -const cls = require('../../services/cls.js'); +const cls = require('../../services/cls'); const {formatAttrForSearch} = require('../../services/attribute_formatter.js'); -const ValidationError = require('../../errors/validation_error.js'); +const ValidationError = require('../../errors/validation_error'); function searchFromNote(req) { const note = becca.getNoteOrThrow(req.params.noteId); diff --git a/src/routes/api/sender.js b/src/routes/api/sender.js index 1e55d7a7b..000d1eecb 100644 --- a/src/routes/api/sender.js +++ b/src/routes/api/sender.js @@ -3,7 +3,7 @@ const imageType = require('image-type'); const imageService = require('../../services/image.js'); const noteService = require('../../services/notes.js'); -const {sanitizeAttributeName} = require('../../services/sanitize_attribute_name.js'); +const {sanitizeAttributeName} = require('../../services/sanitize_attribute_name'); const specialNotesService = require('../../services/special_notes.js'); function uploadImage(req) { diff --git a/src/routes/api/setup.js b/src/routes/api/setup.js index 4ed672f9d..bd328509f 100644 --- a/src/routes/api/setup.js +++ b/src/routes/api/setup.js @@ -2,7 +2,7 @@ const sqlInit = require('../../services/sql_init.js'); const setupService = require('../../services/setup.js'); -const log = require('../../services/log.js'); +const log = require('../../services/log'); const appInfo = require('../../services/app_info.js'); function getStatus() { diff --git a/src/routes/api/similar_notes.js b/src/routes/api/similar_notes.js index 781ba0efc..3dee1d0c1 100644 --- a/src/routes/api/similar_notes.js +++ b/src/routes/api/similar_notes.js @@ -1,7 +1,7 @@ "use strict"; const similarityService = require('../../becca/similarity.js'); -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); async function getSimilarNotes(req) { const noteId = req.params.noteId; diff --git a/src/routes/api/special_notes.js b/src/routes/api/special_notes.js index 5f4c7933b..9c80f507f 100644 --- a/src/routes/api/special_notes.js +++ b/src/routes/api/special_notes.js @@ -1,10 +1,10 @@ "use strict"; const dateNoteService = require('../../services/date_notes.js'); -const sql = require('../../services/sql.js'); -const cls = require('../../services/cls.js'); +const sql = require('../../services/sql'); +const cls = require('../../services/cls'); const specialNotesService = require('../../services/special_notes.js'); -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); function getInboxNote(req) { return specialNotesService.getInboxNote(req.params.date); diff --git a/src/routes/api/sql.js b/src/routes/api/sql.js index 6a9767317..4e06ed78e 100644 --- a/src/routes/api/sql.js +++ b/src/routes/api/sql.js @@ -1,7 +1,7 @@ "use strict"; -const sql = require('../../services/sql.js'); -const becca = require('../../becca/becca.js'); +const sql = require('../../services/sql'); +const becca = require('../../becca/becca'); function getSchema() { const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`); diff --git a/src/routes/api/stats.js b/src/routes/api/stats.js index d67d624d3..05d05d25c 100644 --- a/src/routes/api/stats.js +++ b/src/routes/api/stats.js @@ -1,5 +1,5 @@ -const sql = require('../../services/sql.js'); -const becca = require('../../becca/becca.js'); +const sql = require('../../services/sql'); +const becca = require('../../becca/becca'); function getNoteSize(req) { const {noteId} = req.params; diff --git a/src/routes/api/sync.js b/src/routes/api/sync.js index 44da3e52c..ab32eb671 100644 --- a/src/routes/api/sync.js +++ b/src/routes/api/sync.js @@ -2,15 +2,15 @@ const syncService = require('../../services/sync.js'); const syncUpdateService = require('../../services/sync_update.js'); -const entityChangesService = require('../../services/entity_changes.js'); -const sql = require('../../services/sql.js'); +const entityChangesService = require('../../services/entity_changes'); +const sql = require('../../services/sql'); const sqlInit = require('../../services/sql_init.js'); -const optionService = require('../../services/options.js'); +const optionService = require('../../services/options'); const contentHashService = require('../../services/content_hash.js'); -const log = require('../../services/log.js'); +const log = require('../../services/log'); const syncOptions = require('../../services/sync_options.js'); -const utils = require('../../services/utils.js'); -const ws = require('../../services/ws.js'); +const utils = require('../../services/utils'); +const ws = require('../../services/ws'); async function testSync() { try { diff --git a/src/routes/api/tree.js b/src/routes/api/tree.js index a5b4ad639..c8188068b 100644 --- a/src/routes/api/tree.js +++ b/src/routes/api/tree.js @@ -1,8 +1,8 @@ "use strict"; -const becca = require('../../becca/becca.js'); -const log = require('../../services/log.js'); -const NotFoundError = require('../../errors/not_found_error.js'); +const becca = require('../../becca/becca'); +const log = require('../../services/log'); +const NotFoundError = require('../../errors/not_found_error'); function getNotesAndBranchesAndAttributes(noteIds) { noteIds = new Set(noteIds); diff --git a/src/routes/assets.js b/src/routes/assets.js index d15201557..3f5b81013 100644 --- a/src/routes/assets.js +++ b/src/routes/assets.js @@ -1,7 +1,7 @@ const assetPath = require('../services/asset_path.js'); const path = require("path"); const express = require("express"); -const env = require('../services/env.js'); +const env = require('../services/env'); const persistentCacheStatic = (root, options) => { if (!env.isDev()) { diff --git a/src/routes/custom.js b/src/routes/custom.js index c76240976..f566902f6 100644 --- a/src/routes/custom.js +++ b/src/routes/custom.js @@ -1,9 +1,9 @@ -const log = require('../services/log.js'); +const log = require('../services/log'); const fileService = require('./api/files.js'); const scriptService = require('../services/script.js'); -const cls = require('../services/cls.js'); -const sql = require('../services/sql.js'); -const becca = require('../becca/becca.js'); +const cls = require('../services/cls'); +const sql = require('../services/sql'); +const becca = require('../becca/becca'); function handleRequest(req, res) { // express puts content after first slash into 0 index element diff --git a/src/routes/error_handlers.js b/src/routes/error_handlers.js index 1e9715ed8..4d17b0d5d 100644 --- a/src/routes/error_handlers.js +++ b/src/routes/error_handlers.js @@ -1,4 +1,4 @@ -const log = require('../services/log.js'); +const log = require('../services/log'); function register(app) { app.use((err, req, res, next) => { diff --git a/src/routes/index.js b/src/routes/index.js index d57563bad..277d58381 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,13 +1,13 @@ "use strict"; -const sql = require('../services/sql.js'); +const sql = require('../services/sql'); const attributeService = require('../services/attributes.js'); -const config = require('../services/config.js'); -const optionService = require('../services/options.js'); -const log = require('../services/log.js'); -const env = require('../services/env.js'); -const utils = require('../services/utils.js'); -const protectedSessionService = require('../services/protected_session.js'); +const config = require('../services/config'); +const optionService = require('../services/options'); +const log = require('../services/log'); +const env = require('../services/env'); +const utils = require('../services/utils'); +const protectedSessionService = require('../services/protected_session'); const packageJson = require('../../package.json'); const assetPath = require('../services/asset_path.js'); const appPath = require('../services/app_path.js'); diff --git a/src/routes/login.js b/src/routes/login.js index 6c4af5cdb..5aea1a6d5 100644 --- a/src/routes/login.js +++ b/src/routes/login.js @@ -1,13 +1,13 @@ "use strict"; -const utils = require('../services/utils.js'); -const optionService = require('../services/options.js'); -const myScryptService = require('../services/encryption/my_scrypt.js'); -const log = require('../services/log.js'); -const passwordService = require('../services/encryption/password.js'); +const utils = require('../services/utils'); +const optionService = require('../services/options'); +const myScryptService = require('../services/encryption/my_scrypt'); +const log = require('../services/log'); +const passwordService = require('../services/encryption/password'); const assetPath = require('../services/asset_path.js'); const appPath = require('../services/app_path.js'); -const ValidationError = require('../errors/validation_error.js'); +const ValidationError = require('../errors/validation_error'); function loginPage(req, res) { res.render('login', { diff --git a/src/routes/routes.js b/src/routes/routes.js index fd3cf167a..aa67f7dba 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -1,20 +1,20 @@ "use strict"; -const utils = require('../services/utils.js'); +const utils = require('../services/utils'); const multer = require('multer'); -const log = require('../services/log.js'); +const log = require('../services/log'); const express = require('express'); const router = express.Router(); const auth = require('../services/auth.js'); -const cls = require('../services/cls.js'); -const sql = require('../services/sql.js'); -const entityChangesService = require('../services/entity_changes.js'); +const cls = require('../services/cls'); +const sql = require('../services/sql'); +const entityChangesService = require('../services/entity_changes'); const csurf = require('csurf'); const { createPartialContentHandler } = require("express-partial-content"); const rateLimit = require("express-rate-limit"); -const AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity.js'); -const NotFoundError = require('../errors/not_found_error.js'); -const ValidationError = require('../errors/validation_error.js'); +const AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity'); +const NotFoundError = require('../errors/not_found_error'); +const ValidationError = require('../errors/validation_error'); // page routes const setupRoute = require('./setup.js'); @@ -31,7 +31,7 @@ const cloningApiRoute = require('./api/cloning.js'); const revisionsApiRoute = require('./api/revisions.js'); const recentChangesApiRoute = require('./api/recent_changes.js'); const optionsApiRoute = require('./api/options.js'); -const passwordApiRoute = require('./api/password.js'); +const passwordApiRoute = require('./api/password'); const syncApiRoute = require('./api/sync.js'); const loginApiRoute = require('./api/login.js'); const recentNotesRoute = require('./api/recent_notes.js'); @@ -39,7 +39,7 @@ const appInfoRoute = require('./api/app_info.js'); const exportRoute = require('./api/export.js'); const importRoute = require('./api/import.js'); const setupApiRoute = require('./api/setup.js'); -const sqlRoute = require('./api/sql.js'); +const sqlRoute = require('./api/sql'); const databaseRoute = require('./api/database.js'); const imageRoute = require('./api/image.js'); const attributesRoute = require('./api/attributes.js'); diff --git a/src/routes/session_parser.js b/src/routes/session_parser.js index 404159b62..a2e62e851 100644 --- a/src/routes/session_parser.js +++ b/src/routes/session_parser.js @@ -1,6 +1,6 @@ const session = require("express-session"); const sessionSecret = require('../services/session_secret.js'); -const dataDir = require('../services/data_dir.js'); +const dataDir = require('../services/data_dir'); const FileStore = require('session-file-store')(session); const sessionParser = session({ diff --git a/src/routes/setup.js b/src/routes/setup.js index be14c2310..fe74c68a5 100644 --- a/src/routes/setup.js +++ b/src/routes/setup.js @@ -2,7 +2,7 @@ const sqlInit = require('../services/sql_init.js'); const setupService = require('../services/setup.js'); -const utils = require('../services/utils.js'); +const utils = require('../services/utils'); const assetPath = require('../services/asset_path.js'); const appPath = require('../services/app_path.js'); diff --git a/src/services/anonymization.js b/src/services/anonymization.js index 3beb1c1d6..6a223ec4e 100644 --- a/src/services/anonymization.js +++ b/src/services/anonymization.js @@ -1,9 +1,9 @@ const BUILTIN_ATTRIBUTES = require('./builtin_attributes.js'); const fs = require("fs-extra"); -const dataDir = require('./data_dir.js'); -const dateUtils = require('./date_utils.js'); +const dataDir = require('./data_dir'); +const dateUtils = require('./date_utils'); const Database = require("better-sqlite3"); -const sql = require('./sql.js'); +const sql = require('./sql'); const path = require("path"); function getFullAnonymizationScript() { diff --git a/src/services/app_icon.js b/src/services/app_icon.js index 51e319113..bc845ab8c 100644 --- a/src/services/app_icon.js +++ b/src/services/app_icon.js @@ -1,12 +1,12 @@ "use strict"; const path = require('path'); -const {ELECTRON_APP_ROOT_DIR} = require('./resource_dir.js'); -const log = require('./log.js'); +const {ELECTRON_APP_ROOT_DIR} = require('./resource_dir'); +const log = require('./log'); const os = require('os'); const fs = require('fs'); -const config = require('./config.js'); -const utils = require('./utils.js'); +const config = require('./config'); +const utils = require('./utils'); const template = `[Desktop Entry] Type=Application diff --git a/src/services/app_info.js b/src/services/app_info.js index 1d9d7f965..1d419a4bf 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -2,7 +2,7 @@ const build = require('./build.js'); const packageJson = require('../../package.json'); -const {TRILIUM_DATA_DIR} = require('./data_dir.js'); +const {TRILIUM_DATA_DIR} = require('./data_dir'); const APP_DB_VERSION = 228; const SYNC_VERSION = 32; diff --git a/src/services/app_path.js b/src/services/app_path.js index ab449495d..9f28c9fa2 100644 --- a/src/services/app_path.js +++ b/src/services/app_path.js @@ -1,5 +1,5 @@ const assetPath = require('./asset_path.js'); -const env = require('./env.js'); +const env = require('./env'); module.exports = env.isDev() ? assetPath + "/app" diff --git a/src/services/attributes.js b/src/services/attributes.js index 632a6e09a..685225ae7 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -1,9 +1,9 @@ "use strict"; const searchService = require('./search/services/search.js'); -const sql = require('./sql.js'); -const becca = require('../becca/becca.js'); -const BAttribute = require('../becca/entities/battribute.js'); +const sql = require('./sql'); +const becca = require('../becca/becca'); +const BAttribute = require('../becca/entities/battribute'); const {formatAttrForSearch} = require('./attribute_formatter.js'); const BUILTIN_ATTRIBUTES = require('./builtin_attributes.js'); diff --git a/src/services/auth.js b/src/services/auth.js index adb565134..d212f6676 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -1,12 +1,12 @@ "use strict"; const etapiTokenService = require('./etapi_tokens.js'); -const log = require('./log.js'); +const log = require('./log'); const sqlInit = require('./sql_init.js'); -const utils = require('./utils.js'); -const passwordEncryptionService = require('./encryption/password_encryption.js'); -const config = require('./config.js'); -const passwordService = require('./encryption/password.js'); +const utils = require('./utils'); +const passwordEncryptionService = require('./encryption/password_encryption'); +const config = require('./config'); +const passwordService = require('./encryption/password'); const noAuthentication = config.General && config.General.noAuthentication === true; diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js index 5abf704ed..fc8e80aef 100644 --- a/src/services/backend_script_api.js +++ b/src/services/backend_script_api.js @@ -1,11 +1,11 @@ -const log = require('./log.js'); +const log = require('./log'); const noteService = require('./notes.js'); -const sql = require('./sql.js'); -const utils = require('./utils.js'); +const sql = require('./sql'); +const utils = require('./utils'); const attributeService = require('./attributes.js'); const dateNoteService = require('./date_notes.js'); const treeService = require('./tree.js'); -const config = require('./config.js'); +const config = require('./config'); const axios = require('axios'); const dayjs = require('dayjs'); const xml2js = require('xml2js'); @@ -13,15 +13,15 @@ const cloningService = require('./cloning.js'); const appInfo = require('./app_info.js'); const searchService = require('./search/services/search.js'); const SearchContext = require('./search/search_context.js'); -const becca = require('../becca/becca.js'); -const ws = require('./ws.js'); +const becca = require('../becca/becca'); +const ws = require('./ws'); const SpacedUpdate = require('./spaced_update.js'); const specialNotesService = require('./special_notes.js'); const branchService = require('./branches.js'); const exportService = require('./export/zip.js'); -const syncMutex = require('./sync_mutex.js'); +const syncMutex = require('./sync_mutex'); const backupService = require('./backup.js'); -const optionsService = require('./options.js'); +const optionsService = require('./options'); /** diff --git a/src/services/backup.js b/src/services/backup.js index ef36d91cf..7fa41eb5e 100644 --- a/src/services/backup.js +++ b/src/services/backup.js @@ -1,13 +1,13 @@ "use strict"; -const dateUtils = require('./date_utils.js'); -const optionService = require('./options.js'); +const dateUtils = require('./date_utils'); +const optionService = require('./options'); const fs = require('fs-extra'); -const dataDir = require('./data_dir.js'); -const log = require('./log.js'); -const syncMutexService = require('./sync_mutex.js'); -const cls = require('./cls.js'); -const sql = require('./sql.js'); +const dataDir = require('./data_dir'); +const log = require('./log'); +const syncMutexService = require('./sync_mutex'); +const cls = require('./cls'); +const sql = require('./sql'); const path = require('path'); function getExistingBackups() { diff --git a/src/services/blob-interface.ts b/src/services/blob-interface.ts new file mode 100644 index 000000000..8bfcf1322 --- /dev/null +++ b/src/services/blob-interface.ts @@ -0,0 +1,5 @@ +export interface Blob { + blobId: string; + content: string | Buffer; + utcDateModified: string; +} \ No newline at end of file diff --git a/src/services/blob.js b/src/services/blob.ts similarity index 68% rename from src/services/blob.js rename to src/services/blob.ts index 73f972a36..fac1adfad 100644 --- a/src/services/blob.js +++ b/src/services/blob.ts @@ -1,9 +1,10 @@ -const becca = require('../becca/becca.js'); -const NotFoundError = require('../errors/not_found_error.js'); -const protectedSessionService = require('./protected_session.js'); -const utils = require('./utils.js'); +import becca = require('../becca/becca'); +import NotFoundError = require('../errors/not_found_error'); +import protectedSessionService = require('./protected_session'); +import utils = require('./utils'); +import type { Blob } from "./blob-interface"; -function getBlobPojo(entityName, entityId) { +function getBlobPojo(entityName: string, entityId: string) { const entity = becca.getEntity(entityName, entityId); if (!entity) { throw new NotFoundError(`Entity ${entityName} '${entityId}' was not found.`); @@ -19,13 +20,13 @@ function getBlobPojo(entityName, entityId) { if (!entity.hasStringContent()) { pojo.content = null; } else { - pojo.content = processContent(pojo.content, entity.isProtected, true); + pojo.content = processContent(pojo.content, !!entity.isProtected, true); } return pojo; } -function processContent(content, isProtected, isStringContent) { +function processContent(content: Buffer | string | null, isProtected: boolean, isStringContent: boolean) { if (isProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { content = content === null ? null : protectedSessionService.decrypt(content); @@ -48,11 +49,11 @@ function processContent(content, isProtected, isStringContent) { } } -function calculateContentHash({blobId, content}) { +function calculateContentHash({blobId, content}: Blob) { return utils.hash(`${blobId}|${content.toString()}`); } -module.exports = { +export = { getBlobPojo, processContent, calculateContentHash diff --git a/src/services/branches.js b/src/services/branches.js index 644bde80e..ec0632745 100644 --- a/src/services/branches.js +++ b/src/services/branches.js @@ -1,5 +1,5 @@ const treeService = require('./tree.js'); -const sql = require('./sql.js'); +const sql = require('./sql'); function moveBranchToNote(branchToMove, targetParentNoteId) { if (branchToMove.parentNoteId === targetParentNoteId) { diff --git a/src/services/bulk_actions.js b/src/services/bulk_actions.js index 351370578..3a274c688 100644 --- a/src/services/bulk_actions.js +++ b/src/services/bulk_actions.js @@ -1,9 +1,9 @@ -const log = require('./log.js'); +const log = require('./log'); const revisionService = require('./revisions.js'); -const becca = require('../becca/becca.js'); +const becca = require('../becca/becca'); const cloningService = require('./cloning.js'); const branchService = require('./branches.js'); -const utils = require('./utils.js'); +const utils = require('./utils'); const eraseService = require("./erase.js"); const ACTION_HANDLERS = { diff --git a/src/services/cloning.js b/src/services/cloning.js index 4baae5de7..a42df4ba8 100644 --- a/src/services/cloning.js +++ b/src/services/cloning.js @@ -1,11 +1,11 @@ "use strict"; -const sql = require('./sql.js'); -const eventChangesService = require('./entity_changes.js'); +const sql = require('./sql'); +const eventChangesService = require('./entity_changes'); const treeService = require('./tree.js'); -const BBranch = require('../becca/entities/bbranch.js'); -const becca = require('../becca/becca.js'); -const log = require('./log.js'); +const BBranch = require('../becca/entities/bbranch'); +const becca = require('../becca/becca'); +const log = require('./log'); function cloneNoteToParentNote(noteId, parentNoteId, prefix = null) { if (!(noteId in becca.notes) || !(parentNoteId in becca.notes)) { diff --git a/src/services/cls.js b/src/services/cls.ts similarity index 81% rename from src/services/cls.js rename to src/services/cls.ts index 8e2c2870c..d119c8a93 100644 --- a/src/services/cls.js +++ b/src/services/cls.ts @@ -1,26 +1,29 @@ -const clsHooked = require('cls-hooked'); +import clsHooked = require('cls-hooked'); +import { EntityChange } from './entity_changes_interface'; const namespace = clsHooked.createNamespace("trilium"); -function init(callback) { +type Callback = (...args: any[]) => any; + +function init(callback: Callback) { return namespace.runAndReturn(callback); } -function wrap(callback) { +function wrap(callback: Callback) { return () => { try { init(callback); } - catch (e) { + catch (e: any) { console.log(`Error occurred: ${e.message}: ${e.stack}`); } } } -function get(key) { +function get(key: string) { return namespace.get(key); } -function set(key, value) { +function set(key: string, value: any) { namespace.set(key, value); } @@ -48,7 +51,7 @@ function isEntityEventsDisabled() { return !!namespace.get('disableEntityEvents'); } -function setMigrationRunning(running) { +function setMigrationRunning(running: boolean) { namespace.set('migrationRunning', !!running); } @@ -56,7 +59,7 @@ function isMigrationRunning() { return !!namespace.get('migrationRunning'); } -function disableSlowQueryLogging(disable) { +function disableSlowQueryLogging(disable: boolean) { namespace.set('disableSlowQueryLogging', disable); } @@ -72,7 +75,7 @@ function getAndClearEntityChangeIds() { return entityChangeIds; } -function putEntityChange(entityChange) { +function putEntityChange(entityChange: EntityChange) { if (namespace.get('ignoreEntityChangeIds')) { return; } @@ -93,7 +96,7 @@ function ignoreEntityChangeIds() { namespace.set('ignoreEntityChangeIds', true); } -module.exports = { +export = { init, wrap, get, diff --git a/src/services/config.js b/src/services/config.ts similarity index 65% rename from src/services/config.js rename to src/services/config.ts index 2968f1248..f0437a2d1 100644 --- a/src/services/config.js +++ b/src/services/config.ts @@ -1,10 +1,10 @@ "use strict"; -const ini = require('ini'); -const fs = require('fs'); -const dataDir = require('./data_dir.js'); -const path = require('path'); -const resourceDir = require('./resource_dir.js'); +import ini = require('ini'); +import fs = require('fs'); +import dataDir = require('./data_dir'); +import path = require('path'); +import resourceDir = require('./resource_dir'); const configSampleFilePath = path.resolve(resourceDir.RESOURCE_DIR, "config-sample.ini"); @@ -16,4 +16,4 @@ if (!fs.existsSync(dataDir.CONFIG_INI_PATH)) { const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8')); -module.exports = config; +export = config; diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index 63f934d4f..5d9ef5104 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -1,19 +1,19 @@ "use strict"; -const sql = require('./sql.js'); +const sql = require('./sql'); const sqlInit = require('./sql_init.js'); -const log = require('./log.js'); -const ws = require('./ws.js'); -const syncMutexService = require('./sync_mutex.js'); -const cls = require('./cls.js'); -const entityChangesService = require('./entity_changes.js'); -const optionsService = require('./options.js'); -const BBranch = require('../becca/entities/bbranch.js'); +const log = require('./log'); +const ws = require('./ws'); +const syncMutexService = require('./sync_mutex'); +const cls = require('./cls'); +const entityChangesService = require('./entity_changes'); +const optionsService = require('./options'); +const BBranch = require('../becca/entities/bbranch'); const revisionService = require('./revisions.js'); -const becca = require('../becca/becca.js'); -const utils = require('../services/utils.js'); +const becca = require('../becca/becca'); +const utils = require('../services/utils'); const eraseService = require('../services/erase.js'); -const {sanitizeAttributeName} = require('./sanitize_attribute_name.js'); +const {sanitizeAttributeName} = require('./sanitize_attribute_name'); const noteTypes = require('../services/note_types.js').getNoteTypeNames(); class ConsistencyChecks { diff --git a/src/services/content_hash.js b/src/services/content_hash.js index c9ca7fef6..a42c16503 100644 --- a/src/services/content_hash.js +++ b/src/services/content_hash.js @@ -1,8 +1,8 @@ "use strict"; -const sql = require('./sql.js'); -const utils = require('./utils.js'); -const log = require('./log.js'); +const sql = require('./sql'); +const utils = require('./utils'); +const log = require('./log'); const eraseService = require('./erase.js'); function getEntityHashes() { diff --git a/src/services/data_dir.js b/src/services/data_dir.ts similarity index 93% rename from src/services/data_dir.js rename to src/services/data_dir.ts index 7971eabf4..1b267850a 100644 --- a/src/services/data_dir.js +++ b/src/services/data_dir.ts @@ -8,14 +8,14 @@ * - as a fallback if the previous step fails, we'll use home dir */ -const os = require('os'); -const fs = require('fs'); -const path = require('path'); +import os = require('os'); +import fs = require('fs'); +import path = require('path'); function getAppDataDir() { let appDataDir = os.homedir(); // fallback if OS is not recognized - if (os.platform() === 'win32') { + if (os.platform() === 'win32' && process.env.APPDATA) { appDataDir = process.env.APPDATA; } else if (os.platform() === 'linux') { @@ -68,7 +68,7 @@ const LOG_DIR = process.env.TRILIUM_LOG_DIR || `${DIR_SEP}log`; const ANONYMIZED_DB_DIR = process.env.TRILIUM_ANONYMIZED_DB_DIR || `${DIR_SEP}anonymized-db`; const CONFIG_INI_PATH = process.env.TRILIUM_CONFIG_INI_PATH || `${DIR_SEP}config.ini`; -module.exports = { +export = { TRILIUM_DATA_DIR, DOCUMENT_PATH, BACKUP_DIR, diff --git a/src/services/date_notes.js b/src/services/date_notes.js index 69e2a06a0..5d74b2f47 100644 --- a/src/services/date_notes.js +++ b/src/services/date_notes.js @@ -2,9 +2,9 @@ const noteService = require('./notes.js'); const attributeService = require('./attributes.js'); -const dateUtils = require('./date_utils.js'); -const sql = require('./sql.js'); -const protectedSessionService = require('./protected_session.js'); +const dateUtils = require('./date_utils'); +const sql = require('./sql'); +const protectedSessionService = require('./protected_session'); const searchService = require('../services/search/services/search.js'); const SearchContext = require('../services/search/search_context.js'); const hoistedNoteService = require('./hoisted_note.js'); diff --git a/src/services/date_utils.js b/src/services/date_utils.ts similarity index 88% rename from src/services/date_utils.js rename to src/services/date_utils.ts index 4eb47bdb7..31c58c2e4 100644 --- a/src/services/date_utils.js +++ b/src/services/date_utils.ts @@ -1,5 +1,5 @@ -const dayjs = require('dayjs'); -const cls = require('./cls.js'); +import dayjs = require('dayjs'); +import cls = require('./cls'); const LOCAL_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSSZZ'; const UTC_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ssZ'; @@ -29,15 +29,15 @@ function localNowDate() { } } -function pad(num) { +function pad(num: number) { return num <= 9 ? `0${num}` : `${num}`; } -function utcDateStr(date) { +function utcDateStr(date: Date) { return date.toISOString().split('T')[0]; } -function utcDateTimeStr(date) { +function utcDateTimeStr(date: Date) { return date.toISOString().replace('T', ' '); } @@ -45,16 +45,16 @@ function utcDateTimeStr(date) { * @param str - needs to be in the ISO 8601 format "YYYY-MM-DDTHH:MM:SS.sssZ" format as outputted by dateStr(). * also is assumed to be GMT time (as indicated by the "Z" at the end), *not* local time */ -function parseDateTime(str) { +function parseDateTime(str: string) { try { return new Date(Date.parse(str)); } - catch (e) { + catch (e: any) { throw new Error(`Can't parse date from '${str}': ${e.stack}`); } } -function parseLocalDate(str) { +function parseLocalDate(str: string) { const datePart = str.substr(0, 10); // not specifying the timezone and specifying the time means Date.parse() will use the local timezone @@ -65,7 +65,7 @@ function getDateTimeForFile() { return new Date().toISOString().substr(0, 19).replace(/:/g, ''); } -function validateLocalDateTime(str) { +function validateLocalDateTime(str: string) { if (!str) { return; } @@ -80,7 +80,7 @@ function validateLocalDateTime(str) { } } -function validateUtcDateTime(str) { +function validateUtcDateTime(str: string) { if (!str) { return; } @@ -95,7 +95,7 @@ function validateUtcDateTime(str) { } } -module.exports = { +export = { utcNowDateTime, localNowDateTime, localNowDate, diff --git a/src/services/encryption/data_encryption.js b/src/services/encryption/data_encryption.ts similarity index 85% rename from src/services/encryption/data_encryption.js rename to src/services/encryption/data_encryption.ts index 6b81686ed..b82a0e8c6 100644 --- a/src/services/encryption/data_encryption.js +++ b/src/services/encryption/data_encryption.ts @@ -1,9 +1,9 @@ "use strict"; -const crypto = require('crypto'); -const log = require('../log.js'); +import crypto = require('crypto'); +import log = require('../log'); -function arraysIdentical(a, b) { +function arraysIdentical(a: any[] | Buffer, b: any[] | Buffer) { let i = a.length; if (i !== b.length) return false; while (i--) { @@ -12,12 +12,12 @@ function arraysIdentical(a, b) { return true; } -function shaArray(content) { +function shaArray(content: crypto.BinaryLike) { // we use this as a simple checksum and don't rely on its security, so SHA-1 is good enough return crypto.createHash('sha1').update(content).digest(); } -function pad(data) { +function pad(data: Buffer): Buffer { if (data.length > 16) { data = data.slice(0, 16); } @@ -30,7 +30,7 @@ function pad(data) { return Buffer.from(data); } -function encrypt(key, plainText) { +function encrypt(key: Buffer, plainText: Buffer | string) { if (!key) { throw new Error("No data key!"); } @@ -51,10 +51,7 @@ function encrypt(key, plainText) { return encryptedDataWithIv.toString('base64'); } -/** - * @returns {Buffer|false|null} - */ -function decrypt(key, cipherText) { +function decrypt(key: Buffer, cipherText: string | Buffer): Buffer | false | null { if (cipherText === null) { return null; } @@ -88,12 +85,12 @@ function decrypt(key, cipherText) { return payload; } - catch (e) { + catch (e: any) { // recovery from https://github.com/zadam/trilium/issues/510 if (e.message?.includes("WRONG_FINAL_BLOCK_LENGTH") || e.message?.includes("wrong final block length")) { log.info("Caught WRONG_FINAL_BLOCK_LENGTH, returning cipherText instead"); - return cipherText; + return Buffer.from(cipherText); } else { throw e; @@ -101,7 +98,7 @@ function decrypt(key, cipherText) { } } -function decryptString(dataKey, cipherText) { +function decryptString(dataKey: Buffer, cipherText: string) { const buffer = decrypt(dataKey, cipherText); if (buffer === null) { @@ -115,7 +112,7 @@ function decryptString(dataKey, cipherText) { return buffer.toString('utf-8'); } -module.exports = { +export = { encrypt, decrypt, decryptString diff --git a/src/services/encryption/my_scrypt.js b/src/services/encryption/my_scrypt.ts similarity index 58% rename from src/services/encryption/my_scrypt.js rename to src/services/encryption/my_scrypt.ts index bc01cdde7..c80632bc0 100644 --- a/src/services/encryption/my_scrypt.js +++ b/src/services/encryption/my_scrypt.ts @@ -1,28 +1,28 @@ "use strict"; -const optionService = require('../options.js'); -const crypto = require('crypto'); +import optionService = require('../options'); +import crypto = require('crypto'); -function getVerificationHash(password) { +function getVerificationHash(password: crypto.BinaryLike) { const salt = optionService.getOption('passwordVerificationSalt'); return getScryptHash(password, salt); } -function getPasswordDerivedKey(password) { +function getPasswordDerivedKey(password: crypto.BinaryLike) { const salt = optionService.getOption('passwordDerivedKeySalt'); return getScryptHash(password, salt); } -function getScryptHash(password, salt) { +function getScryptHash(password: crypto.BinaryLike, salt: crypto.BinaryLike) { const hashed = crypto.scryptSync(password, salt, 32, {N: 16384, r:8, p:1}); return hashed; } -module.exports = { +export = { getVerificationHash, getPasswordDerivedKey }; diff --git a/src/services/encryption/password.js b/src/services/encryption/password.ts similarity index 78% rename from src/services/encryption/password.js rename to src/services/encryption/password.ts index 563e86c02..c14e27700 100644 --- a/src/services/encryption/password.js +++ b/src/services/encryption/password.ts @@ -1,16 +1,16 @@ "use strict"; -const sql = require('../sql.js'); -const optionService = require('../options.js'); -const myScryptService = require('./my_scrypt.js'); -const utils = require('../utils.js'); -const passwordEncryptionService = require('./password_encryption.js'); +import sql = require('../sql'); +import optionService = require('../options'); +import myScryptService = require('./my_scrypt'); +import utils = require('../utils'); +import passwordEncryptionService = require('./password_encryption'); function isPasswordSet() { return !!sql.getValue("SELECT value FROM options WHERE name = 'passwordVerificationHash'"); } -function changePassword(currentPassword, newPassword) { +function changePassword(currentPassword: string, newPassword: string) { if (!isPasswordSet()) { throw new Error("Password has not been set yet, so it cannot be changed. Use 'setPassword' instead."); } @@ -29,8 +29,11 @@ function changePassword(currentPassword, newPassword) { optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32)); const newPasswordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(newPassword)); - - passwordEncryptionService.setDataKey(newPassword, decryptedDataKey); + + if (decryptedDataKey) { + // TODO: what should happen if the decrypted data key is null? + passwordEncryptionService.setDataKey(newPassword, decryptedDataKey); + } optionService.setOption('passwordVerificationHash', newPasswordVerificationKey); }); @@ -40,7 +43,7 @@ function changePassword(currentPassword, newPassword) { }; } -function setPassword(password) { +function setPassword(password: string) { if (isPasswordSet()) { throw new Error("Password is set already. Either change it or perform 'reset password' first."); } @@ -48,13 +51,13 @@ function setPassword(password) { optionService.createOption('passwordVerificationSalt', utils.randomSecureToken(32), true); optionService.createOption('passwordDerivedKeySalt', utils.randomSecureToken(32), true); - const passwordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(password), true); + const passwordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(password)); optionService.createOption('passwordVerificationHash', passwordVerificationKey, true); // passwordEncryptionService expects these options to already exist optionService.createOption('encryptedDataKey', '', true); - passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true); + passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16)); return { success: true diff --git a/src/services/encryption/password_encryption.js b/src/services/encryption/password_encryption.ts similarity index 71% rename from src/services/encryption/password_encryption.js rename to src/services/encryption/password_encryption.ts index 336be8f2f..36420d03d 100644 --- a/src/services/encryption/password_encryption.js +++ b/src/services/encryption/password_encryption.ts @@ -1,9 +1,9 @@ -const optionService = require('../options.js'); -const myScryptService = require('./my_scrypt.js'); -const utils = require('../utils.js'); -const dataEncryptionService = require('./data_encryption.js'); +import optionService = require('../options'); +import myScryptService = require('./my_scrypt'); +import utils = require('../utils'); +import dataEncryptionService = require('./data_encryption'); -function verifyPassword(password) { +function verifyPassword(password: string) { const givenPasswordHash = utils.toBase64(myScryptService.getVerificationHash(password)); const dbPasswordHash = optionService.getOptionOrNull('passwordVerificationHash'); @@ -15,7 +15,7 @@ function verifyPassword(password) { return givenPasswordHash === dbPasswordHash; } -function setDataKey(password, plainTextDataKey) { +function setDataKey(password: string, plainTextDataKey: string | Buffer) { const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password); const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, plainTextDataKey); @@ -23,8 +23,7 @@ function setDataKey(password, plainTextDataKey) { optionService.setOption('encryptedDataKey', newEncryptedDataKey); } -/** @return {Buffer} */ -function getDataKey(password) { +function getDataKey(password: string) { const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password); const encryptedDataKey = optionService.getOption('encryptedDataKey'); @@ -34,7 +33,7 @@ function getDataKey(password) { return decryptedDataKey; } -module.exports = { +export = { verifyPassword, getDataKey, setDataKey diff --git a/src/services/entity_changes.js b/src/services/entity_changes.ts similarity index 74% rename from src/services/entity_changes.js rename to src/services/entity_changes.ts index 0e0a8ddb3..bc75432b0 100644 --- a/src/services/entity_changes.js +++ b/src/services/entity_changes.ts @@ -1,27 +1,29 @@ -const sql = require('./sql.js'); -const dateUtils = require('./date_utils.js'); -const log = require('./log.js'); -const cls = require('./cls.js'); -const utils = require('./utils.js'); -const instanceId = require('./instance_id.js'); -const becca = require('../becca/becca.js'); -const blobService = require('../services/blob.js'); +import sql = require('./sql'); +import dateUtils = require('./date_utils'); +import log = require('./log'); +import cls = require('./cls'); +import utils = require('./utils'); +import instanceId = require('./instance_id'); +import becca = require('../becca/becca'); +import blobService = require('../services/blob'); +import { EntityChange } from './entity_changes_interface'; +import type { Blob } from "./blob-interface"; let maxEntityChangeId = 0; -function putEntityChangeWithInstanceId(origEntityChange, instanceId) { +function putEntityChangeWithInstanceId(origEntityChange: EntityChange, instanceId: string) { const ec = {...origEntityChange, instanceId}; putEntityChange(ec); } -function putEntityChangeWithForcedChange(origEntityChange) { +function putEntityChangeWithForcedChange(origEntityChange: EntityChange) { const ec = {...origEntityChange, changeId: null}; putEntityChange(ec); } -function putEntityChange(origEntityChange) { +function putEntityChange(origEntityChange: EntityChange) { const ec = {...origEntityChange}; delete ec.id; @@ -36,12 +38,14 @@ function putEntityChange(origEntityChange) { ec.isErased = ec.isErased ? 1 : 0; ec.id = sql.replace("entity_changes", ec); - maxEntityChangeId = Math.max(maxEntityChangeId, ec.id); + if (ec.id) { + maxEntityChangeId = Math.max(maxEntityChangeId, ec.id); + } cls.putEntityChange(ec); } -function putNoteReorderingEntityChange(parentNoteId, componentId) { +function putNoteReorderingEntityChange(parentNoteId: string, componentId: string) { putEntityChange({ entityName: "note_reordering", entityId: parentNoteId, @@ -53,7 +57,7 @@ function putNoteReorderingEntityChange(parentNoteId, componentId) { instanceId }); - const eventService = require('./events.js'); + const eventService = require('./events'); eventService.emit(eventService.ENTITY_CHANGED, { entityName: 'note_reordering', @@ -61,7 +65,7 @@ function putNoteReorderingEntityChange(parentNoteId, componentId) { }); } -function putEntityChangeForOtherInstances(ec) { +function putEntityChangeForOtherInstances(ec: EntityChange) { putEntityChange({ ...ec, changeId: null, @@ -69,8 +73,8 @@ function putEntityChangeForOtherInstances(ec) { }); } -function addEntityChangesForSector(entityName, sector) { - const entityChanges = sql.getRows(`SELECT * FROM entity_changes WHERE entityName = ? AND SUBSTR(entityId, 1, 1) = ?`, [entityName, sector]); +function addEntityChangesForSector(entityName: string, sector: string) { + const entityChanges = sql.getRows(`SELECT * FROM entity_changes WHERE entityName = ? AND SUBSTR(entityId, 1, 1) = ?`, [entityName, sector]); let entitiesInserted = entityChanges.length; @@ -89,9 +93,9 @@ function addEntityChangesForSector(entityName, sector) { log.info(`Added sector ${sector} of '${entityName}' (${entitiesInserted} entities) to the sync queue.`); } -function addEntityChangesForDependingEntity(sector, tableName, primaryKeyColumn) { +function addEntityChangesForDependingEntity(sector: string, tableName: string, primaryKeyColumn: string) { // problem in blobs might be caused by problem in entity referencing the blob - const dependingEntityChanges = sql.getRows(` + const dependingEntityChanges = sql.getRows(` SELECT dep_change.* FROM entity_changes orig_sector JOIN ${tableName} ON ${tableName}.blobId = orig_sector.entityId @@ -105,7 +109,7 @@ function addEntityChangesForDependingEntity(sector, tableName, primaryKeyColumn) return dependingEntityChanges.length; } -function cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey) { +function cleanupEntityChangesForMissingEntities(entityName: string, entityPrimaryKey: string) { sql.execute(` DELETE FROM entity_changes @@ -115,11 +119,11 @@ function cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey) { AND entityId NOT IN (SELECT ${entityPrimaryKey} FROM ${entityName})`); } -function fillEntityChanges(entityName, entityPrimaryKey, condition = '') { +function fillEntityChanges(entityName: string, entityPrimaryKey: string, condition = '') { cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey); sql.transactional(() => { - const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName} ${condition}`); + const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName} ${condition}`); let createdCount = 0; @@ -133,14 +137,14 @@ function fillEntityChanges(entityName, entityPrimaryKey, condition = '') { createdCount++; - const ec = { + const ec: Partial = { entityName, entityId, isErased: false }; if (entityName === 'blobs') { - const blob = sql.getRow("SELECT blobId, content, utcDateModified FROM blobs WHERE blobId = ?", [entityId]); + const blob = sql.getRow("SELECT blobId, content, utcDateModified FROM blobs WHERE blobId = ?", [entityId]); ec.hash = blobService.calculateContentHash(blob); ec.utcDateChanged = blob.utcDateModified; ec.isSynced = true; // blobs are always synced @@ -161,7 +165,7 @@ function fillEntityChanges(entityName, entityPrimaryKey, condition = '') { } } - putEntityChange(ec); + putEntityChange(ec as EntityChange); } if (createdCount > 0) { @@ -186,10 +190,10 @@ function fillAllEntityChanges() { } function recalculateMaxEntityChangeId() { - maxEntityChangeId = sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes"); + maxEntityChangeId = sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes"); } -module.exports = { +export = { putNoteReorderingEntityChange, putEntityChangeForOtherInstances, putEntityChangeWithForcedChange, diff --git a/src/services/entity_changes_interface.ts b/src/services/entity_changes_interface.ts new file mode 100644 index 000000000..745a8a8fa --- /dev/null +++ b/src/services/entity_changes_interface.ts @@ -0,0 +1,15 @@ +export interface EntityChange { + id?: number | null; + noteId?: string; + entityName: string; + entityId: string; + entity?: any; + positions?: Record; + hash: string; + utcDateChanged?: string; + isSynced: boolean | 1 | 0; + isErased: boolean | 1 | 0; + componentId?: string | null; + changeId?: string | null; + instanceId?: string | null; +} \ No newline at end of file diff --git a/src/services/env.js b/src/services/env.js deleted file mode 100644 index e7fa6caf8..000000000 --- a/src/services/env.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - isDev: function () { - return !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === 'dev'); - } -}; \ No newline at end of file diff --git a/src/services/env.ts b/src/services/env.ts new file mode 100644 index 000000000..23c94ee0f --- /dev/null +++ b/src/services/env.ts @@ -0,0 +1,7 @@ +function isDev() { + return !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === 'dev'); +} + +export = { + isDev +}; \ No newline at end of file diff --git a/src/services/erase.js b/src/services/erase.js index da65c3ebb..7ffb30ebc 100644 --- a/src/services/erase.js +++ b/src/services/erase.js @@ -1,11 +1,11 @@ -const sql = require("./sql.js"); +const sql = require("./sql"); const revisionService = require("./revisions.js"); -const log = require("./log.js"); -const entityChangesService = require("./entity_changes.js"); -const optionService = require("./options.js"); -const dateUtils = require("./date_utils.js"); +const log = require("./log"); +const entityChangesService = require("./entity_changes"); +const optionService = require("./options"); +const dateUtils = require("./date_utils"); const sqlInit = require("./sql_init.js"); -const cls = require("./cls.js"); +const cls = require("./cls"); function eraseNotes(noteIdsToErase) { if (noteIdsToErase.length === 0) { diff --git a/src/services/etapi_tokens.js b/src/services/etapi_tokens.js index 4033b8016..1c4e0338e 100644 --- a/src/services/etapi_tokens.js +++ b/src/services/etapi_tokens.js @@ -1,6 +1,6 @@ -const becca = require('../becca/becca.js'); -const utils = require('./utils.js'); -const BEtapiToken = require('../becca/entities/betapi_token.js'); +const becca = require('../becca/becca'); +const utils = require('./utils'); +const BEtapiToken = require('../becca/entities/betapi_token'); const crypto = require("crypto"); function getTokens() { diff --git a/src/services/events.js b/src/services/events.ts similarity index 82% rename from src/services/events.js rename to src/services/events.ts index acb409107..fffcc3982 100644 --- a/src/services/events.js +++ b/src/services/events.ts @@ -1,4 +1,4 @@ -const log = require('./log.js'); +const log = require('./log'); const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED"; const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION"; @@ -11,13 +11,16 @@ const ENTITY_DELETE_SYNCED = "ENTITY_DELETE_SYNCED"; const CHILD_NOTE_CREATED = "CHILD_NOTE_CREATED"; const NOTE_CONTENT_CHANGE = "NOTE_CONTENT_CHANGED"; -const eventListeners = {}; +type EventType = string | string[]; +type EventListener = (data: any) => void; + +const eventListeners: Record = {}; /** * @param {string|string[]}eventTypes - can be either single event or an array of events * @param listener */ -function subscribe(eventTypes, listener) { +function subscribe(eventTypes: EventType, listener: EventListener) { if (!Array.isArray(eventTypes)) { eventTypes = [ eventTypes ]; } @@ -28,7 +31,7 @@ function subscribe(eventTypes, listener) { } } -function subscribeBeccaLoader(eventTypes, listener) { +function subscribeBeccaLoader(eventTypes: EventType, listener: EventListener) { if (!Array.isArray(eventTypes)) { eventTypes = [ eventTypes ]; } @@ -41,7 +44,7 @@ function subscribeBeccaLoader(eventTypes, listener) { } } -function emit(eventType, data) { +function emit(eventType: string, data: any) { const listeners = eventListeners[eventType]; if (listeners) { @@ -49,7 +52,7 @@ function emit(eventType, data) { try { listener(data); } - catch (e) { + catch (e: any) { log.error(`Listener threw error: ${e.message}, stack: ${e.stack}`); // we won't stop execution because of listener } @@ -57,7 +60,7 @@ function emit(eventType, data) { } } -module.exports = { +export = { subscribe, subscribeBeccaLoader, emit, diff --git a/src/services/export/opml.js b/src/services/export/opml.js index 976fb5b24..2faa6d513 100644 --- a/src/services/export/opml.js +++ b/src/services/export/opml.js @@ -1,7 +1,7 @@ "use strict"; -const utils = require('../utils.js'); -const becca = require('../../becca/becca.js'); +const utils = require('../utils'); +const becca = require('../../becca/becca'); function exportToOpml(taskContext, branch, version, res) { if (!['1.0', '2.0'].includes(version)) { diff --git a/src/services/export/single.js b/src/services/export/single.js index a3bc03335..5021ef3a3 100644 --- a/src/services/export/single.js +++ b/src/services/export/single.js @@ -2,9 +2,9 @@ const mimeTypes = require('mime-types'); const html = require('html'); -const utils = require('../utils.js'); +const utils = require('../utils'); const mdService = require('./md.js'); -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); function exportSingleNote(taskContext, branch, format, res) { const note = branch.getNote(); diff --git a/src/services/export/zip.js b/src/services/export/zip.js index dba7338d3..8fd8fd896 100644 --- a/src/services/export/zip.js +++ b/src/services/export/zip.js @@ -1,21 +1,21 @@ "use strict"; const html = require('html'); -const dateUtils = require('../date_utils.js'); +const dateUtils = require('../date_utils'); const path = require('path'); const mimeTypes = require('mime-types'); const mdService = require('./md.js'); const packageInfo = require('../../../package.json'); -const utils = require('../utils.js'); -const protectedSessionService = require('../protected_session.js'); +const utils = require('../utils'); +const protectedSessionService = require('../protected_session'); const sanitize = require("sanitize-filename"); const fs = require("fs"); -const becca = require('../../becca/becca.js'); -const RESOURCE_DIR = require('../../services/resource_dir.js').RESOURCE_DIR; +const becca = require('../../becca/becca'); +const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; const archiver = require('archiver'); -const log = require('../log.js'); -const TaskContext = require('../task_context.js'); -const ValidationError = require('../../errors/validation_error.js'); +const log = require('../log'); +const TaskContext = require('../task_context'); +const ValidationError = require('../../errors/validation_error'); const NoteMeta = require('../meta/note_meta.js'); const AttachmentMeta = require('../meta/attachment_meta.js'); const AttributeMeta = require('../meta/attribute_meta.js'); diff --git a/src/services/handlers.js b/src/services/handlers.js index 769c69485..1a9c8e353 100644 --- a/src/services/handlers.js +++ b/src/services/handlers.js @@ -1,9 +1,9 @@ -const eventService = require('./events.js'); +const eventService = require('./events'); const scriptService = require('./script.js'); const treeService = require('./tree.js'); const noteService = require('./notes.js'); -const becca = require('../becca/becca.js'); -const BAttribute = require('../becca/entities/battribute.js'); +const becca = require('../becca/becca'); +const BAttribute = require('../becca/entities/battribute'); const hiddenSubtreeService = require('./hidden_subtree.js'); const oneTimeTimer = require('./one_time_timer.js'); diff --git a/src/services/hidden_subtree.js b/src/services/hidden_subtree.js index 9c2820943..6976fcab9 100644 --- a/src/services/hidden_subtree.js +++ b/src/services/hidden_subtree.js @@ -1,7 +1,7 @@ -const becca = require('../becca/becca.js'); +const becca = require('../becca/becca'); const noteService = require('./notes.js'); -const BAttribute = require('../becca/entities/battribute.js'); -const log = require('./log.js'); +const BAttribute = require('../becca/entities/battribute'); +const log = require('./log'); const migrationService = require('./migration.js'); const LBTPL_ROOT = "_lbTplRoot"; diff --git a/src/services/hoisted_note.js b/src/services/hoisted_note.js index f3f4e5675..a75d4addc 100644 --- a/src/services/hoisted_note.js +++ b/src/services/hoisted_note.js @@ -1,5 +1,5 @@ -const cls = require('./cls.js'); -const becca = require('../becca/becca.js'); +const cls = require('./cls'); +const becca = require('../becca/becca'); function getHoistedNoteId() { return cls.getHoistedNoteId(); diff --git a/src/services/host.js b/src/services/host.js index 2430d45c2..389203e99 100644 --- a/src/services/host.js +++ b/src/services/host.js @@ -1,3 +1,3 @@ -const config = require('./config.js'); +const config = require('./config'); module.exports = process.env.TRILIUM_HOST || config['Network']['host'] || '0.0.0.0'; diff --git a/src/services/image.js b/src/services/image.js index 5a45e85ca..04add20f8 100644 --- a/src/services/image.js +++ b/src/services/image.js @@ -1,11 +1,11 @@ "use strict"; -const becca = require('../becca/becca.js'); -const log = require('./log.js'); -const protectedSessionService = require('./protected_session.js'); +const becca = require('../becca/becca'); +const log = require('./log'); +const protectedSessionService = require('./protected_session'); const noteService = require('./notes.js'); -const optionService = require('./options.js'); -const sql = require('./sql.js'); +const optionService = require('./options'); +const sql = require('./sql'); const jimp = require('jimp'); const imageType = require('image-type'); const sanitizeFilename = require('sanitize-filename'); diff --git a/src/services/import/enex.js b/src/services/import/enex.js index db0dc9d6c..7cc8973ce 100644 --- a/src/services/import/enex.js +++ b/src/services/import/enex.js @@ -1,14 +1,14 @@ const sax = require("sax"); const stream = require('stream'); const {Throttle} = require('stream-throttle'); -const log = require('../log.js'); -const utils = require('../utils.js'); -const sql = require('../sql.js'); +const log = require('../log'); +const utils = require('../utils'); +const sql = require('../sql'); const noteService = require('../notes.js'); const imageService = require('../image.js'); -const protectedSessionService = require('../protected_session.js'); +const protectedSessionService = require('../protected_session'); const htmlSanitizer = require('../html_sanitizer.js'); -const {sanitizeAttributeName} = require('../sanitize_attribute_name.js'); +const {sanitizeAttributeName} = require('../sanitize_attribute_name'); /** * date format is e.g. 20181121T193703Z or 2013-04-14T16:19:00.000Z (Mac evernote, see #3496) diff --git a/src/services/import/markdown.js b/src/services/import/markdown.js index 90b6d1f43..7cdd6b3d2 100644 --- a/src/services/import/markdown.js +++ b/src/services/import/markdown.js @@ -2,7 +2,7 @@ const marked = require("marked"); const htmlSanitizer = require('../html_sanitizer.js'); -const importUtils = require('./utils.js'); +const importUtils = require('./utils'); function renderToHtml(content, title) { const html = marked.parse(content, { diff --git a/src/services/import/opml.js b/src/services/import/opml.js index eb7b891d6..a547bf7ad 100644 --- a/src/services/import/opml.js +++ b/src/services/import/opml.js @@ -2,7 +2,7 @@ const noteService = require('../../services/notes.js'); const parseString = require('xml2js').parseString; -const protectedSessionService = require('../protected_session.js'); +const protectedSessionService = require('../protected_session'); const htmlSanitizer = require('../html_sanitizer.js'); /** diff --git a/src/services/import/single.js b/src/services/import/single.js index 7d3ea164f..5e7c92630 100644 --- a/src/services/import/single.js +++ b/src/services/import/single.js @@ -2,11 +2,11 @@ const noteService = require('../../services/notes.js'); const imageService = require('../../services/image.js'); -const protectedSessionService = require('../protected_session.js'); +const protectedSessionService = require('../protected_session'); const markdownService = require('./markdown.js'); const mimeService = require('./mime.js'); -const utils = require('../../services/utils.js'); -const importUtils = require('./utils.js'); +const utils = require('../../services/utils'); +const importUtils = require('./utils'); const htmlSanitizer = require('../html_sanitizer.js'); function importSingleFile(taskContext, file, parentNote) { diff --git a/src/services/import/zip.js b/src/services/import/zip.js index 5097ba925..cf6e870ac 100644 --- a/src/services/import/zip.js +++ b/src/services/import/zip.js @@ -1,19 +1,19 @@ "use strict"; -const BAttribute = require('../../becca/entities/battribute.js'); -const utils = require('../../services/utils.js'); -const log = require('../../services/log.js'); +const BAttribute = require('../../becca/entities/battribute'); +const utils = require('../../services/utils'); +const log = require('../../services/log'); const noteService = require('../../services/notes.js'); const attributeService = require('../../services/attributes.js'); -const BBranch = require('../../becca/entities/bbranch.js'); +const BBranch = require('../../becca/entities/bbranch'); const path = require('path'); -const protectedSessionService = require('../protected_session.js'); +const protectedSessionService = require('../protected_session'); const mimeService = require('./mime.js'); const treeService = require('../tree.js'); const yauzl = require("yauzl"); const htmlSanitizer = require('../html_sanitizer.js'); -const becca = require('../../becca/becca.js'); -const BAttachment = require('../../becca/entities/battachment.js'); +const becca = require('../../becca/becca'); +const BAttachment = require('../../becca/entities/battachment'); const markdownService = require('./markdown.js'); /** diff --git a/src/services/instance_id.js b/src/services/instance_id.js deleted file mode 100644 index 49f0a4209..000000000 --- a/src/services/instance_id.js +++ /dev/null @@ -1,5 +0,0 @@ -const utils = require('./utils.js'); - -const instanceId = utils.randomString(12); - -module.exports = instanceId; diff --git a/src/services/instance_id.ts b/src/services/instance_id.ts new file mode 100644 index 000000000..6e0eb503c --- /dev/null +++ b/src/services/instance_id.ts @@ -0,0 +1,5 @@ +import utils = require('./utils'); + +const instanceId = utils.randomString(12); + +export = instanceId; diff --git a/src/services/keyboard_actions.js b/src/services/keyboard_actions.js index 694b7a1d7..6ad590f07 100644 --- a/src/services/keyboard_actions.js +++ b/src/services/keyboard_actions.js @@ -1,8 +1,8 @@ "use strict"; -const optionService = require('./options.js'); -const log = require('./log.js'); -const utils = require('./utils.js'); +const optionService = require('./options'); +const log = require('./log'); +const utils = require('./utils'); const isMac = process.platform === "darwin"; const isElectron = utils.isElectron(); diff --git a/src/services/log.js b/src/services/log.ts similarity index 80% rename from src/services/log.js rename to src/services/log.ts index 935d695ef..742fdcdb1 100644 --- a/src/services/log.js +++ b/src/services/log.ts @@ -1,14 +1,15 @@ "use strict"; -const fs = require('fs'); -const dataDir = require('./data_dir.js'); -const cls = require('./cls.js'); +import { Request, Response } from "express"; +import fs = require("fs"); +import dataDir = require('./data_dir'); +import cls = require('./cls'); if (!fs.existsSync(dataDir.LOG_DIR)) { fs.mkdirSync(dataDir.LOG_DIR, 0o700); } -let logFile = null; +let logFile!: fs.WriteStream; const SECOND = 1000; const MINUTE = 60 * SECOND; @@ -17,7 +18,7 @@ const DAY = 24 * HOUR; const NEW_LINE = process.platform === "win32" ? '\r\n' : '\n'; -let todaysMidnight = null; +let todaysMidnight!: Date; initLogFile(); @@ -39,7 +40,7 @@ function initLogFile() { logFile = fs.createWriteStream(path, {flags: 'a'}); } -function checkDate(millisSinceMidnight) { +function checkDate(millisSinceMidnight: number) { if (millisSinceMidnight >= DAY) { initLogFile(); @@ -49,7 +50,7 @@ function checkDate(millisSinceMidnight) { return millisSinceMidnight; } -function log(str) { +function log(str: string) { const bundleNoteId = cls.get("bundleNoteId"); if (bundleNoteId) { @@ -65,17 +66,17 @@ function log(str) { console.log(str); } -function info(message) { +function info(message: string) { log(message); } -function error(message) { +function error(message: string) { log(`ERROR: ${message}`); } const requestBlacklist = [ "/libraries", "/app", "/images", "/stylesheets", "/api/recent-notes" ]; -function request(req, res, timeMs, responseLength = "?") { +function request(req: Request, res: Response, timeMs: number, responseLength = "?") { for (const bl of requestBlacklist) { if (req.url.startsWith(bl)) { return; @@ -90,13 +91,13 @@ function request(req, res, timeMs, responseLength = "?") { `${res.statusCode} ${req.method} ${req.url} with ${responseLength} bytes took ${timeMs}ms`); } -function pad(num) { +function pad(num: number) { num = Math.floor(num); return num < 10 ? (`0${num}`) : num.toString(); } -function padMilli(num) { +function padMilli(num: number) { if (num < 10) { return `00${num}`; } @@ -108,7 +109,7 @@ function padMilli(num) { } } -function formatTime(millisSinceMidnight) { +function formatTime(millisSinceMidnight: number) { return `${pad(millisSinceMidnight / HOUR)}:${pad((millisSinceMidnight % HOUR) / MINUTE)}:${pad((millisSinceMidnight % MINUTE) / SECOND)}.${padMilli(millisSinceMidnight % SECOND)}`; } @@ -116,7 +117,7 @@ function formatDate() { return `${pad(todaysMidnight.getFullYear())}-${pad(todaysMidnight.getMonth() + 1)}-${pad(todaysMidnight.getDate())}`; } -module.exports = { +export = { info, error, request diff --git a/src/services/migration.js b/src/services/migration.js index aeb7ba217..3b6caa251 100644 --- a/src/services/migration.js +++ b/src/services/migration.js @@ -1,11 +1,11 @@ const backupService = require('./backup.js'); -const sql = require('./sql.js'); +const sql = require('./sql'); const fs = require('fs-extra'); -const log = require('./log.js'); -const utils = require('./utils.js'); -const resourceDir = require('./resource_dir.js'); +const log = require('./log'); +const utils = require('./utils'); +const resourceDir = require('./resource_dir'); const appInfo = require('./app_info.js'); -const cls = require('./cls.js'); +const cls = require('./cls'); async function migrate() { const currentDbVersion = getDbVersion(); diff --git a/src/services/notes.js b/src/services/notes.js index 80a3b8e84..47fc0ab32 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -1,27 +1,27 @@ -const sql = require('./sql.js'); -const optionService = require('./options.js'); -const dateUtils = require('./date_utils.js'); -const entityChangesService = require('./entity_changes.js'); -const eventService = require('./events.js'); -const cls = require('../services/cls.js'); -const protectedSessionService = require('../services/protected_session.js'); -const log = require('../services/log.js'); -const utils = require('../services/utils.js'); +const sql = require('./sql'); +const optionService = require('./options'); +const dateUtils = require('./date_utils'); +const entityChangesService = require('./entity_changes'); +const eventService = require('./events'); +const cls = require('../services/cls'); +const protectedSessionService = require('../services/protected_session'); +const log = require('../services/log'); +const utils = require('../services/utils'); const revisionService = require('./revisions.js'); const request = require('./request.js'); const path = require('path'); const url = require('url'); -const becca = require('../becca/becca.js'); -const BBranch = require('../becca/entities/bbranch.js'); -const BNote = require('../becca/entities/bnote.js'); -const BAttribute = require('../becca/entities/battribute.js'); -const BAttachment = require('../becca/entities/battachment.js'); +const becca = require('../becca/becca'); +const BBranch = require('../becca/entities/bbranch'); +const BNote = require('../becca/entities/bnote'); +const BAttribute = require('../becca/entities/battribute'); +const BAttachment = require('../becca/entities/battachment'); const dayjs = require("dayjs"); const htmlSanitizer = require('./html_sanitizer.js'); -const ValidationError = require('../errors/validation_error.js'); +const ValidationError = require('../errors/validation_error'); const noteTypesService = require('./note_types.js'); const fs = require("fs"); -const ws = require('./ws.js'); +const ws = require('./ws'); const html2plaintext = require('html2plaintext') /** @param {BNote} parentNote */ diff --git a/src/services/options.js b/src/services/options.ts similarity index 71% rename from src/services/options.js rename to src/services/options.ts index 6c33b9478..a7b5ad475 100644 --- a/src/services/options.js +++ b/src/services/options.ts @@ -1,22 +1,21 @@ -const becca = require('../becca/becca.js'); -const sql = require('./sql.js'); +import becca = require('../becca/becca'); +import { OptionRow } from '../becca/entities/rows'; +import sql = require('./sql'); -/** @returns {string|null} */ -function getOptionOrNull(name) { +function getOptionOrNull(name: string): string | null { let option; if (becca.loaded) { option = becca.getOption(name); } else { // e.g. in initial sync becca is not loaded because DB is not initialized - option = sql.getRow("SELECT * FROM options WHERE name = ?", [name]); + option = sql.getRow("SELECT * FROM options WHERE name = ?", [name]); } return option ? option.value : null; } -/** @returns {string} */ -function getOption(name) { +function getOption(name: string): string { const val = getOptionOrNull(name); if (val === null) { @@ -26,8 +25,7 @@ function getOption(name) { return val; } -/** @returns {int} */ -function getOptionInt(name, defaultValue = undefined) { +function getOptionInt(name: string, defaultValue?: number): number { const val = getOption(name); const intVal = parseInt(val); @@ -43,8 +41,7 @@ function getOptionInt(name, defaultValue = undefined) { return intVal; } -/** @returns {boolean} */ -function getOptionBool(name) { +function getOptionBool(name: string): boolean { const val = getOption(name); if (!['true', 'false'].includes(val)) { @@ -54,7 +51,7 @@ function getOptionBool(name) { return val === 'true'; } -function setOption(name, value) { +function setOption(name: string, value: string | boolean) { if (value === true || value === false) { value = value.toString(); } @@ -71,9 +68,9 @@ function setOption(name, value) { } } -function createOption(name, value, isSynced) { +function createOption(name: string, value: string, isSynced: boolean) { // to avoid circular dependency, need to find a better solution - const BOption = require('../becca/entities/boption.js'); + const BOption = require('../becca/entities/boption'); new BOption({ name: name, @@ -87,7 +84,7 @@ function getOptions() { } function getOptionMap() { - const map = {}; + const map: Record = {}; for (const option of Object.values(becca.options)) { map[option.name] = option.value; @@ -96,7 +93,7 @@ function getOptionMap() { return map; } -module.exports = { +export = { getOption, getOptionInt, getOptionBool, diff --git a/src/services/options_init.js b/src/services/options_init.js index 95514d6d6..5e7ca2f10 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -1,8 +1,8 @@ -const optionService = require('./options.js'); +const optionService = require('./options'); const appInfo = require('./app_info.js'); -const utils = require('./utils.js'); -const log = require('./log.js'); -const dateUtils = require('./date_utils.js'); +const utils = require('./utils'); +const log = require('./log'); +const dateUtils = require('./date_utils'); const keyboardActions = require('./keyboard_actions.js'); function initDocumentOptions() { diff --git a/src/services/port.js b/src/services/port.js index 938eb3324..c57f22a5d 100644 --- a/src/services/port.js +++ b/src/services/port.js @@ -1,7 +1,7 @@ -const config = require('./config.js'); -const utils = require('./utils.js'); -const env = require('./env.js'); -const dataDir = require('./data_dir.js'); +const config = require('./config'); +const utils = require('./utils'); +const env = require('./env'); +const dataDir = require('./data_dir'); function parseAndValidate(portStr, source) { const portNum = parseInt(portStr); diff --git a/src/services/promoted_attribute_definition_parser.js b/src/services/promoted_attribute_definition_parser.ts similarity index 78% rename from src/services/promoted_attribute_definition_parser.js rename to src/services/promoted_attribute_definition_parser.ts index 937dae1de..3efe16f4d 100644 --- a/src/services/promoted_attribute_definition_parser.js +++ b/src/services/promoted_attribute_definition_parser.ts @@ -1,6 +1,15 @@ -function parse(value) { +interface DefinitionObject { + isPromoted?: boolean; + labelType?: string; + multiplicity?: string; + numberPrecision?: number; + promotedAlias?: string; + inverseRelation?: string; +} + +function parse(value: string): DefinitionObject { const tokens = value.split(',').map(t => t.trim()); - const defObj = {}; + const defObj: DefinitionObject = {}; for (const token of tokens) { if (token === 'promoted') { @@ -35,6 +44,6 @@ function parse(value) { return defObj; } -module.exports = { +export = { parse }; diff --git a/src/services/protected_session.js b/src/services/protected_session.js deleted file mode 100644 index 2ade5e338..000000000 --- a/src/services/protected_session.js +++ /dev/null @@ -1,76 +0,0 @@ -"use strict"; - -const log = require('./log.js'); -const dataEncryptionService = require('./encryption/data_encryption.js'); - -let dataKey = null; - -function setDataKey(decryptedDataKey) { - dataKey = Array.from(decryptedDataKey); -} - -function getDataKey() { - return dataKey; -} - -function resetDataKey() { - dataKey = null; -} - -function isProtectedSessionAvailable() { - return !!dataKey; -} - -function encrypt(plainText) { - if (plainText === null) { - return null; - } - - return dataEncryptionService.encrypt(getDataKey(), plainText); -} - -function decrypt(cipherText) { - if (cipherText === null) { - return null; - } - - return dataEncryptionService.decrypt(getDataKey(), cipherText); -} - -function decryptString(cipherText) { - return dataEncryptionService.decryptString(getDataKey(), cipherText); -} - -let lastProtectedSessionOperationDate = null; - -function touchProtectedSession() { - if (isProtectedSessionAvailable()) { - lastProtectedSessionOperationDate = Date.now(); - } -} - -function checkProtectedSessionExpiration() { - const options = require('./options.js'); - const protectedSessionTimeout = options.getOptionInt('protectedSessionTimeout'); - if (isProtectedSessionAvailable() - && lastProtectedSessionOperationDate - && Date.now() - lastProtectedSessionOperationDate > protectedSessionTimeout * 1000) { - - resetDataKey(); - - log.info("Expiring protected session"); - - require('./ws.js').reloadFrontend("leaving protected session"); - } -} - -module.exports = { - setDataKey, - resetDataKey, - isProtectedSessionAvailable, - encrypt, - decrypt, - decryptString, - touchProtectedSession, - checkProtectedSessionExpiration -}; diff --git a/src/services/protected_session.ts b/src/services/protected_session.ts new file mode 100644 index 000000000..6c1d6c223 --- /dev/null +++ b/src/services/protected_session.ts @@ -0,0 +1,82 @@ +"use strict"; + +import log = require('./log'); +import dataEncryptionService = require('./encryption/data_encryption'); + +let dataKey: Buffer | null = null; + +function setDataKey(decryptedDataKey: Buffer) { + dataKey = Buffer.from(decryptedDataKey); +} + +function getDataKey() { + return dataKey; +} + +function resetDataKey() { + dataKey = null; +} + +function isProtectedSessionAvailable() { + return !!dataKey; +} + +function encrypt(plainText: string | Buffer) { + const dataKey = getDataKey(); + if (plainText === null || dataKey === null) { + return null; + } + + return dataEncryptionService.encrypt(dataKey, plainText); +} + +function decrypt(cipherText: string | Buffer): Buffer | null { + const dataKey = getDataKey(); + if (cipherText === null || dataKey === null) { + return null; + } + + return dataEncryptionService.decrypt(dataKey, cipherText) || null; +} + +function decryptString(cipherText: string): string | null { + const dataKey = getDataKey(); + if (dataKey === null) { + return null; + } + return dataEncryptionService.decryptString(dataKey, cipherText); +} + +let lastProtectedSessionOperationDate: number | null = null; + +function touchProtectedSession() { + if (isProtectedSessionAvailable()) { + lastProtectedSessionOperationDate = Date.now(); + } +} + +function checkProtectedSessionExpiration() { + const options = require('./options'); + const protectedSessionTimeout = options.getOptionInt('protectedSessionTimeout'); + if (isProtectedSessionAvailable() + && lastProtectedSessionOperationDate + && Date.now() - lastProtectedSessionOperationDate > protectedSessionTimeout * 1000) { + + resetDataKey(); + + log.info("Expiring protected session"); + + require('./ws').reloadFrontend("leaving protected session"); + } +} + +export = { + setDataKey, + resetDataKey, + isProtectedSessionAvailable, + encrypt, + decrypt, + decryptString, + touchProtectedSession, + checkProtectedSessionExpiration +}; diff --git a/src/services/request.js b/src/services/request.js index 610e44f51..88771439b 100644 --- a/src/services/request.js +++ b/src/services/request.js @@ -1,7 +1,7 @@ "use strict"; -const utils = require('./utils.js'); -const log = require('./log.js'); +const utils = require('./utils'); +const log = require('./log'); const url = require('url'); const syncOptions = require('./sync_options.js'); diff --git a/src/services/resource_dir.js b/src/services/resource_dir.ts similarity index 85% rename from src/services/resource_dir.js rename to src/services/resource_dir.ts index 088a6d7d3..cba351ac8 100644 --- a/src/services/resource_dir.js +++ b/src/services/resource_dir.ts @@ -1,6 +1,6 @@ -const log = require('./log.js'); -const path = require('path'); -const fs = require('fs'); +import log = require('./log'); +import path = require('path'); +import fs = require('fs'); const RESOURCE_DIR = path.resolve(__dirname, "../.."); @@ -20,7 +20,7 @@ if (!fs.existsSync(MIGRATIONS_DIR)) { process.exit(1); } -module.exports = { +export = { RESOURCE_DIR, MIGRATIONS_DIR, DB_INIT_DIR, diff --git a/src/services/revisions.js b/src/services/revisions.js index 743284789..7697a35f4 100644 --- a/src/services/revisions.js +++ b/src/services/revisions.js @@ -1,9 +1,9 @@ "use strict"; -const log = require('./log.js'); -const sql = require('./sql.js'); -const protectedSessionService = require('./protected_session.js'); -const dateUtils = require('./date_utils.js'); +const log = require('./log'); +const sql = require('./sql'); +const protectedSessionService = require('./protected_session'); +const dateUtils = require('./date_utils'); /** * @param {BNote} note diff --git a/src/services/sanitize_attribute_name.js b/src/services/sanitize_attribute_name.ts similarity index 75% rename from src/services/sanitize_attribute_name.js rename to src/services/sanitize_attribute_name.ts index 3a7c9ff67..62b2b03b3 100644 --- a/src/services/sanitize_attribute_name.js +++ b/src/services/sanitize_attribute_name.ts @@ -1,5 +1,5 @@ -function sanitizeAttributeName(origName) { - let fixedName; +function sanitizeAttributeName(origName: string) { + let fixedName: string; if (origName === '') { fixedName = "unnamed"; @@ -13,6 +13,6 @@ function sanitizeAttributeName(origName) { } -module.exports = { +export = { sanitizeAttributeName }; diff --git a/src/services/scheduler.js b/src/services/scheduler.js index 48a19815a..2a712a1ce 100644 --- a/src/services/scheduler.js +++ b/src/services/scheduler.js @@ -1,10 +1,10 @@ const scriptService = require('./script.js'); -const cls = require('./cls.js'); +const cls = require('./cls'); const sqlInit = require('./sql_init.js'); -const config = require('./config.js'); -const log = require('./log.js'); +const config = require('./config'); +const log = require('./log'); const attributeService = require('../services/attributes.js'); -const protectedSessionService = require('../services/protected_session.js'); +const protectedSessionService = require('../services/protected_session'); const hiddenSubtreeService = require('./hidden_subtree.js'); /** diff --git a/src/services/script.js b/src/services/script.js index 6119075e3..f82f50444 100644 --- a/src/services/script.js +++ b/src/services/script.js @@ -1,7 +1,7 @@ const ScriptContext = require('./script_context.js'); -const cls = require('./cls.js'); -const log = require('./log.js'); -const becca = require('../becca/becca.js'); +const cls = require('./cls'); +const log = require('./log'); +const becca = require('../becca/becca'); function executeNote(note, apiParams) { if (!note.isJavaScript() || note.getScriptEnv() !== 'backend' || !note.isContentAvailable()) { diff --git a/src/services/script_context.js b/src/services/script_context.js index 67400d630..d4b83bc37 100644 --- a/src/services/script_context.js +++ b/src/services/script_context.js @@ -1,4 +1,4 @@ -const utils = require('./utils.js'); +const utils = require('./utils'); const BackendScriptApi = require('./backend_script_api.js'); function ScriptContext(allNotes, apiParams = {}) { diff --git a/src/services/search/expressions/ancestor.js b/src/services/search/expressions/ancestor.js index 57c3adb5f..5275492a1 100644 --- a/src/services/search/expressions/ancestor.js +++ b/src/services/search/expressions/ancestor.js @@ -1,9 +1,9 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); -const log = require('../../log.js'); -const becca = require('../../../becca/becca.js'); +const NoteSet = require('../note_set'); +const log = require('../../log'); +const becca = require('../../../becca/becca'); class AncestorExp extends Expression { constructor(ancestorNoteId, ancestorDepth) { diff --git a/src/services/search/expressions/attribute_exists.js b/src/services/search/expressions/attribute_exists.js index f9be65689..4c723a433 100644 --- a/src/services/search/expressions/attribute_exists.js +++ b/src/services/search/expressions/attribute_exists.js @@ -1,7 +1,7 @@ "use strict"; -const NoteSet = require('../note_set.js'); -const becca = require('../../../becca/becca.js'); +const NoteSet = require('../note_set'); +const becca = require('../../../becca/becca'); const Expression = require('./expression.js'); class AttributeExistsExp extends Expression { diff --git a/src/services/search/expressions/child_of.js b/src/services/search/expressions/child_of.js index d53b49c20..fde480a83 100644 --- a/src/services/search/expressions/child_of.js +++ b/src/services/search/expressions/child_of.js @@ -1,7 +1,7 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); +const NoteSet = require('../note_set'); class ChildOfExp extends Expression { constructor(subExpression) { diff --git a/src/services/search/expressions/descendant_of.js b/src/services/search/expressions/descendant_of.js index fa0e50dd3..11ad011e8 100644 --- a/src/services/search/expressions/descendant_of.js +++ b/src/services/search/expressions/descendant_of.js @@ -1,8 +1,8 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); -const becca = require('../../../becca/becca.js'); +const NoteSet = require('../note_set'); +const becca = require('../../../becca/becca'); class DescendantOfExp extends Expression { constructor(subExpression) { diff --git a/src/services/search/expressions/is_hidden.js b/src/services/search/expressions/is_hidden.js index 32f33b512..e5ff48536 100644 --- a/src/services/search/expressions/is_hidden.js +++ b/src/services/search/expressions/is_hidden.js @@ -1,7 +1,7 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); +const NoteSet = require('../note_set'); /** * Note is hidden when all its note paths start in hidden subtree (i.e., the note is not cloned into visible tree) diff --git a/src/services/search/expressions/label_comparison.js b/src/services/search/expressions/label_comparison.js index 0bc27ff0c..961bf13f5 100644 --- a/src/services/search/expressions/label_comparison.js +++ b/src/services/search/expressions/label_comparison.js @@ -1,8 +1,8 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); -const becca = require('../../../becca/becca.js'); +const NoteSet = require('../note_set'); +const becca = require('../../../becca/becca'); class LabelComparisonExp extends Expression { constructor(attributeType, attributeName, comparator) { diff --git a/src/services/search/expressions/note_content_fulltext.js b/src/services/search/expressions/note_content_fulltext.js index 1b607b308..98b8a804f 100644 --- a/src/services/search/expressions/note_content_fulltext.js +++ b/src/services/search/expressions/note_content_fulltext.js @@ -1,12 +1,12 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); -const log = require('../../log.js'); -const becca = require('../../../becca/becca.js'); -const protectedSessionService = require('../../protected_session.js'); +const NoteSet = require('../note_set'); +const log = require('../../log'); +const becca = require('../../../becca/becca'); +const protectedSessionService = require('../../protected_session'); const striptags = require('striptags'); -const utils = require('../../utils.js'); +const utils = require('../../utils'); const ALLOWED_OPERATORS = ['=', '!=', '*=*', '*=', '=*', '%=']; @@ -38,7 +38,7 @@ class NoteContentFulltextExp extends Expression { } const resultNoteSet = new NoteSet(); - const sql = require('../../sql.js'); + const sql = require('../../sql'); for (const row of sql.iterateRows(` SELECT noteId, type, mime, content, isProtected diff --git a/src/services/search/expressions/note_flat_text.js b/src/services/search/expressions/note_flat_text.js index 11cc466ec..e9fc2fad8 100644 --- a/src/services/search/expressions/note_flat_text.js +++ b/src/services/search/expressions/note_flat_text.js @@ -1,9 +1,9 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); -const becca = require('../../../becca/becca.js'); -const utils = require('../../utils.js'); +const NoteSet = require('../note_set'); +const becca = require('../../../becca/becca'); +const utils = require('../../utils'); class NoteFlatTextExp extends Expression { constructor(tokens) { diff --git a/src/services/search/expressions/or.js b/src/services/search/expressions/or.js index 1704b5c60..c5a9c64c4 100644 --- a/src/services/search/expressions/or.js +++ b/src/services/search/expressions/or.js @@ -1,7 +1,7 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); +const NoteSet = require('../note_set'); const TrueExp = require('./true.js'); class OrExp extends Expression { diff --git a/src/services/search/expressions/order_by_and_limit.js b/src/services/search/expressions/order_by_and_limit.js index c00f361f1..9a68f9eb2 100644 --- a/src/services/search/expressions/order_by_and_limit.js +++ b/src/services/search/expressions/order_by_and_limit.js @@ -1,7 +1,7 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); +const NoteSet = require('../note_set'); class OrderByAndLimitExp extends Expression { constructor(orderDefinitions, limit) { diff --git a/src/services/search/expressions/parent_of.js b/src/services/search/expressions/parent_of.js index 2243d3540..5f388696b 100644 --- a/src/services/search/expressions/parent_of.js +++ b/src/services/search/expressions/parent_of.js @@ -1,7 +1,7 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); +const NoteSet = require('../note_set'); class ParentOfExp extends Expression { constructor(subExpression) { diff --git a/src/services/search/expressions/property_comparison.js b/src/services/search/expressions/property_comparison.js index 9b014a642..5f8ac14b3 100644 --- a/src/services/search/expressions/property_comparison.js +++ b/src/services/search/expressions/property_comparison.js @@ -1,7 +1,7 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); +const NoteSet = require('../note_set'); const buildComparator = require('../services/build_comparator.js'); /** diff --git a/src/services/search/expressions/relation_where.js b/src/services/search/expressions/relation_where.js index dee56b1dd..77283b45c 100644 --- a/src/services/search/expressions/relation_where.js +++ b/src/services/search/expressions/relation_where.js @@ -1,8 +1,8 @@ "use strict"; const Expression = require('./expression.js'); -const NoteSet = require('../note_set.js'); -const becca = require('../../../becca/becca.js'); +const NoteSet = require('../note_set'); +const becca = require('../../../becca/becca'); class RelationWhereExp extends Expression { constructor(relationName, subExpression) { diff --git a/src/services/search/note_set.js b/src/services/search/note_set.ts similarity index 69% rename from src/services/search/note_set.js rename to src/services/search/note_set.ts index 82c236cf6..47c644c38 100644 --- a/src/services/search/note_set.js +++ b/src/services/search/note_set.ts @@ -1,40 +1,46 @@ "use strict"; +import BNote = require("../../becca/entities/bnote"); + class NoteSet { - constructor(notes = []) { - /** @type {BNote[]} */ + + private noteIdSet: Set; + + notes: BNote[]; + sorted: boolean; + + constructor(notes: BNote[] = []) { this.notes = notes; this.noteIdSet = new Set(notes.map(note => note.noteId)); - /** @type {boolean} */ this.sorted = false; } - add(note) { + add(note: BNote) { if (!this.hasNote(note)) { this.notes.push(note); this.noteIdSet.add(note.noteId); } } - addAll(notes) { + addAll(notes: BNote[]) { for (const note of notes) { this.add(note); } } - hasNote(note) { + hasNote(note: BNote) { return this.hasNoteId(note.noteId); } - hasNoteId(noteId) { + hasNoteId(noteId: string) { return this.noteIdSet.has(noteId); } - mergeIn(anotherNoteSet) { + mergeIn(anotherNoteSet: NoteSet) { this.addAll(anotherNoteSet.notes); } - minus(anotherNoteSet) { + minus(anotherNoteSet: NoteSet) { const newNoteSet = new NoteSet(); for (const note of this.notes) { @@ -46,7 +52,7 @@ class NoteSet { return newNoteSet; } - intersection(anotherNoteSet) { + intersection(anotherNoteSet: NoteSet) { const newNoteSet = new NoteSet(); for (const note of this.notes) { @@ -59,4 +65,4 @@ class NoteSet { } } -module.exports = NoteSet; +export = NoteSet; diff --git a/src/services/search/search_result.js b/src/services/search/search_result.js index 3a094bc8c..61f7d86d8 100644 --- a/src/services/search/search_result.js +++ b/src/services/search/search_result.js @@ -1,7 +1,7 @@ "use strict"; const beccaService = require('../../becca/becca_service.js'); -const becca = require('../../becca/becca.js'); +const becca = require('../../becca/becca'); class SearchResult { constructor(notePathArray) { diff --git a/src/services/search/services/parse.js b/src/services/search/services/parse.js index b31ea265d..84c717f6e 100644 --- a/src/services/search/services/parse.js +++ b/src/services/search/services/parse.js @@ -17,7 +17,7 @@ const OrderByAndLimitExp = require('../expressions/order_by_and_limit.js'); const AncestorExp = require('../expressions/ancestor.js'); const buildComparator = require('./build_comparator.js'); const ValueExtractor = require('../value_extractor.js'); -const utils = require('../../utils.js'); +const utils = require('../../utils'); const TrueExp = require('../expressions/true.js'); const IsHiddenExp = require('../expressions/is_hidden.js'); diff --git a/src/services/search/services/search.js b/src/services/search/services/search.js index e1ceed086..498d31310 100644 --- a/src/services/search/services/search.js +++ b/src/services/search/services/search.js @@ -6,10 +6,10 @@ const handleParens = require('./handle_parens.js'); const parse = require('./parse.js'); const SearchResult = require('../search_result.js'); const SearchContext = require('../search_context.js'); -const becca = require('../../../becca/becca.js'); +const becca = require('../../../becca/becca'); const beccaService = require('../../../becca/becca_service.js'); -const utils = require('../../utils.js'); -const log = require('../../log.js'); +const utils = require('../../utils'); +const log = require('../../log'); const hoistedNoteService = require('../../hoisted_note.js'); function searchFromNote(note) { @@ -90,7 +90,7 @@ function searchFromRelation(note, relationName) { } function loadNeededInfoFromDatabase() { - const sql = require('../../sql.js'); + const sql = require('../../sql'); /** * This complex structure is needed to calculate total occupied space by a note. Several object instances diff --git a/src/services/session_secret.js b/src/services/session_secret.js index 90eaa65bc..16c1ed8c3 100644 --- a/src/services/session_secret.js +++ b/src/services/session_secret.js @@ -2,8 +2,8 @@ const fs = require('fs'); const crypto = require('crypto'); -const dataDir = require('./data_dir.js'); -const log = require('./log.js'); +const dataDir = require('./data_dir'); +const log = require('./log'); const sessionSecretPath = `${dataDir.TRILIUM_DATA_DIR}/session_secret.txt`; diff --git a/src/services/setup.js b/src/services/setup.js index 67eb7f3ce..7434659be 100644 --- a/src/services/setup.js +++ b/src/services/setup.js @@ -1,12 +1,12 @@ const syncService = require('./sync.js'); -const log = require('./log.js'); +const log = require('./log'); const sqlInit = require('./sql_init.js'); -const optionService = require('./options.js'); +const optionService = require('./options'); const syncOptions = require('./sync_options.js'); const request = require('./request.js'); const appInfo = require('./app_info.js'); -const utils = require('./utils.js'); -const becca = require('../becca/becca.js'); +const utils = require('./utils'); +const becca = require('../becca/becca'); async function hasSyncServerSchemaAndSeed() { const response = await requestToSyncServer('GET', '/api/setup/status'); diff --git a/src/services/special_notes.js b/src/services/special_notes.js index 60d202df0..fd229a876 100644 --- a/src/services/special_notes.js +++ b/src/services/special_notes.js @@ -1,9 +1,9 @@ const attributeService = require('./attributes.js'); const dateNoteService = require('./date_notes.js'); -const becca = require('../becca/becca.js'); +const becca = require('../becca/becca'); const noteService = require('./notes.js'); -const dateUtils = require('./date_utils.js'); -const log = require('./log.js'); +const dateUtils = require('./date_utils'); +const log = require('./log'); const hoistedNoteService = require('./hoisted_note.js'); const searchService = require('./search/services/search.js'); const SearchContext = require('./search/search_context.js'); diff --git a/src/services/sql.js b/src/services/sql.ts similarity index 73% rename from src/services/sql.js rename to src/services/sql.ts index 38cbabe19..f0a9d7d29 100644 --- a/src/services/sql.js +++ b/src/services/sql.ts @@ -4,17 +4,20 @@ * @module sql */ -const log = require('./log.js'); -const Database = require('better-sqlite3'); -const dataDir = require('./data_dir.js'); -const cls = require('./cls.js'); -const fs = require("fs-extra"); +import log = require('./log'); +import type { Statement, Database as DatabaseType, RunResult } from "better-sqlite3"; +import dataDir = require('./data_dir'); +import cls = require('./cls'); +import fs = require("fs-extra"); +import Database = require('better-sqlite3'); -const dbConnection = new Database(dataDir.DOCUMENT_PATH); +const dbConnection: DatabaseType = new Database(dataDir.DOCUMENT_PATH); dbConnection.pragma('journal_mode = WAL'); const LOG_ALL_QUERIES = false; +type Params = any; + [`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `SIGTERM`].forEach(eventType => { process.on(eventType, () => { if (dbConnection) { @@ -25,7 +28,7 @@ const LOG_ALL_QUERIES = false; }); }); -function insert(tableName, rec, replace = false) { +function insert(tableName: string, rec: T, replace = false) { const keys = Object.keys(rec || {}); if (keys.length === 0) { log.error(`Can't insert empty object into table ${tableName}`); @@ -48,11 +51,11 @@ function insert(tableName, rec, replace = false) { return res ? res.lastInsertRowid : null; } -function replace(tableName, rec) { - return insert(tableName, rec, true); +function replace(tableName: string, rec: T): number | null { + return insert(tableName, rec, true) as number | null; } -function upsert(tableName, primaryKey, rec) { +function upsert(tableName: string, primaryKey: string, rec: T) { const keys = Object.keys(rec || {}); if (keys.length === 0) { log.error(`Can't upsert empty object into table ${tableName}`); @@ -70,16 +73,16 @@ function upsert(tableName, primaryKey, rec) { for (const idx in rec) { if (rec[idx] === true || rec[idx] === false) { - rec[idx] = rec[idx] ? 1 : 0; + (rec as any)[idx] = rec[idx] ? 1 : 0; } } execute(query, rec); } -const statementCache = {}; +const statementCache: Record = {}; -function stmt(sql) { +function stmt(sql: string) { if (!(sql in statementCache)) { statementCache[sql] = dbConnection.prepare(sql); } @@ -87,31 +90,34 @@ function stmt(sql) { return statementCache[sql]; } -function getRow(query, params = []) { - return wrap(query, s => s.get(params)); +function getRow(query: string, params: Params = []): T { + return wrap(query, s => s.get(params)) as T; } -function getRowOrNull(query, params = []) { +function getRowOrNull(query: string, params: Params = []): T | null { const all = getRows(query, params); + if (!all) { + return null; + } - return all.length > 0 ? all[0] : null; + return (all.length > 0 ? all[0] : null) as (T | null); } -function getValue(query, params = []) { - return wrap(query, s => s.pluck().get(params)); +function getValue(query: string, params: Params = []): T { + return wrap(query, s => s.pluck().get(params)) as T; } // smaller values can result in better performance due to better usage of statement cache const PARAM_LIMIT = 100; -function getManyRows(query, params) { - let results = []; +function getManyRows(query: string, params: Params): T[] { + let results: unknown[] = []; while (params.length > 0) { const curParams = params.slice(0, Math.min(params.length, PARAM_LIMIT)); params = params.slice(curParams.length); - const curParamsObj = {}; + const curParamsObj: Record = {}; let j = 1; for (const param of curParams) { @@ -130,18 +136,18 @@ function getManyRows(query, params) { results = results.concat(subResults); } - return results; + return (results as (T[] | null) || []); } -function getRows(query, params = []) { - return wrap(query, s => s.all(params)); +function getRows(query: string, params: Params = []): T[] { + return wrap(query, s => s.all(params)) as T[]; } -function getRawRows(query, params = []) { - return wrap(query, s => s.raw().all(params)); +function getRawRows(query: string, params: Params = []): T[] | null { + return wrap(query, s => s.raw().all(params)) as T[] | null; } -function iterateRows(query, params = []) { +function iterateRows(query: string, params: Params = []) { if (LOG_ALL_QUERIES) { console.log(query); } @@ -149,26 +155,26 @@ function iterateRows(query, params = []) { return stmt(query).iterate(params); } -function getMap(query, params = []) { - const map = {}; - const results = getRawRows(query, params); +function getMap(query: string, params: Params = []) { + const map: Record = {} as Record; + const results = getRawRows<[K, V]>(query, params); - for (const row of results) { + for (const row of results || []) { map[row[0]] = row[1]; } return map; } -function getColumn(query, params = []) { - return wrap(query, s => s.pluck().all(params)); +function getColumn(query: string, params: Params = []): T[] { + return wrap(query, s => s.pluck().all(params)) as T[]; } -function execute(query, params = []) { - return wrap(query, s => s.run(params)); +function execute(query: string, params: Params = []): RunResult { + return wrap(query, s => s.run(params)) as RunResult; } -function executeMany(query, params) { +function executeMany(query: string, params: Params) { if (LOG_ALL_QUERIES) { console.log(query); } @@ -177,7 +183,7 @@ function executeMany(query, params) { const curParams = params.slice(0, Math.min(params.length, PARAM_LIMIT)); params = params.slice(curParams.length); - const curParamsObj = {}; + const curParamsObj: Record = {}; let j = 1; for (const param of curParams) { @@ -192,7 +198,7 @@ function executeMany(query, params) { } } -function executeScript(query) { +function executeScript(query: string): DatabaseType { if (LOG_ALL_QUERIES) { console.log(query); } @@ -200,7 +206,7 @@ function executeScript(query) { return dbConnection.exec(query); } -function wrap(query, func) { +function wrap(query: string, func: (statement: Statement) => unknown): unknown { const startTimestamp = Date.now(); let result; @@ -211,7 +217,7 @@ function wrap(query, func) { try { result = func(stmt(query)); } - catch (e) { + catch (e: any) { if (e.message.includes("The database connection is not open")) { // this often happens on killing the app which puts these alerts in front of user // in these cases error should be simply ignored. @@ -237,12 +243,12 @@ function wrap(query, func) { return result; } -function transactional(func) { +function transactional(func: (statement: Statement) => T) { try { - const ret = dbConnection.transaction(func).deferred(); + const ret = (dbConnection.transaction(func) as any).deferred(); if (!dbConnection.inTransaction) { // i.e. transaction was really committed (and not just savepoint released) - require('./ws.js').sendTransactionEntityChangesToAllClients(); + require('./ws').sendTransactionEntityChangesToAllClients(); } return ret; @@ -257,13 +263,13 @@ function transactional(func) { } // the maxEntityChangeId has been incremented during failed transaction, need to recalculate - require('./entity_changes.js').recalculateMaxEntityChangeId(); + require('./entity_changes').recalculateMaxEntityChangeId(); throw e; } } -function fillParamList(paramIds, truncate = true) { +function fillParamList(paramIds: string[], truncate = true) { if (paramIds.length === 0) { return; } @@ -286,7 +292,7 @@ function fillParamList(paramIds, truncate = true) { s.run(paramIds); } -async function copyDatabase(targetFilePath) { +async function copyDatabase(targetFilePath: string) { try { fs.unlinkSync(targetFilePath); } catch (e) { @@ -295,7 +301,7 @@ async function copyDatabase(targetFilePath) { await dbConnection.backup(targetFilePath); } -function disableSlowQueryLogging(cb) { +function disableSlowQueryLogging(cb: () => T) { const orig = cls.isSlowQueryLoggingDisabled(); try { @@ -308,7 +314,7 @@ function disableSlowQueryLogging(cb) { } } -module.exports = { +export = { dbConnection, insert, replace, diff --git a/src/services/sql_init.js b/src/services/sql_init.js index f1b4e9152..da28b35d8 100644 --- a/src/services/sql_init.js +++ b/src/services/sql_init.js @@ -1,15 +1,15 @@ -const log = require('./log.js'); +const log = require('./log'); const fs = require('fs'); -const resourceDir = require('./resource_dir.js'); -const sql = require('./sql.js'); -const utils = require('./utils.js'); -const optionService = require('./options.js'); +const resourceDir = require('./resource_dir'); +const sql = require('./sql'); +const utils = require('./utils'); +const optionService = require('./options'); const port = require('./port.js'); -const BOption = require('../becca/entities/boption.js'); -const TaskContext = require('./task_context.js'); +const BOption = require('../becca/entities/boption'); +const TaskContext = require('./task_context'); const migrationService = require('./migration.js'); -const cls = require('./cls.js'); -const config = require('./config.js'); +const cls = require('./cls'); +const config = require('./config'); const dbReady = utils.deferred(); @@ -62,8 +62,8 @@ async function createInitialDatabase() { require('../becca/becca_loader.js').load(); - const BNote = require('../becca/entities/bnote.js'); - const BBranch = require('../becca/entities/bbranch.js'); + const BNote = require('../becca/entities/bnote'); + const BBranch = require('../becca/entities/bbranch'); log.info("Creating root note ..."); @@ -88,7 +88,7 @@ async function createInitialDatabase() { optionsInitService.initDocumentOptions(); optionsInitService.initNotSyncedOptions(true, {}); optionsInitService.initStartupOptions(); - require('./encryption/password.js').resetPassword(); + require('./encryption/password').resetPassword(); }); log.info("Importing demo content ..."); @@ -105,7 +105,7 @@ async function createInitialDatabase() { const startNoteId = sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition"); - const optionService = require('./options.js'); + const optionService = require('./options'); optionService.setOption('openNoteContexts', JSON.stringify([ { notePath: startNoteId, diff --git a/src/services/sync.js b/src/services/sync.js index fbafdfcfa..4410e571a 100644 --- a/src/services/sync.js +++ b/src/services/sync.js @@ -1,22 +1,22 @@ "use strict"; -const log = require('./log.js'); -const sql = require('./sql.js'); -const optionService = require('./options.js'); -const utils = require('./utils.js'); -const instanceId = require('./instance_id.js'); -const dateUtils = require('./date_utils.js'); +const log = require('./log'); +const sql = require('./sql'); +const optionService = require('./options'); +const utils = require('./utils'); +const instanceId = require('./instance_id'); +const dateUtils = require('./date_utils'); const syncUpdateService = require('./sync_update.js'); const contentHashService = require('./content_hash.js'); const appInfo = require('./app_info.js'); const syncOptions = require('./sync_options.js'); -const syncMutexService = require('./sync_mutex.js'); -const cls = require('./cls.js'); +const syncMutexService = require('./sync_mutex'); +const cls = require('./cls'); const request = require('./request.js'); -const ws = require('./ws.js'); -const entityChangesService = require('./entity_changes.js'); +const ws = require('./ws'); +const entityChangesService = require('./entity_changes'); const entityConstructor = require('../becca/entity_constructor.js'); -const becca = require('../becca/becca.js'); +const becca = require('../becca/becca'); let proxyToggle = true; diff --git a/src/services/sync_mutex.js b/src/services/sync_mutex.ts similarity index 88% rename from src/services/sync_mutex.js rename to src/services/sync_mutex.ts index fb95d03c4..9ad0fd08c 100644 --- a/src/services/sync_mutex.js +++ b/src/services/sync_mutex.ts @@ -6,7 +6,7 @@ const Mutex = require('async-mutex').Mutex; const instance = new Mutex(); -async function doExclusively(func) { +async function doExclusively(func: () => T) { const releaseMutex = await instance.acquire(); try { @@ -17,6 +17,6 @@ async function doExclusively(func) { } } -module.exports = { +export = { doExclusively }; diff --git a/src/services/sync_options.js b/src/services/sync_options.js index 7cf44c06f..a059be973 100644 --- a/src/services/sync_options.js +++ b/src/services/sync_options.js @@ -1,7 +1,7 @@ "use strict"; -const optionService = require('./options.js'); -const config = require('./config.js'); +const optionService = require('./options'); +const config = require('./config'); /* * Primary configuration for sync is in the options (document), but we allow to override diff --git a/src/services/sync_update.js b/src/services/sync_update.js index 94091e9ef..6b6bbf556 100644 --- a/src/services/sync_update.js +++ b/src/services/sync_update.js @@ -1,9 +1,9 @@ -const sql = require('./sql.js'); -const log = require('./log.js'); -const entityChangesService = require('./entity_changes.js'); -const eventService = require('./events.js'); +const sql = require('./sql'); +const log = require('./log'); +const entityChangesService = require('./entity_changes'); +const eventService = require('./events'); const entityConstructor = require('../becca/entity_constructor.js'); -const ws = require('./ws.js'); +const ws = require('./ws'); function updateEntities(entityChanges, instanceId) { if (entityChanges.length === 0) { diff --git a/src/services/task_context.js b/src/services/task_context.ts similarity index 77% rename from src/services/task_context.js rename to src/services/task_context.ts index 58530ffec..7a16a2645 100644 --- a/src/services/task_context.js +++ b/src/services/task_context.ts @@ -1,12 +1,20 @@ "use strict"; -const ws = require('./ws.js'); +import ws = require('./ws'); // taskId => TaskContext -const taskContexts = {}; +const taskContexts: Record = {}; class TaskContext { - constructor(taskId, taskType = null, data = {}) { + + private taskId: string; + private taskType: string | null; + private data: {} | null; + private progressCount: number; + private lastSentCountTs: number; + noteDeletionHandlerTriggered: boolean; + + constructor(taskId: string, taskType: string | null = null, data: {} | null = {}) { this.taskId = taskId; this.taskType = taskType; this.data = data; @@ -23,8 +31,7 @@ class TaskContext { this.increaseProgressCount(); } - /** @returns {TaskContext} */ - static getInstance(taskId, taskType, data = null) { + static getInstance(taskId: string, taskType: string, data: {} | null = null): TaskContext { if (!taskContexts[taskId]) { taskContexts[taskId] = new TaskContext(taskId, taskType, data); } @@ -48,7 +55,7 @@ class TaskContext { } } - reportError(message) { + reportError(message: string) { ws.sendMessageToAllClients({ type: 'taskError', taskId: this.taskId, @@ -58,7 +65,7 @@ class TaskContext { }); } - taskSucceeded(result) { + taskSucceeded(result: string) { ws.sendMessageToAllClients({ type: 'taskSucceeded', taskId: this.taskId, @@ -69,4 +76,4 @@ class TaskContext { } } -module.exports = TaskContext; +export = TaskContext; diff --git a/src/services/tray.js b/src/services/tray.js index bb32a78bb..7d8e34e1e 100644 --- a/src/services/tray.js +++ b/src/services/tray.js @@ -1,7 +1,7 @@ const { Menu, Tray } = require('electron'); const path = require('path'); const windowService = require('./window.js'); -const optionService = require('./options.js'); +const optionService = require('./options'); const UPDATE_TRAY_EVENTS = [ 'minimize', 'maximize', 'show', 'hide' diff --git a/src/services/tree.js b/src/services/tree.js index 3973b0f6e..66276ab2f 100644 --- a/src/services/tree.js +++ b/src/services/tree.js @@ -1,10 +1,10 @@ "use strict"; -const sql = require('./sql.js'); -const log = require('./log.js'); -const BBranch = require('../becca/entities/bbranch.js'); -const entityChangesService = require('./entity_changes.js'); -const becca = require('../becca/becca.js'); +const sql = require('./sql'); +const log = require('./log'); +const BBranch = require('../becca/entities/bbranch'); +const entityChangesService = require('./entity_changes'); +const becca = require('../becca/becca'); function validateParentChild(parentNoteId, childNoteId, branchId = null) { if (['root', '_hidden', '_share', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(childNoteId)) { diff --git a/src/services/utils.js b/src/services/utils.ts similarity index 72% rename from src/services/utils.js rename to src/services/utils.ts index 2e99c6ef0..dc6129770 100644 --- a/src/services/utils.js +++ b/src/services/utils.ts @@ -1,18 +1,18 @@ "use strict"; -const crypto = require('crypto'); +import crypto = require('crypto'); const randtoken = require('rand-token').generator({source: 'crypto'}); -const unescape = require('unescape'); -const escape = require('escape-html'); -const sanitize = require("sanitize-filename"); -const mimeTypes = require('mime-types'); -const path = require('path'); +import unescape = require('unescape'); +import escape = require('escape-html'); +import sanitize = require("sanitize-filename"); +import mimeTypes = require('mime-types'); +import path = require('path'); function newEntityId() { return randomString(12); } -function randomString(length) { +function randomString(length: number): string { return randtoken.generate(length); } @@ -20,11 +20,11 @@ function randomSecureToken(bytes = 32) { return crypto.randomBytes(bytes).toString('base64'); } -function md5(content) { +function md5(content: crypto.BinaryLike) { return crypto.createHash('md5').update(content).digest('hex'); } -function hashedBlobId(content) { +function hashedBlobId(content: string | Buffer) { if (content === null || content === undefined) { content = ""; } @@ -41,19 +41,16 @@ function hashedBlobId(content) { return kindaBase62Hash.substr(0, 20); } -function toBase64(plainText) { +function toBase64(plainText: string | Buffer) { return Buffer.from(plainText).toString('base64'); } -/** - * @returns {Buffer} - */ -function fromBase64(encodedText) { +function fromBase64(encodedText: string) { return Buffer.from(encodedText, 'base64'); } -function hmac(secret, value) { - const hmac = crypto.createHmac('sha256', Buffer.from(secret.toString(), 'ASCII')); +function hmac(secret: any, value: any) { + const hmac = crypto.createHmac('sha256', Buffer.from(secret.toString(), 'ascii')); hmac.update(value.toString()); return hmac.digest('base64'); } @@ -62,30 +59,30 @@ function isElectron() { return !!process.versions['electron']; } -function hash(text) { +function hash(text: string) { text = text.normalize(); return crypto.createHash('sha1').update(text).digest('base64'); } -function isEmptyOrWhitespace(str) { +function isEmptyOrWhitespace(str: string) { return str === null || str.match(/^ *$/) !== null; } -function sanitizeSqlIdentifier(str) { +function sanitizeSqlIdentifier(str: string) { return str.replace(/[^A-Za-z0-9_]/g, ""); } -function escapeHtml(str) { +function escapeHtml(str: string) { return escape(str); } -function unescapeHtml(str) { +function unescapeHtml(str: string) { return unescape(str); } -function toObject(array, fn) { - const obj = {}; +function toObject(array: T[], fn: (item: T) => [K, V]): Record { + const obj: Record = {} as Record; // TODO: unsafe? for (const item of array) { const ret = fn(item); @@ -96,12 +93,12 @@ function toObject(array, fn) { return obj; } -function stripTags(text) { +function stripTags(text: string) { return text.replace(/<(?:.|\n)*?>/gm, ''); } -function union(a, b) { - const obj = {}; +function union(a: T[], b: T[]): T[] { + const obj: Record = {} as Record; // TODO: unsafe? for (let i = a.length-1; i >= 0; i--) { obj[a[i]] = a[i]; @@ -111,7 +108,7 @@ function union(a, b) { obj[b[i]] = b[i]; } - const res = []; + const res: T[] = []; for (const k in obj) { if (obj.hasOwnProperty(k)) { // <-- optional @@ -122,7 +119,7 @@ function union(a, b) { return res; } -function escapeRegExp(str) { +function escapeRegExp(str: string) { return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); } @@ -135,7 +132,7 @@ function crash() { } } -function sanitizeFilenameForHeader(filename) { +function sanitizeFilenameForHeader(filename: string) { let sanitizedFilename = sanitize(filename); if (sanitizedFilename.trim().length === 0) { @@ -145,7 +142,7 @@ function sanitizeFilenameForHeader(filename) { return encodeURIComponent(sanitizedFilename); } -function getContentDisposition(filename) { +function getContentDisposition(filename: string) { const sanitizedFilename = sanitizeFilenameForHeader(filename); return `file; filename="${sanitizedFilename}"; filename*=UTF-8''${sanitizedFilename}`; @@ -159,24 +156,24 @@ const STRING_MIME_TYPES = [ "image/svg+xml" ]; -function isStringNote(type, mime) { +function isStringNote(type: string, mime: string) { // render and book are string note in the sense that they are expected to contain empty string return ["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas"].includes(type) || mime.startsWith('text/') || STRING_MIME_TYPES.includes(mime); } -function quoteRegex(url) { +function quoteRegex(url: string) { return url.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); } -function replaceAll(string, replaceWhat, replaceWith) { +function replaceAll(string: string, replaceWhat: string, replaceWith: string) { const quotedReplaceWhat = quoteRegex(replaceWhat); return string.replace(new RegExp(quotedReplaceWhat, "g"), replaceWith); } -function formatDownloadTitle(fileName, type, mime) { +function formatDownloadTitle(fileName: string, type: string, mime: string) { if (!fileName) { fileName = "untitled"; } @@ -218,7 +215,7 @@ function formatDownloadTitle(fileName, type, mime) { } } -function removeTextFileExtension(filePath) { +function removeTextFileExtension(filePath: string) { const extension = path.extname(filePath).toLowerCase(); if (extension === '.md' || extension === '.markdown' || extension === '.html') { @@ -229,7 +226,7 @@ function removeTextFileExtension(filePath) { } } -function getNoteTitle(filePath, replaceUnderscoresWithSpaces, noteMeta) { +function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta: { title: string }) { if (noteMeta) { return noteMeta.title; } else { @@ -241,7 +238,7 @@ function getNoteTitle(filePath, replaceUnderscoresWithSpaces, noteMeta) { } } -function timeLimit(promise, limitMs, errorMessage) { +function timeLimit(promise: Promise, limitMs: number, errorMessage: string): Promise { if (!promise || !promise.then) { // it's not actually a promise return promise; } @@ -267,23 +264,28 @@ function timeLimit(promise, limitMs, errorMessage) { }); } -function deferred() { - return (() => { - let resolve, reject; +interface DeferredPromise extends Promise { + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void +} - let promise = new Promise((res, rej) => { +function deferred(): DeferredPromise { + return (() => { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: any) => void; + + let promise = new Promise((res, rej) => { resolve = res; reject = rej; - }); + }) as DeferredPromise; promise.resolve = resolve; promise.reject = reject; - - return promise; + return promise as DeferredPromise; })(); } -function removeDiacritic(str) { +function removeDiacritic(str: string) { if (!str) { return ""; } @@ -291,12 +293,12 @@ function removeDiacritic(str) { return str.normalize("NFD").replace(/\p{Diacritic}/gu, ""); } -function normalize(str) { +function normalize(str: string) { return removeDiacritic(str).toLowerCase(); } -function toMap(list, key) { - const map = {}; +function toMap>(list: T[], key: keyof T): Record { + const map: Record = {}; for (const el of list) { map[el[key]] = el; @@ -305,11 +307,11 @@ function toMap(list, key) { return map; } -function isString(x) { +function isString(x: any) { return Object.prototype.toString.call(x) === "[object String]"; } -module.exports = { +export = { randomSecureToken, randomString, md5, diff --git a/src/services/window.js b/src/services/window.js index 68f70010a..f855175c5 100644 --- a/src/services/window.js +++ b/src/services/window.js @@ -1,11 +1,11 @@ const path = require('path'); const url = require("url"); const port = require('./port.js'); -const optionService = require('./options.js'); -const env = require('./env.js'); -const log = require('./log.js'); +const optionService = require('./options'); +const env = require('./env'); +const log = require('./log'); const sqlInit = require('./sql_init.js'); -const cls = require('./cls.js'); +const cls = require('./cls'); const keyboardActionsService = require('./keyboard_actions.js'); const {ipcMain} = require('electron'); diff --git a/src/services/ws.js b/src/services/ws.ts similarity index 74% rename from src/services/ws.js rename to src/services/ws.ts index 9dead1866..87caddcba 100644 --- a/src/services/ws.js +++ b/src/services/ws.ts @@ -1,15 +1,17 @@ -const WebSocket = require('ws'); -const utils = require('./utils.js'); -const log = require('./log.js'); -const sql = require('./sql.js'); -const cls = require('./cls.js'); -const config = require('./config.js'); -const syncMutexService = require('./sync_mutex.js'); -const protectedSessionService = require('./protected_session.js'); -const becca = require('../becca/becca.js'); -const AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity.js'); +import WebSocket = require('ws'); +import utils = require('./utils'); +import log = require('./log'); +import sql = require('./sql'); +import cls = require('./cls'); +import config = require('./config'); +import syncMutexService = require('./sync_mutex'); +import protectedSessionService = require('./protected_session'); +import becca = require('../becca/becca'); +import AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity'); -const env = require('./env.js'); +import env = require('./env'); +import { IncomingMessage, Server } from 'http'; +import { EntityChange } from './entity_changes_interface'; if (env.isDev()) { const chokidar = require('chokidar'); const debounce = require('debounce'); @@ -21,15 +23,32 @@ if (env.isDev()) { .on('unlink', debouncedReloadFrontend); } -let webSocketServer; -let lastSyncedPush = null; +let webSocketServer!: WebSocket.Server; +let lastSyncedPush: number | null = null; -function init(httpServer, sessionParser) { +interface Message { + type: string; + data?: { + lastSyncedPush?: number | null, + entityChanges?: any[] + } | null, + lastSyncedPush?: number | null, + + progressCount?: number; + taskId?: string; + taskType?: string | null; + message?: string; + reason?: string; + result?: string; +} + +type SessionParser = (req: IncomingMessage, params: {}, cb: () => void) => void; +function init(httpServer: Server, sessionParser: SessionParser) { webSocketServer = new WebSocket.Server({ verifyClient: (info, done) => { sessionParser(info.req, {}, () => { const allowed = utils.isElectron() - || info.req.session.loggedIn + || (info.req as any).session.loggedIn || (config.General && config.General.noAuthentication); if (!allowed) { @@ -43,12 +62,12 @@ function init(httpServer, sessionParser) { }); webSocketServer.on('connection', (ws, req) => { - ws.id = utils.randomString(10); + (ws as any).id = utils.randomString(10); console.log(`websocket client connected`); ws.on('message', async messageJson => { - const message = JSON.parse(messageJson); + const message = JSON.parse(messageJson as any); if (message.type === 'log-error') { log.info(`JS Error: ${message.error}\r @@ -73,7 +92,7 @@ Stack: ${message.stack}`); }); } -function sendMessage(client, message) { +function sendMessage(client: WebSocket, message: Message) { const jsonStr = JSON.stringify(message); if (client.readyState === WebSocket.OPEN) { @@ -81,7 +100,7 @@ function sendMessage(client, message) { } } -function sendMessageToAllClients(message) { +function sendMessageToAllClients(message: Message) { const jsonStr = JSON.stringify(message); if (webSocketServer) { @@ -97,7 +116,7 @@ function sendMessageToAllClients(message) { } } -function fillInAdditionalProperties(entityChange) { +function fillInAdditionalProperties(entityChange: EntityChange) { if (entityChange.isErased) { return; } @@ -123,14 +142,14 @@ function fillInAdditionalProperties(entityChange) { if (!entityChange.entity) { entityChange.entity = sql.getRow(`SELECT * FROM notes WHERE noteId = ?`, [entityChange.entityId]); - if (entityChange.entity.isProtected) { - entityChange.entity.title = protectedSessionService.decryptString(entityChange.entity.title); + if (entityChange.entity?.isProtected) { + entityChange.entity.title = protectedSessionService.decryptString(entityChange.entity.title || ""); } } } else if (entityChange.entityName === 'revisions') { - entityChange.noteId = sql.getValue(`SELECT noteId - FROM revisions - WHERE revisionId = ?`, [entityChange.entityId]); + entityChange.noteId = sql.getValue(`SELECT noteId + FROM revisions + WHERE revisionId = ?`, [entityChange.entityId]); } else if (entityChange.entityName === 'note_reordering') { entityChange.positions = {}; @@ -138,7 +157,9 @@ function fillInAdditionalProperties(entityChange) { if (parentNote) { for (const childBranch of parentNote.getChildBranches()) { - entityChange.positions[childBranch.branchId] = childBranch.notePosition; + if (childBranch?.branchId) { + entityChange.positions[childBranch.branchId] = childBranch.notePosition; + } } } } else if (entityChange.entityName === 'options') { @@ -160,7 +181,7 @@ function fillInAdditionalProperties(entityChange) { } // entities with higher number can reference the entities with lower number -const ORDERING = { +const ORDERING: Record = { "etapi_tokens": 0, "attributes": 2, "branches": 2, @@ -172,14 +193,17 @@ const ORDERING = { "options": 0 }; -function sendPing(client, entityChangeIds = []) { +function sendPing(client: WebSocket, entityChangeIds = []) { if (entityChangeIds.length === 0) { sendMessage(client, { type: 'ping' }); return; } - const entityChanges = sql.getManyRows(`SELECT * FROM entity_changes WHERE id IN (???)`, entityChangeIds); + const entityChanges = sql.getManyRows(`SELECT * FROM entity_changes WHERE id IN (???)`, entityChangeIds); + if (!entityChanges) { + return; + } // sort entity changes since froca expects "referential order", i.e. referenced entities should already exist // in froca. @@ -190,7 +214,7 @@ function sendPing(client, entityChangeIds = []) { try { fillInAdditionalProperties(entityChange); } - catch (e) { + catch (e: any) { log.error(`Could not fill additional properties for entity change ${JSON.stringify(entityChange)} because of error: ${e.message}: ${e.stack}`); } } @@ -228,15 +252,15 @@ function syncFailed() { sendMessageToAllClients({ type: 'sync-failed', lastSyncedPush }); } -function reloadFrontend(reason) { +function reloadFrontend(reason: string) { sendMessageToAllClients({ type: 'reload-frontend', reason }); } -function setLastSyncedPush(entityChangeId) { +function setLastSyncedPush(entityChangeId: number) { lastSyncedPush = entityChangeId; } -module.exports = { +export = { init, sendMessageToAllClients, syncPushInProgress, diff --git a/src/share/routes.js b/src/share/routes.js index 2805933a8..9e1eb5d17 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -11,7 +11,7 @@ const assetPath = require('../services/asset_path.js'); const appPath = require('../services/app_path.js'); const searchService = require('../services/search/services/search.js'); const SearchContext = require('../services/search/search_context.js'); -const log = require('../services/log.js'); +const log = require('../services/log'); /** * @param {SNote} note @@ -236,7 +236,7 @@ function register(router) { addNoIndexHeader(note, res); - const utils = require('../services/utils.js'); + const utils = require('../services/utils'); const filename = utils.formatDownloadTitle(note.title, note.type, note.mime); @@ -304,7 +304,7 @@ function register(router) { addNoIndexHeader(attachment.note, res); - const utils = require('../services/utils.js'); + const utils = require('../services/utils'); const filename = utils.formatDownloadTitle(attachment.title, null, attachment.mime); diff --git a/src/share/shaca/entities/sattachment.js b/src/share/shaca/entities/sattachment.js index 46504ba14..4b76fea2c 100644 --- a/src/share/shaca/entities/sattachment.js +++ b/src/share/shaca/entities/sattachment.js @@ -1,7 +1,7 @@ "use strict"; -const sql = require('../../sql.js'); -const utils = require('../../../services/utils.js'); +const sql = require('../../sql'); +const utils = require('../../../services/utils'); const AbstractShacaEntity = require('./abstract_shaca_entity.js'); class SAttachment extends AbstractShacaEntity { diff --git a/src/share/shaca/entities/snote.js b/src/share/shaca/entities/snote.js index 167f238d0..fd889c23f 100644 --- a/src/share/shaca/entities/snote.js +++ b/src/share/shaca/entities/snote.js @@ -1,7 +1,7 @@ "use strict"; -const sql = require('../../sql.js'); -const utils = require('../../../services/utils.js'); +const sql = require('../../sql'); +const utils = require('../../../services/utils'); const AbstractShacaEntity = require('./abstract_shaca_entity.js'); const escape = require('escape-html'); diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.js index 00ba63182..5d1648f49 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.js @@ -1,14 +1,14 @@ "use strict"; -const sql = require('../sql.js'); +const sql = require('../sql'); const shaca = require('./shaca.js'); -const log = require('../../services/log.js'); +const log = require('../../services/log'); const SNote = require('./entities/snote.js'); const SBranch = require('./entities/sbranch.js'); const SAttribute = require('./entities/sattribute.js'); const SAttachment = require('./entities/sattachment.js'); const shareRoot = require('../share_root.js'); -const eventService = require('../../services/events.js'); +const eventService = require('../../services/events'); function load() { const start = Date.now(); diff --git a/src/share/sql.js b/src/share/sql.js index 07dd2fd85..485c87921 100644 --- a/src/share/sql.js +++ b/src/share/sql.js @@ -1,7 +1,7 @@ "use strict"; const Database = require('better-sqlite3'); -const dataDir = require('../services/data_dir.js'); +const dataDir = require('../services/data_dir'); const dbConnection = new Database(dataDir.DOCUMENT_PATH, { readonly: true }); diff --git a/src/tools/generate_document.js b/src/tools/generate_document.js index 67f202a2b..844b6e0f3 100644 --- a/src/tools/generate_document.js +++ b/src/tools/generate_document.js @@ -7,7 +7,7 @@ require('../becca/entity_constructor.js'); const sqlInit = require('../services/sql_init.js'); const noteService = require('../services/notes.js'); const attributeService = require('../services/attributes.js'); -const cls = require('../services/cls.js'); +const cls = require('../services/cls'); const cloningService = require('../services/cloning.js'); const loremIpsum = require('lorem-ipsum').loremIpsum; diff --git a/src/types/unescape.d.ts b/src/types/unescape.d.ts new file mode 100644 index 000000000..465ebd9e9 --- /dev/null +++ b/src/types/unescape.d.ts @@ -0,0 +1,4 @@ +declare module 'unescape' { + function unescape(str: string, type?: string): string; + export = unescape; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..0ce95ff39 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "moduleResolution": "Node", + "declaration": false, + "sourceMap": true, + "outDir": "./dist", + "strict": true, + "noImplicitAny": true, + "lib": ["ES2022"] + }, + "include": [ + "./src/**/*.js", + "./src/**/*.ts" + ], + "exclude": ["./node_modules/**/*"], + "ts-node": { + "files": true + }, + "files": [ + "src/types/unescape.d.ts" + ] + }