Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit e51e66c

Browse files
authored
Merge branch 'develop' into renovate/eslint-plugin-playwright-2.x
2 parents 2dbae31 + 05ad066 commit e51e66c

23 files changed

Lines changed: 859 additions & 203 deletions

.github/workflows/dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939

4040
- uses: nrwl/nx-set-shas@v4
4141
- name: Check affected
42-
run: pnpm nx affected -t rebuild-deps
42+
run: pnpm nx affected -t build rebuild-deps
4343

4444
report-electron-size:
4545
name: Report Electron size

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ TriliumNext Notes is an open-source, cross-platform hierarchical note taking app
88

99
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for quick overview:
1010

11-
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="https://github.com/TriliumNext/Notes/blob/develop/images/screenshots/app.png?raw=true" alt="Trilium Screenshot" width="1000"></a>
11+
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
1212

1313
## ⚠️ Why TriliumNext?
1414

_regroup/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"chore:generate-openapi": "tsx bin/generate-openapi.js"
3636
},
3737
"devDependencies": {
38-
"@playwright/test": "1.51.1",
38+
"@playwright/test": "1.52.0",
3939
"@stylistic/eslint-plugin": "4.2.0",
4040
"@types/express": "5.0.1",
4141
"@types/node": "22.15.3",

apps/db-compare/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Database compare tool
2+
3+
> [!IMPORTANT]
4+
> The original implementation was signficantly out of date. While we have made the effort of updating dependencies and getting it to run, currently it only compares the old database structure (v214).
5+
6+
To build and run manually:
7+
8+
```sh
9+
nx build db-compare
10+
node ./apps/db-compare/dist/compare.js
11+
```
12+
13+
To serve development build with arguments:
14+
15+
```sh
16+
nx serve db-compare --args "apps/server/spec/db/document_v214.db" --args "apps/server/spec/db/document_v214_migrated.db"
17+
```

apps/db-compare/eslint.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import baseConfig from "../../eslint.config.mjs";
2+
3+
export default [
4+
...baseConfig
5+
];

apps/db-compare/package.json

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"name": "@triliumnext/db-compare",
3+
"version": "0.0.1",
4+
"private": true,
5+
"description": "Tool to compare content of Trilium databases. Useful for debugging sync problems.",
6+
"dependencies": {
7+
"colors": "1.4.0",
8+
"diff": "7.0.0",
9+
"sqlite": "5.1.1",
10+
"sqlite3": "5.1.5"
11+
},
12+
"nx": {
13+
"name": "db-compare",
14+
"targets": {
15+
"build": {
16+
"executor": "@nx/esbuild:esbuild",
17+
"outputs": [
18+
"{options.outputPath}"
19+
],
20+
"defaultConfiguration": "production",
21+
"options": {
22+
"platform": "node",
23+
"outputPath": "apps/db-compare/dist",
24+
"format": [
25+
"cjs"
26+
],
27+
"bundle": false,
28+
"main": "apps/db-compare/src/compare.ts",
29+
"tsConfig": "apps/db-compare/tsconfig.app.json",
30+
"assets": [],
31+
"esbuildOptions": {
32+
"sourcemap": true,
33+
"outExtension": {
34+
".js": ".js"
35+
}
36+
}
37+
},
38+
"configurations": {
39+
"development": {},
40+
"production": {
41+
"esbuildOptions": {
42+
"sourcemap": false,
43+
"outExtension": {
44+
".js": ".js"
45+
}
46+
}
47+
}
48+
}
49+
},
50+
"serve": {
51+
"executor": "@nx/js:node",
52+
"defaultConfiguration": "development",
53+
"dependsOn": [
54+
"build"
55+
],
56+
"options": {
57+
"buildTarget": "db-compare:build",
58+
"runBuildTargetDependencies": false
59+
},
60+
"configurations": {
61+
"development": {
62+
"buildTarget": "db-compare:build:development"
63+
},
64+
"production": {
65+
"buildTarget": "db-compare:build:production"
66+
}
67+
}
68+
}
69+
}
70+
},
71+
"devDependencies": {
72+
"@types/colors": "1.2.4",
73+
"@types/diff": "^7.0.2"
74+
}
75+
}

apps/db-compare/src/compare.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"use strict";
2+
3+
import * as jsDiff from "diff";
4+
import * as sqlite from "sqlite";
5+
import * as sqlite3 from "sqlite3";
6+
import sql from "./sql.js";
7+
8+
import "colors";
9+
import path from "path";
10+
11+
function printDiff(one: string, two: string) {
12+
const diff = jsDiff.diffChars(one, two);
13+
14+
diff.forEach(function(part){
15+
// green for additions, red for deletions
16+
// grey for common parts
17+
const color = part.added ? 'green' :
18+
part.removed ? 'red' : 'grey';
19+
process.stderr.write(part.value[color]);
20+
});
21+
22+
console.log("");
23+
}
24+
25+
function checkMissing(table: string, name: string, ids1: string[], ids2: string[]) {
26+
const missing = ids1.filter(item => ids2.indexOf(item) < 0);
27+
28+
if (missing.length > 0) {
29+
console.log("Missing IDs from " + name + " table " + table + ": ", missing);
30+
}
31+
}
32+
33+
function handleBuffer(obj: { content: Buffer | string }) {
34+
if (obj && Buffer.isBuffer(obj.content)) {
35+
obj.content = obj.content.toString();
36+
}
37+
38+
return obj;
39+
}
40+
41+
function compareRows(table: string, rsLeft: Record<string, any>, rsRight: Record<string, any>, column: string) {
42+
const leftIds = Object.keys(rsLeft);
43+
const rightIds = Object.keys(rsRight);
44+
45+
console.log("");
46+
console.log("--------------------------------------------------------");
47+
console.log(`${table} - ${leftIds.length}/${rightIds.length}`);
48+
49+
checkMissing(table, "right", leftIds, rightIds);
50+
checkMissing(table, "left", rightIds, leftIds);
51+
52+
const commonIds = leftIds.filter(item => rightIds.includes(item));
53+
54+
for (const id of commonIds) {
55+
const valueLeft = handleBuffer(rsLeft[id]);
56+
const valueRight = handleBuffer(rsRight[id]);
57+
58+
const left = JSON.stringify(valueLeft, null, 2);
59+
const right = JSON.stringify(valueRight, null, 2);
60+
61+
if (left !== right) {
62+
console.log("Table " + table + " row with " + column + "=" + id + " differs:");
63+
console.log("Left: ", left);
64+
console.log("Right: ", right);
65+
printDiff(left, right);
66+
}
67+
}
68+
}
69+
70+
async function main() {
71+
const dbLeftPath = process.argv.at(-2);
72+
const dbRightPath = process.argv.at(-1);
73+
74+
if (process.argv.length < 4 || !dbLeftPath || !dbRightPath) {
75+
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} path/to/first.db path/to/second.db`);
76+
process.exit(1);
77+
}
78+
79+
let dbLeft: sqlite.Database;
80+
let dbRight: sqlite.Database;
81+
82+
try {
83+
dbLeft = await sqlite.open({filename: dbLeftPath, driver: sqlite3.Database});
84+
} catch (e: any) {
85+
console.error(`Could not load first database at ${path.resolve(dbRightPath)} due to: ${e.message}`);
86+
process.exit(2);
87+
}
88+
89+
try {
90+
dbRight = await sqlite.open({filename: dbRightPath, driver: sqlite3.Database});
91+
} catch (e: any) {
92+
console.error(`Could not load second database at ${path.resolve(dbRightPath)} due to: ${e.message}`);
93+
process.exit(3);
94+
}
95+
96+
async function compare(table: string, column: string, query: string) {
97+
const rsLeft = await sql.getIndexed(dbLeft, column, query);
98+
const rsRight = await sql.getIndexed(dbRight, column, query);
99+
100+
compareRows(table, rsLeft, rsRight, column);
101+
}
102+
103+
await compare("branches", "branchId",
104+
"SELECT branchId, noteId, parentNoteId, notePosition, utcDateModified, isDeleted, prefix FROM branches");
105+
106+
await compare("notes", "noteId",
107+
"SELECT noteId, title, dateCreated, utcDateCreated, isProtected, isDeleted FROM notes WHERE isDeleted = 0");
108+
109+
await compare("note_contents", "noteId",
110+
"SELECT note_contents.noteId, note_contents.content FROM note_contents JOIN notes USING(noteId) WHERE isDeleted = 0");
111+
112+
await compare("note_revisions", "noteRevisionId",
113+
"SELECT noteRevisionId, noteId, title, dateCreated, dateLastEdited, utcDateCreated, utcDateLastEdited, isProtected FROM note_revisions");
114+
115+
await compare("note_revision_contents", "noteRevisionId",
116+
"SELECT noteRevisionId, content FROM note_revision_contents");
117+
118+
await compare("options", "name",
119+
`SELECT name, value, utcDateModified FROM options WHERE isSynced = 1`);
120+
121+
await compare("attributes", "attributeId",
122+
"SELECT attributeId, noteId, type, name, value FROM attributes");
123+
124+
await compare("etapi_tokens", "etapiTokenId",
125+
"SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified, isDeleted FROM etapi_tokens");
126+
127+
await compare("entity_changes", "uniqueId",
128+
"SELECT entityName || '-' || entityId AS uniqueId, hash, isErased, utcDateChanged FROM entity_changes WHERE isSynced = 1");
129+
}
130+
131+
(async () => {
132+
try {
133+
await main();
134+
} catch (e) {
135+
console.error(e);
136+
}
137+
})();

apps/db-compare/src/sql.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use strict";
2+
3+
import type { Database } from "sqlite";
4+
5+
async function getSingleResult(db: Database, query: string, params: any[] = []) {
6+
return await wrap(db, async db => db.get(query, ...params));
7+
}
8+
9+
async function getSingleResultOrNull(db: Database, query: string, params: any[] = []) {
10+
const all = await wrap(db, async db => db.all(query, ...params));
11+
12+
return all.length > 0 ? all[0] : null;
13+
}
14+
15+
async function getSingleValue(db: Database, query: string, params: any[] = []) {
16+
const row = await getSingleResultOrNull(db, query, params);
17+
18+
if (!row) {
19+
return null;
20+
}
21+
22+
return row[Object.keys(row)[0]];
23+
}
24+
25+
async function getResults(db: Database, query: string, params: any[] = []) {
26+
return await wrap(db, async db => db.all(query, ...params));
27+
}
28+
29+
async function getIndexed(db: Database, column: string, query: string, params: any[] = []) {
30+
const results = await getResults(db, query, params);
31+
32+
const map: Record<string, any> = {};
33+
34+
for (const row of results) {
35+
map[row[column]] = row;
36+
}
37+
38+
return map;
39+
}
40+
41+
async function getMap(db: Database, query: string, params: any[] = []) {
42+
const map: Record<string, any> = {};
43+
const results = await getResults(db, query, params);
44+
45+
for (const row of results) {
46+
const keys = Object.keys(row);
47+
48+
map[row[keys[0]]] = row[keys[1]];
49+
}
50+
51+
return map;
52+
}
53+
54+
async function getFlattenedResults(db: Database, key: string, query: string, params: any[] = []) {
55+
const list = [];
56+
const result = await getResults(db, query, params);
57+
58+
for (const row of result) {
59+
list.push(row[key]);
60+
}
61+
62+
return list;
63+
}
64+
65+
async function execute(db: Database, query: string, params: any[] = []) {
66+
return await wrap(db, async db => db.run(query, ...params));
67+
}
68+
69+
async function wrap<T>(db: Database, func: (db: Database) => Promise<T>) {
70+
const thisError = new Error();
71+
72+
try {
73+
return await func(db);
74+
} catch (e: any) {
75+
console.error("Error executing query. Inner exception: " + e.stack + thisError.stack);
76+
77+
throw thisError;
78+
}
79+
}
80+
81+
export default {
82+
getSingleValue,
83+
getSingleResult,
84+
getSingleResultOrNull,
85+
getResults,
86+
getIndexed,
87+
getMap,
88+
getFlattenedResults,
89+
execute
90+
};

apps/db-compare/tsconfig.app.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"types": [
6+
"node"
7+
],
8+
"rootDir": "src",
9+
"tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo",
10+
"verbatimModuleSyntax": false
11+
},
12+
"include": [
13+
"src/**/*.ts"
14+
],
15+
"exclude": [
16+
"eslint.config.js",
17+
"eslint.config.cjs",
18+
"eslint.config.mjs"
19+
]
20+
}

apps/db-compare/tsconfig.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"files": [],
4+
"include": [],
5+
"references": [
6+
{
7+
"path": "./tsconfig.app.json"
8+
}
9+
]
10+
}

0 commit comments

Comments
 (0)