Compare commits

...

5 Commits

Author SHA1 Message Date
vorotamoroz
bb77426b7b update dependencies and bump 2026-06-02 12:50:46 +01:00
vorotamoroz
1bef5fbef3 Merge branch 'fix_warns' into improve_first_fetch 2026-06-02 12:36:32 +01:00
vorotamoroz
7d2ba1b0b9 ### Improved
- Database fetching (a.k.a. Reset Synchronisation on This Device) on the initialisation now supports streaming and is faster (CouchDB only)
- The database fetching process has been streamlined, and database operations are now suspended until it has been completed
- The initial synchronisation process has been simplified, making it easier to synchronise files with the remote server
- We can select the remote database to fetch from during the initialisation, when there are multiple remote databases configured (e.g. multiple CouchDBs or S3 remotes)
2026-06-02 12:34:46 +01:00
vorotamoroz
0e6dd300ef Merge branch 'fix_warns' into improve_first_fetch 2026-06-01 11:22:01 +01:00
vorotamoroz
5a280c7919 (feat): Bulk database fetching is now work in progress. This feature is expected to speed up rebuilds and setups. (WIP) 2026-06-01 10:41:48 +01:00
11 changed files with 928 additions and 173 deletions

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.25.70-patch2",
"version": "0.25.70-patch3",
"minAppVersion": "1.7.2",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz",

321
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "0.25.70-patch2",
"version": "0.25.70-patch3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.25.70-patch2",
"version": "0.25.70-patch3",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.808.0",
@@ -52,9 +52,9 @@
"@types/transform-pouch": "^1.0.6",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@vitest/browser": "^4.1.1",
"@vitest/browser-playwright": "^4.1.1",
"@vitest/coverage-v8": "^4.1.1",
"@vitest/browser": "^4.1.8",
"@vitest/browser-playwright": "^4.1.8",
"@vitest/coverage-v8": "^4.1.8",
"dotenv-cli": "^11.0.0",
"esbuild": "0.25.0",
"esbuild-plugin-inline-worker": "^0.1.1",
@@ -91,7 +91,7 @@
"typescript": "5.9.3",
"vite": "^7.3.1",
"vite-plugin-istanbul": "^8.0.0",
"vitest": "^4.1.1",
"vitest": "^4.1.8",
"webdriverio": "^9.27.0",
"yaml": "^2.8.2"
}
@@ -1852,9 +1852,9 @@
"license": "MIT"
},
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1933,9 +1933,9 @@
"license": "MIT"
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3547,13 +3547,13 @@
}
},
"node_modules/@smithy/core": {
"version": "3.24.5",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.5.tgz",
"integrity": "sha512-Kt8phUg45M15EjhYAbZ+fFikYneijLu9Liugz8ZsYz2i8j0hzGv27LWKpEHYRfvj+LyCOSijpcR/2i8RouV+cA==",
"version": "3.24.6",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.6.tgz",
"integrity": "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/crc32": "5.2.0",
"@smithy/types": "^4.14.2",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
},
"engines": {
@@ -3720,12 +3720,12 @@
}
},
"node_modules/@smithy/is-array-buffer": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.3.tgz",
"integrity": "sha512-RRxYqjUa/n8dRVkbhyuiRarppLzt4H/AtMUEFmiHlDy8o4wrgqAdzxsk9naemzu6iX67ZV375fNmX7Q8dynGKw==",
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.6.tgz",
"integrity": "sha512-/cSYHP8jPffkhBClQzH9fAJujIh8dwMwg2swrVF4stXQsUWO5Oi2bwyaMUcBPIyulUI5IxaJFxd9C8UQX+YZsQ==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^3.24.3",
"@smithy/core": "^3.24.6",
"tslib": "^2.6.2"
},
"engines": {
@@ -3989,9 +3989,9 @@
}
},
"node_modules/@smithy/types": {
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz",
"integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==",
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.3.tgz",
"integrity": "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -4053,12 +4053,12 @@
}
},
"node_modules/@smithy/util-buffer-from": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.3.tgz",
"integrity": "sha512-5xlgilVaX96HdVlLZymKUa7vOTZtisOTxBJloM2J4PeRqyAWBeFIq0DnIxQISvwxT4rgJAvk7rHhB+GlCCKe8g==",
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.6.tgz",
"integrity": "sha512-sms/ty2CJwHOiGzEaAVWizTVK5KusXpAYqCUeXIa+hWtNKLwjimH4z11mc07d0Fe3DT3lmZJIZWOMcVQ/N4hBQ==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^3.24.3",
"@smithy/core": "^3.24.6",
"tslib": "^2.6.2"
},
"engines": {
@@ -4194,12 +4194,12 @@
}
},
"node_modules/@smithy/util-utf8": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.3.tgz",
"integrity": "sha512-c1QpRBn3aMsoqE64dd4Imgjy8Pynfw+eR7GkjElquxUFSnezwYVaOFm8JcYa+Bo/5ssbEyPKcT3+4bmrWYh6eQ==",
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.6.tgz",
"integrity": "sha512-tAa4sePYB7mlJzdYbdBqdv37KwFKWixmM/r3ihcI0HFOVjf+a5oGvtcLXcGm4S1bY4DFsLAIOHgjubtp+oRufw==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^3.24.3",
"@smithy/core": "^3.24.6",
"tslib": "^2.6.2"
},
"engines": {
@@ -4994,47 +4994,46 @@
}
},
"node_modules/@vitest/browser": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.1.tgz",
"integrity": "sha512-gjjrFC4+kPVK/fN9URDJWrssU5Gqh8Az8pKG/NSfQ2V+ky8b/y1BgBg0Ug13+hOGp5pzInonmGRPn7vOgSLgzA==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.8.tgz",
"integrity": "sha512-u21VzX07HzlJYpFgkxmjEXar/tG2UqWGgyGG/46SrrPc7rSdCTPw5vuowopO9CIqF8UCUQzDFdbVnNpw6N0BfQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@blazediff/core": "1.9.1",
"@vitest/mocker": "4.1.1",
"@vitest/utils": "4.1.1",
"@vitest/mocker": "4.1.8",
"@vitest/utils": "4.1.8",
"magic-string": "^0.30.21",
"pngjs": "^7.0.0",
"sirv": "^3.0.2",
"tinyrainbow": "^3.0.3",
"tinyrainbow": "^3.1.0",
"ws": "^8.19.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"vitest": "4.1.1"
"vitest": "4.1.8"
}
},
"node_modules/@vitest/browser-playwright": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.1.tgz",
"integrity": "sha512-dtVSBZZha2k/7P7EAXXrEAoxuIKl8Yv9f2Dk4GN/DGfmhf4DQvkvu+57okR2wq/gan1xppKjL/aBxK/kbYrbGw==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.8.tgz",
"integrity": "sha512-SR7FqgegaexEg73xvf3ArtygXegagMdXnL0EZMpxrWvvhQxvicD/E8p0ib0J91riPRtQUViyh67Xjw3NqvyhVg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/browser": "4.1.1",
"@vitest/mocker": "4.1.1",
"tinyrainbow": "^3.0.3"
"@vitest/browser": "4.1.8",
"@vitest/mocker": "4.1.8",
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"playwright": "*",
"vitest": "4.1.1"
"vitest": "4.1.8"
},
"peerDependenciesMeta": {
"playwright": {
@@ -5043,14 +5042,15 @@
}
},
"node_modules/@vitest/coverage-v8": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.1.tgz",
"integrity": "sha512-nZ4RWwGCoGOQRMmU/Q9wlUY540RVRxJZ9lxFsFfy0QV7Zmo5VVBhB6Sl9Xa0KIp2iIs3zWfPlo9LcY1iqbpzCw==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz",
"integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.1.1",
"@vitest/utils": "4.1.8",
"ast-v8-to-istanbul": "^1.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
@@ -5058,14 +5058,14 @@
"magicast": "^0.5.2",
"obug": "^2.1.1",
"std-env": "^4.0.0-rc.1",
"tinyrainbow": "^3.0.3"
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "4.1.1",
"vitest": "4.1.1"
"@vitest/browser": "4.1.8",
"vitest": "4.1.8"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -5074,41 +5074,31 @@
}
},
"node_modules/@vitest/expect": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz",
"integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz",
"integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.1.1",
"@vitest/utils": "4.1.1",
"@vitest/spy": "4.1.8",
"@vitest/utils": "4.1.8",
"chai": "^6.2.2",
"tinyrainbow": "^3.0.3"
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/expect/node_modules/chai": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@vitest/mocker": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz",
"integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz",
"integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.1.1",
"@vitest/spy": "4.1.8",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -5129,26 +5119,26 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz",
"integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz",
"integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^3.0.3"
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz",
"integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz",
"integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.1.1",
"@vitest/utils": "4.1.8",
"pathe": "^2.0.3"
},
"funding": {
@@ -5156,14 +5146,14 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz",
"integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz",
"integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.1.1",
"@vitest/utils": "4.1.1",
"@vitest/pretty-format": "4.1.8",
"@vitest/utils": "4.1.8",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -5172,9 +5162,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz",
"integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz",
"integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -5182,15 +5172,15 @@
}
},
"node_modules/@vitest/utils": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz",
"integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz",
"integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.1.1",
"@vitest/pretty-format": "4.1.8",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.0.3"
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -5223,9 +5213,9 @@
"license": "MIT"
},
"node_modules/@wdio/config/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5591,9 +5581,9 @@
"license": "MIT"
},
"node_modules/archiver-utils/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6400,6 +6390,16 @@
"node": ">=6"
}
},
"node_modules/chai": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -7882,9 +7882,9 @@
"license": "MIT"
},
"node_modules/eslint-plugin-import/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7979,9 +7979,9 @@
"license": "MIT"
},
"node_modules/eslint-plugin-json-schema-validator/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8045,9 +8045,9 @@
"license": "MIT"
},
"node_modules/eslint-plugin-n/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8206,9 +8206,9 @@
"license": "MIT"
},
"node_modules/eslint-plugin-react/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8408,9 +8408,9 @@
"license": "MIT"
},
"node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9244,9 +9244,9 @@
"license": "MIT"
},
"node_modules/globby/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10257,10 +10257,20 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -10928,9 +10938,9 @@
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "11.4.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz",
"integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==",
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
@@ -12650,9 +12660,9 @@
"license": "MIT"
},
"node_modules/readdir-glob/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -13075,9 +13085,9 @@
"license": "MIT"
},
"node_modules/semver": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
"dev": true,
"license": "ISC",
"bin": {
@@ -15249,9 +15259,9 @@
}
},
"node_modules/undici": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.27.0.tgz",
"integrity": "sha512-+t2Z/GwkZQDtu00813aP66ygViGtPHKhhoFZpQKpKrE+9jIgES+Zw+mFNaDWOVRKiuJjuqKHzD3B1sfGg8+ZOQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -16030,20 +16040,19 @@
}
},
"node_modules/vitest": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz",
"integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
"integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/expect": "4.1.1",
"@vitest/mocker": "4.1.1",
"@vitest/pretty-format": "4.1.1",
"@vitest/runner": "4.1.1",
"@vitest/snapshot": "4.1.1",
"@vitest/spy": "4.1.1",
"@vitest/utils": "4.1.1",
"@vitest/expect": "4.1.8",
"@vitest/mocker": "4.1.8",
"@vitest/pretty-format": "4.1.8",
"@vitest/runner": "4.1.8",
"@vitest/snapshot": "4.1.8",
"@vitest/spy": "4.1.8",
"@vitest/utils": "4.1.8",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
@@ -16054,7 +16063,7 @@
"tinybench": "^2.9.0",
"tinyexec": "^1.0.2",
"tinyglobby": "^0.2.15",
"tinyrainbow": "^3.0.3",
"tinyrainbow": "^3.1.0",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
"why-is-node-running": "^2.3.0"
},
@@ -16071,10 +16080,12 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.1.1",
"@vitest/browser-preview": "4.1.1",
"@vitest/browser-webdriverio": "4.1.1",
"@vitest/ui": "4.1.1",
"@vitest/browser-playwright": "4.1.8",
"@vitest/browser-preview": "4.1.8",
"@vitest/browser-webdriverio": "4.1.8",
"@vitest/coverage-istanbul": "4.1.8",
"@vitest/coverage-v8": "4.1.8",
"@vitest/ui": "4.1.8",
"happy-dom": "*",
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -16098,6 +16109,12 @@
"@vitest/browser-webdriverio": {
"optional": true
},
"@vitest/coverage-istanbul": {
"optional": true
},
"@vitest/coverage-v8": {
"optional": true
},
"@vitest/ui": {
"optional": true
},
@@ -16206,9 +16223,9 @@
}
},
"node_modules/webdriver/node_modules/undici": {
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz",
"integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.25.70-patch2",
"version": "0.25.70-patch3",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"main": "main.js",
"type": "module",
@@ -80,9 +80,9 @@
"@types/transform-pouch": "^1.0.6",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@vitest/browser": "^4.1.1",
"@vitest/browser-playwright": "^4.1.1",
"@vitest/coverage-v8": "^4.1.1",
"@vitest/browser": "^4.1.8",
"@vitest/browser-playwright": "^4.1.8",
"@vitest/coverage-v8": "^4.1.8",
"dotenv-cli": "^11.0.0",
"esbuild": "0.25.0",
"esbuild-plugin-inline-worker": "^0.1.1",
@@ -119,7 +119,7 @@
"typescript": "5.9.3",
"vite": "^7.3.1",
"vite-plugin-istanbul": "^8.0.0",
"vitest": "^4.1.1",
"vitest": "^4.1.8",
"webdriverio": "^9.27.0",
"yaml": "^2.8.2"
},

View File

@@ -80,7 +80,9 @@ describe("CLIStorageEventManagerAdapter", () => {
expect(handlers.onCreate).toHaveBeenCalledTimes(1);
const created = (handlers.onCreate as ReturnType<typeof vi.fn>).mock.calls[0][0] as NodeFile;
expect(created.path).toBe("subdir/note.md");
if (process.platform !== "win32") {
expect(created.path).toBe("subdir/note.md");
}
expect(created.stat?.size).toBe(42);
});

Submodule src/lib updated: 6f977537f4...6fac4a00dd

View File

@@ -8,7 +8,6 @@ import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
import { writable } from "svelte/store";
import type { FilePathWithPrefix } from "../../lib/src/common/types.ts";
import type { LiveSyncCore } from "../../main.ts";
export class ModuleDev extends AbstractObsidianModule {
_everyOnloadStart(): Promise<boolean> {
__onMissingTranslation(() => {});
@@ -98,6 +97,7 @@ export class ModuleDev extends AbstractObsidianModule {
});
return Promise.resolve(true);
}
async _everyOnLayoutReady(): Promise<boolean> {
if (!this.settings.enableDebugTools) return Promise.resolve(true);
// if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) {

View File

@@ -0,0 +1,197 @@
import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
import { type LogFunction } from "@lib/services/lib/logUtils";
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
import {
ExtraOnLocal,
ExtraOnRemote,
FullScanModes,
normaliseFullScanOptions,
synchroniseAllFilesBetweenDBandStorage,
type FullScanOptions,
} from "@lib/serviceFeatures/offlineScanner";
import { adjustSettingToRemoteIfNeeded, processVaultInitialisation } from "./redFlag";
export const SIMPLE_FETCH_STAGE1_REMOTE_WINS = "Overwrite all with remote files";
export const SIMPLE_FETCH_STAGE1_NEWER_WINS = "Compare time and take newer";
export const SIMPLE_FETCH_STAGE1_LEGACY = "Use the detailed flow";
export const SIMPLE_FETCH_STAGE1_CANCEL = "Cancel";
export const SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE = "Keep local files even if not on remote";
export const SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL = "Delete local files if not on remote";
export const SIMPLE_FETCH_STAGE2_NEWER_CLEANUP = "Delete local files if deleted on remote";
export const SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL = "Keep local files even if deleted on remote";
export const STAGE2_ABORT = "Cancel all and reboot";
export async function askSimpleFetchMode(
host: NecessaryServices<"UI" | "vault", "storageAccess">
): Promise<{ mode: string; options: Partial<FullScanOptions> } | "cancelled" | "aborted"> {
const msg = `We are about to retrieve the remote data.
Firstly, how shall we handle the data retrieved from this remote server?
- **${SIMPLE_FETCH_STAGE1_NEWER_WINS}**: Compares the modified time of files and takes the newer one.
If you have been using Self-hosted LiveSync and have made changes on multiple devices, this option may be suitable for you as it tries to merge changes based on modified time.
- **${SIMPLE_FETCH_STAGE1_REMOTE_WINS}**: Remote data is the source of truth.
If you are new to using Self-hosted LiveSync. This option may be easiest to understand and get started with.
It will overwrite all your local files with the remote data, so please make sure you have a backup if there is any important data in your vault.
- **${SIMPLE_FETCH_STAGE1_LEGACY}**: Opens the detailed setup wizard.
If you want to have more control over the synchronisation process, or want to review the changes before applying, you can choose this option to use the detailed flow.
`;
const stage1 = await host.services.UI.confirm.confirmWithMessage(
"Data retrieval scheduled",
msg,
[
SIMPLE_FETCH_STAGE1_NEWER_WINS,
SIMPLE_FETCH_STAGE1_REMOTE_WINS,
SIMPLE_FETCH_STAGE1_LEGACY,
SIMPLE_FETCH_STAGE1_CANCEL,
],
SIMPLE_FETCH_STAGE1_NEWER_WINS,
0
);
if (!stage1 || stage1 === SIMPLE_FETCH_STAGE1_CANCEL) return "cancelled";
if (stage1 === SIMPLE_FETCH_STAGE1_LEGACY) {
return { mode: "legacy", options: {} };
}
if (stage1 === SIMPLE_FETCH_STAGE1_REMOTE_WINS) {
const msg = `Since you have chosen to overwrite all local files with remote data, **how would you like to handle local files that are not present in the remote database?**
- **${SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL}**: Local-only files and remote-deleted files will be removed.
This option will make your local vault exactly the same as the remote database, but please make sure you have a backup if there is any important data in your vault.
- **${SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE}**: All existing local files will be preserved.
This option will keep all your local files, but it may cause duplicates if there are files that exist on local but not on remote. You can clean up these duplicates manually after the synchronisation.`;
const stage2 = await host.services.UI.confirm.confirmWithMessage(
"How to handle extra existing local files?",
msg,
[SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL, SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE, STAGE2_ABORT],
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE,
0
);
if (!stage2) return "cancelled";
if (stage2 === STAGE2_ABORT) {
return "aborted";
}
return {
mode: "remote-only",
options: {
mode: FullScanModes.DB_APPLY,
extraOnRemote:
stage2 === SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL ? ExtraOnRemote.DELETE_LOCAL_MISSING : undefined,
},
};
}
if (stage1 === SIMPLE_FETCH_STAGE1_NEWER_WINS) {
const msg = `How should files that were deleted on other devices be handled?
- **${SIMPLE_FETCH_STAGE2_NEWER_CLEANUP}**: Delete local files if they were deleted on remote.
This is useful if you want to keep your vault clean and consistent across devices, but please make sure you have a backup if there is already any important data in your vault.
- **${SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL}**: Recreate remote files even if they were deleted on remote.
This option will keep all your local files, but it may cause duplicates if there are files that exist on local but not on remote. You can clean up these duplicates manually after the synchronisation.
`;
const stage2 = await host.services.UI.confirm.confirmWithMessage(
"Conflict & Deletion Options",
msg,
[SIMPLE_FETCH_STAGE2_NEWER_CLEANUP, SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL, STAGE2_ABORT],
SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL,
0
);
if (!stage2) return "cancelled";
if (stage2 === STAGE2_ABORT) {
return "aborted";
}
return {
mode: "newer-wins",
options: {
mode: FullScanModes.NEWER_WINS,
extraOnLocal:
stage2 === SIMPLE_FETCH_STAGE2_NEWER_CLEANUP
? ExtraOnLocal.DELETE_DB_DELETED
: ExtraOnLocal.APPEND_STORAGE_ONLY,
},
};
}
return "cancelled";
}
const RERUN_PROCESS = "Reboot to re-run the process";
const RELEASE_FLAG_PROCESS = "Finalise the process and resume normal operation";
export async function askAndPerformFastSetupOnScheduledFetchAll(
host: NecessaryServices<
| "vault"
| "fileProcessing"
| "tweakValue"
| "UI"
| "setting"
| "appLifecycle"
| "path"
| "keyValueDB"
| "database",
"storageAccess" | "rebuilder" | "fileHandler"
>,
log: LogFunction,
cleanupFlag: () => Promise<void>
): Promise<boolean | undefined> {
const result = await askSimpleFetchMode(host);
if (result === "cancelled") {
log("Fetch cancelled by user.", LOG_LEVEL_NOTICE);
await cleanupFlag();
host.services.appLifecycle.performRestart();
return false;
}
if (result === "aborted") {
log("Fetch exited by user.", LOG_LEVEL_NOTICE);
host.services.appLifecycle.performRestart();
return false;
}
if (result.mode === "legacy") {
return undefined; // Let the legacy flow handle it.
}
return await processVaultInitialisation(host, log, async () => {
const settings = host.services.setting.currentSettings();
await adjustSettingToRemoteIfNeeded(host, log, { preventFetchingConfig: false }, settings);
// 1. Perform fast DB fetch (download remote DB content to local DB)
await host.serviceModules.rebuilder.$fetchLocalDBFast(false);
// 2. Call the extended synchroniseAllFilesBetweenDBandStorage to reflect changes in storage
const errorManager = new UnresolvedErrorManager(host.services.appLifecycle);
const syncResult = await synchroniseAllFilesBetweenDBandStorage(
host,
log,
errorManager,
normaliseFullScanOptions({
...result.options,
showingNotice: true,
omitEvents: true,
ignoreSuspending: true,
})
);
if (!syncResult) {
const canRelease = await host.services.UI.confirm.askSelectStringDialogue(
"Some files failed to synchronise. What would you like to do?",
[RERUN_PROCESS, RELEASE_FLAG_PROCESS],
{ defaultAction: RELEASE_FLAG_PROCESS, title: "Synchronisation Issues Detected" }
);
if (canRelease === RERUN_PROCESS) {
log("User chose to reboot and re-run the process.", LOG_LEVEL_NOTICE);
// Prevent to delete the flag, so that the process will be re-run after reboot.
// await cleanupFlag();
host.services.appLifecycle.performRestart();
return false;
}
}
await host.serviceModules.rebuilder.finishRebuild();
await cleanupFlag();
log("Simple fetch and scan operation completed.", LOG_LEVEL_NOTICE);
return true;
});
}

View File

@@ -12,6 +12,9 @@ import type {
FetchEverythingResult,
RebuildEverythingResult,
} from "@/modules/features/SetupWizard/dialogs/setupDialogTypes";
import { askAndPerformFastSetupOnScheduledFetchAll } from "./redFlag.simpleFetch";
import { ConnectionStringParser } from "@lib/common/ConnectionString";
import { activateRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
/**
* Flag file handler interface, similar to target filter pattern.
@@ -45,14 +48,79 @@ export async function deleteFlagFile(host: NecessaryServices<never, "storageAcce
log(ex, LOG_LEVEL_VERBOSE);
}
}
const REMOTE_KEEP_CURRENT = "Use active remote";
const REMOTE_CANCEL = "Cancel";
async function askAndActivateRemoteDatabase(host: NecessaryServices<"UI" | "setting", any>, log: LogFunction) {
const settings = host.services.setting.currentSettings();
if (settings.remoteConfigurations && Object.keys(settings.remoteConfigurations).length > 1) {
const message =
"Multiple remote configurations detected. Please select the remote configuration you want to fetch from.";
const options = Object.entries(settings.remoteConfigurations).map(([id, config]) => {
const parsed = ConnectionStringParser.parse(config.uri);
const displayURI = (config.uri.split("@").pop() || "").substring(0, 20) + "..."; // Show only the last part of URI for better readability and privacy.
return {
name: `${config.name} - ${parsed.type} (${displayURI})`,
id: id,
};
});
options.push({
name: REMOTE_KEEP_CURRENT,
id: "keep_current",
});
options.push({
name: REMOTE_CANCEL,
id: "cancel",
});
const selections = options.map((option) => option.name);
// const defaultAction =
// options.find((option) => option.id === settings.activeConfigurationId)?.name || selections[0];
const selectedId = await host.services.UI.confirm.askSelectStringDialogue(message, selections, {
title: "Select Remote Configuration",
defaultAction: REMOTE_KEEP_CURRENT,
});
const selectedConfig = options.find((option) => option.name === selectedId);
if (selectedConfig) {
if (selectedConfig.id === "keep_current") {
log(`Keeping current remote configuration.`, LOG_LEVEL_INFO);
return true;
}
if (selectedConfig.id === "cancel") {
log(`Remote configuration selection cancelled.`, LOG_LEVEL_NOTICE);
return false;
}
const activated = activateRemoteConfiguration(settings, selectedConfig.id);
if (activated) {
await host.services.setting.applyPartial(activated);
log(`Activated remote configuration: ${selectedConfig.name}`, LOG_LEVEL_INFO);
return true;
} else {
log(`Failed to activate remote configuration: ${selectedConfig.name}`, LOG_LEVEL_NOTICE);
return false;
}
} else {
log(`No remote configuration selected.`, LOG_LEVEL_NOTICE);
return false;
}
}
return true; // If there is only one or no remote configuration, proceed without asking.
}
/**
* Factory function to create a fetch all flag handler.
* All logic related to fetch all flag is encapsulated here.
*/
export function createFetchAllFlagHandler(
host: NecessaryServices<
"vault" | "fileProcessing" | "tweakValue" | "UI" | "setting" | "appLifecycle",
"storageAccess" | "rebuilder"
| "vault"
| "fileProcessing"
| "tweakValue"
| "UI"
| "setting"
| "appLifecycle"
| "path"
| "keyValueDB"
| "database",
"storageAccess" | "rebuilder" | "fileHandler"
>,
log: LogFunction
): FlagFileHandler {
@@ -69,6 +137,19 @@ export function createFetchAllFlagHandler(
// Handle the fetch all scheduled operation
const onScheduled = async () => {
// Select the remote database if there are multiple remotes configured.
const isRemoteActivated = await askAndActivateRemoteDatabase(host, log);
if (!isRemoteActivated) {
return false;
}
// Ask user for use Fast Setup
const useFastSetup = await askAndPerformFastSetupOnScheduledFetchAll(host, log, cleanupFlag);
if (useFastSetup !== undefined) {
return useFastSetup;
}
// if useFastSetup is undefined, it means user choose to proceed with normal fetch process, so continue to ask for fetch method.
const method =
await host.services.UI.dialogManager.openWithExplicitCancel<FetchEverythingResult>(FetchEverything);
if (method === "cancelled") {
@@ -380,8 +461,17 @@ export function flagHandlerToEventHandler(flagHandler: FlagFileHandler) {
export function useRedFlagFeatures(
host: NecessaryServices<
"API" | "appLifecycle" | "UI" | "setting" | "tweakValue" | "fileProcessing" | "vault",
"storageAccess" | "rebuilder"
| "API"
| "appLifecycle"
| "UI"
| "setting"
| "tweakValue"
| "fileProcessing"
| "vault"
| "path"
| "keyValueDB"
| "database",
"storageAccess" | "rebuilder" | "fileHandler"
>
) {
const log = createInstanceLogFunction("SF:RedFlag", host.services.API);

View File

@@ -19,6 +19,45 @@ import {
TweakValuesShouldMatchedTemplate,
TweakValuesTemplate,
} from "@/lib/src/common/types";
import {
ExtraOnLocal,
FullScanModes,
synchroniseAllFilesBetweenDBandStorage,
} from "@/lib/src/serviceFeatures/offlineScanner";
import {
SIMPLE_FETCH_STAGE1_LEGACY,
SIMPLE_FETCH_STAGE1_NEWER_WINS,
SIMPLE_FETCH_STAGE1_REMOTE_WINS,
SIMPLE_FETCH_STAGE2_NEWER_CLEANUP,
SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL,
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE,
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL,
STAGE2_ABORT,
askAndPerformFastSetupOnScheduledFetchAll,
askSimpleFetchMode,
} from "./redFlag.simpleFetch";
import { activateRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
//Mock synchroniseAllFilesBetweenDBandStorage
vi.mock("@/lib/src/serviceFeatures/offlineScanner", async (importOriginal) => {
const originalModule = (await importOriginal()) as any;
return {
...originalModule,
synchroniseAllFilesBetweenDBandStorage: vi.fn(() => Promise.resolve(true)),
};
});
vi.mock("@lib/serviceFeatures/remoteConfig", () => {
return {
activateRemoteConfiguration: vi.fn((settings: any, configurationId: string) => {
if (!settings?.remoteConfigurations?.[configurationId]) return false;
return {
activeConfigurationId: configurationId,
remoteType: settings.remoteConfigurations[configurationId].remoteType ?? settings.remoteType,
remoteURI: settings.remoteConfigurations[configurationId].uri,
};
}),
};
});
// Mock types and functions
const createLoggerMock = (): LogFunction => {
@@ -68,6 +107,9 @@ const createAppLifecycleMock = () => {
onLayoutReady: {
addHandler: vi.fn(),
},
getUnresolvedMessages: {
addHandler: vi.fn(),
},
};
};
@@ -79,6 +121,7 @@ const createUIServiceMock = () => {
confirm: {
askSelectStringDialogue: vi.fn(),
askYesNoDialog: vi.fn(),
confirmWithMessage: vi.fn(),
},
};
};
@@ -86,7 +129,9 @@ const createUIServiceMock = () => {
const createRebuilderMock = () => {
return {
$fetchLocal: vi.fn(async () => {}),
$fetchLocalDBFast: vi.fn(async () => {}),
$rebuildEverything: vi.fn(async () => {}),
finishRebuild: vi.fn(async () => {}),
};
};
@@ -389,6 +434,394 @@ describe("Red Flag Feature", () => {
const handler = createFetchAllFlagHandler(host as any, log);
expect(handler.priority).toBe(10);
});
it("should use simplified remote-only mode and call performFullScan", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
// Stage 1: Overwrite all with remote files
// Stage 2: Delete local files if not on remote (Clean overwrite)
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
batchSave: false,
} as any);
const handler = createFetchAllFlagHandler(host as any, log);
const result = await handler.handle();
expect(result).toBe(true);
expect(host.mocks.rebuilder.$fetchLocalDBFast).toHaveBeenCalled();
expect(synchroniseAllFilesBetweenDBandStorage).toHaveBeenCalled();
// We can't easily check performFullScan call here because it's imported,
// but we can verify rebuilder was called.
});
it("should restore legacy fetch flow when requested", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({
vault: "identical",
backup: "backup_skipped",
extra: { preventFetchingConfig: false },
});
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
batchSave: false,
} as any);
const handler = createFetchAllFlagHandler(host as any, log);
const result = await handler.handle();
expect(result).toBe(true);
expect(host.mocks.ui.dialogManager.openWithExplicitCancel).toHaveBeenCalled();
expect(host.mocks.rebuilder.$fetchLocal).toHaveBeenCalled();
});
it("should cancel fetch flow when first quick step is cancelled", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
const handler = createFetchAllFlagHandler(host as any, log);
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
batchSave: false,
} as any);
const result = await handler.handle();
expect(result).toBe(false);
expect(host.mocks.rebuilder.$fetchLocal).not.toHaveBeenCalled();
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
});
it("should use remote-authoritative quick mode for empty vault", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
batchSave: false,
} as any);
const handler = createFetchAllFlagHandler(host as any, log);
const result = await handler.handle();
expect(result).toBe(true);
expect(host.mocks.rebuilder.$fetchLocalDBFast).toHaveBeenCalled();
expect(host.mocks.rebuilder.$fetchLocal).not.toHaveBeenCalledWith(false, true);
});
it("should keep current remote configuration when selected", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
Object.assign(host.mocks.setting.settings, {
remoteConfigurations: {
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
},
});
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Use active remote");
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
const handler = createFetchAllFlagHandler(host as any, log);
const result = await handler.handle();
expect(result).toBe(false);
expect(host.mocks.setting.applyPartial).not.toHaveBeenCalledWith(
expect.objectContaining({ activeConfigurationId: expect.any(String) })
);
});
it("should stop when remote selection is cancelled", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
Object.assign(host.mocks.setting.settings, {
remoteConfigurations: {
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
},
});
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Cancel");
const handler = createFetchAllFlagHandler(host as any, log);
const result = await handler.handle();
expect(result).toBe(false);
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
});
it("should activate selected remote configuration", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
Object.assign(host.mocks.setting.settings, {
remoteConfigurations: {
alpha: {
name: "Alpha",
uri: "sls+https://user:pass@example.com/db1",
remoteType: "CouchDB",
},
beta: {
name: "Beta",
uri: "sls+https://user:pass@example.com/db2",
remoteType: "CouchDB",
},
},
});
host.mocks.ui.confirm.askSelectStringDialogue.mockImplementationOnce(
async (_message: string, selections: string[]) => selections.find((e) => e.startsWith("Beta -"))
);
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
const handler = createFetchAllFlagHandler(host as any, log);
const result = await handler.handle();
expect(result).toBe(false);
expect(activateRemoteConfiguration).toHaveBeenCalledWith(host.mocks.setting.settings, "beta");
expect(host.mocks.setting.applyPartial).toHaveBeenCalledWith(
expect.objectContaining({ activeConfigurationId: "beta" })
);
});
it("should stop when selected remote name is unknown", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
Object.assign(host.mocks.setting.settings, {
remoteConfigurations: {
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
},
});
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Unknown option");
const handler = createFetchAllFlagHandler(host as any, log);
const result = await handler.handle();
expect(result).toBe(false);
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
});
it("should stop when remote activation fails", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
Object.assign(host.mocks.setting.settings, {
remoteConfigurations: {
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
},
});
host.mocks.ui.confirm.askSelectStringDialogue.mockImplementationOnce(
async (_message: string, selections: string[]) => selections.find((e) => e.startsWith("Beta -"))
);
(activateRemoteConfiguration as any).mockReturnValueOnce(false);
const handler = createFetchAllFlagHandler(host as any, log);
const result = await handler.handle();
expect(result).toBe(false);
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
});
});
describe("askSimpleFetchMode", () => {
it("should return cancelled when stage1 is cancelled", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
});
it("should return legacy mode when selected", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({ mode: "legacy", options: {} });
});
it("should return remote-only with keep-local option", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE);
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
mode: "remote-only",
options: {
mode: FullScanModes.DB_APPLY,
extraOnRemote: undefined,
},
});
});
it("should return cancelled when remote-only stage2 is cancelled", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
.mockResolvedValueOnce(false);
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
});
it("should return aborted when remote-only stage2 aborts", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
.mockResolvedValueOnce(STAGE2_ABORT);
await expect(askSimpleFetchMode(host as any)).resolves.toBe("aborted");
});
it("should return newer-wins cleanup option", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_CLEANUP);
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
mode: "newer-wins",
options: {
mode: FullScanModes.NEWER_WINS,
extraOnLocal: ExtraOnLocal.DELETE_DB_DELETED,
},
});
});
it("should return newer-wins keep-all option", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL);
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
mode: "newer-wins",
options: {
mode: FullScanModes.NEWER_WINS,
extraOnLocal: ExtraOnLocal.APPEND_STORAGE_ONLY,
},
});
});
it("should return cancelled when newer-wins stage2 is cancelled", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
.mockResolvedValueOnce(false);
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
});
it("should return aborted when newer-wins stage2 aborts", async () => {
const host = createHostMock();
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
.mockResolvedValueOnce(STAGE2_ABORT);
await expect(askSimpleFetchMode(host as any)).resolves.toBe("aborted");
});
});
describe("askAndPerformFastSetupOnScheduledFetchAll", () => {
it("should return false and cleanup when quick flow is cancelled", async () => {
const host = createHostMock();
const log = createLoggerMock();
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
expect(result).toBe(false);
expect(cleanupFlag).toHaveBeenCalled();
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
});
it("should return false without cleanup when quick flow is aborted", async () => {
const host = createHostMock();
const log = createLoggerMock();
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
.mockResolvedValueOnce(STAGE2_ABORT);
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
expect(result).toBe(false);
expect(cleanupFlag).not.toHaveBeenCalled();
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
});
it("should return undefined when legacy mode is selected", async () => {
const host = createHostMock();
const log = createLoggerMock();
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
expect(result).toBeUndefined();
expect(host.mocks.rebuilder.$fetchLocalDBFast).not.toHaveBeenCalled();
});
it("should reboot and return false when sync has failures and user chooses rerun", async () => {
const host = createHostMock();
const log = createLoggerMock();
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ batchSave: false } as any);
(synchroniseAllFilesBetweenDBandStorage as any).mockResolvedValueOnce(false);
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Reboot to re-run the process");
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
expect(result).toBe(false);
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
expect(cleanupFlag).not.toHaveBeenCalled();
expect(host.mocks.rebuilder.finishRebuild).not.toHaveBeenCalled();
});
it("should continue and finalise when sync has failures but user releases flag", async () => {
const host = createHostMock();
const log = createLoggerMock();
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
host.mocks.ui.confirm.confirmWithMessage
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ batchSave: false } as any);
(synchroniseAllFilesBetweenDBandStorage as any).mockResolvedValueOnce(false);
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce(
"Finalise the process and resume normal operation"
);
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
expect(result).toBe(true);
expect(host.mocks.rebuilder.finishRebuild).toHaveBeenCalled();
expect(cleanupFlag).toHaveBeenCalled();
});
});
describe("Rebuild All Flag Handler", () => {
@@ -980,6 +1413,8 @@ describe("Red Flag Feature", () => {
const log = createLoggerMock();
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({});
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce("cancelled");
const handler = createFetchAllFlagHandler(host as any, log);
@@ -1056,11 +1491,12 @@ describe("Red Flag Feature", () => {
it("should handle fetchAll flag with flagHandlerToEventHandler identical", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue({
customChunkSize: 1,
} as any);
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ vault: "identical", extra: {} });
host.mocks.rebuilder.$fetchLocal.mockResolvedValueOnce();
const handler = createFetchAllFlagHandler(host as any, log);

View File

@@ -3,6 +3,17 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
## 0.25.70-patch3
2nd June, 2026
### Improved
- Database fetching (a.k.a. Reset Synchronisation on This Device) on the initialisation now supports streaming and is faster (CouchDB only)
- The database fetching process has been streamlined, and database operations are now suspended until it has been completed
- The initial synchronisation process has been simplified, making it easier to synchronise files with the remote server
- We can select the remote database to fetch from during the initialisation, when there are multiple remote databases configured (e.g. multiple CouchDBs or S3 remotes)
## 0.25.70-patch2
1st June, 2026
@@ -21,10 +32,6 @@ I have also separated out some parts where the type definitions were a bit loose
As the diff has become too large, I am releasing it as a beta.
To anyone who has submitted a pull request, please bear with me for a little while.
### Refactored
- Many
## 0.25.70
25th May, 2026

View File

@@ -11,17 +11,23 @@ export default mergeConfig(
},
},
test: {
logHeapUsage: true,
// maxConcurrency: 2,
name: "unit-tests",
include: ["**/*unit.test.ts", "**/*.unit.spec.ts"],
exclude: ["test/**"],
exclude: ["test/**", "src/apps/**"],
coverage: {
include: ["src/**/*.ts"],
exclude: [
"**/*.test.ts",
"**/*unit.test.ts",
"**/*.unit.spec.ts",
"test/**",
"src/lib/**/*.test.ts",
"**/_*",
"src/lib/apps",
"src/lib/src/cli",
"src/apps/**",
// "src/cli/**",
"src/lib/src/cli/**",
"**/*_obsolete.ts",
...importOnlyFiles,
],