Compare commits

...

2 Commits

Author SHA1 Message Date
vorotamoroz 82172e68e3 Update notes 2026-06-15 10:52:29 +01:00
vorotamoroz 24bc8404fe ### Improved
- Fetch chunks on demand now respects network conditions.

### Misc
- Prettified
2026-06-15 10:50:25 +01:00
10 changed files with 130 additions and 136 deletions
+5 -1
View File
@@ -109,7 +109,11 @@ Deno.test("daemon: ignore rules behaviour", async (t) => {
await runCliOrFail(vaultDir, "--settings", settingsFile, "mirror");
const dbList = await runCliOrFail(vaultDir, "--settings", settingsFile, "ls");
assertNotContains(dbList, "debug.log", "debug.log (ignored via .gitignore import) was unexpectedly synced to database");
assertNotContains(
dbList,
"debug.log",
"debug.log (ignored via .gitignore import) was unexpectedly synced to database"
);
assertContains(dbList, "regular.md", "regular.md was not synced normally alongside .gitignore import rules");
console.log("[PASS] Case 3 verified successfully");
});
+4 -31
View File
@@ -46,9 +46,7 @@ Deno.test("decoupled database and vault", async () => {
console.log("[INFO] applying CouchDB environment variables to settings");
await applyCouchdbSettings(settingsFile, uri, user, password, dbname);
} else {
console.warn(
"[WARN] CouchDB environment variables are not fully set. Push and pull operations may fail."
);
console.warn("[WARN] CouchDB environment variables are not fully set. Push and pull operations may fail.");
await markSettingsConfigured(settingsFile);
}
@@ -59,29 +57,11 @@ Deno.test("decoupled database and vault", async () => {
// 1. Test push command with decoupled vault directory
console.log(`[INFO] push with decoupled vault -> ${REMOTE_PATH}`);
await runCliOrFail(
dbDir,
"--vault",
vaultDir,
"--settings",
settingsFile,
"push",
srcFile,
REMOTE_PATH
);
await runCliOrFail(dbDir, "--vault", vaultDir, "--settings", settingsFile, "push", srcFile, REMOTE_PATH);
// 2. Test pull command with decoupled vault directory
console.log(`[INFO] pull with decoupled vault <- ${REMOTE_PATH}`);
await runCliOrFail(
dbDir,
"--vault",
vaultDir,
"--settings",
settingsFile,
"pull",
REMOTE_PATH,
pulledFile
);
await runCliOrFail(dbDir, "--vault", vaultDir, "--settings", settingsFile, "pull", REMOTE_PATH, pulledFile);
const pulled = await Deno.readTextFile(pulledFile);
assertEquals(pulled, content, "push/pull roundtrip with decoupled vault content mismatch");
@@ -93,14 +73,7 @@ Deno.test("decoupled database and vault", async () => {
// 4. Test mirror command with decoupled vault directory
console.log("[INFO] mirror with decoupled vault");
await runCliOrFail(
dbDir,
"--vault",
vaultDir,
"--settings",
settingsFile,
"mirror"
);
await runCliOrFail(dbDir, "--vault", vaultDir, "--settings", settingsFile, "mirror");
const restoredFile = join(vaultDir, REMOTE_PATH);
const restored = await Deno.readTextFile(restoredFile);
@@ -14,7 +14,7 @@ import { getOptimalLoopbackIp } from "./helpers/net.ts";
Deno.test("p2p-peers: discovers host through local relay", async () => {
const loopbackIp = await getOptimalLoopbackIp();
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
+1 -1
View File
@@ -15,7 +15,7 @@ import { getOptimalLoopbackIp } from "./helpers/net.ts";
Deno.test("p2p-sync: discovers peer and completes sync", async () => {
const loopbackIp = await getOptimalLoopbackIp();
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
@@ -15,7 +15,7 @@ import { getOptimalLoopbackIp } from "./helpers/net.ts";
Deno.test("p2p: three nodes detect and resolve conflicts", async () => {
const loopbackIp = await getOptimalLoopbackIp();
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
@@ -61,11 +61,7 @@ Deno.test("remote management commands", async () => {
// 1. remote-status outputs valid JSON with CouchDB details
console.log("[CASE] remote-status outputs valid JSON with CouchDB details");
const statusOutput = await runCliCombinedOrFail(vaultDir, "--settings", settingsFile, "remote-status");
assertContains(
statusOutput,
`"db_name": "${dbname}"`,
"remote-status should return JSON containing db_name"
);
assertContains(statusOutput, `"db_name": "${dbname}"`, "remote-status should return JSON containing db_name");
console.log("[PASS] remote-status verified");
// 2. lock-remote locks and verifies state
+1 -1
Submodule src/lib updated: 5a552b3ec4...29b552f34b
+1
View File
@@ -124,6 +124,7 @@ export class ModuleLiveSyncMain extends AbstractModule {
await this.services.database.openDatabase({
databaseEvents: this.services.databaseEvents,
replicator: this.services.replicator,
replication: this.services.replication,
});
// this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this);
// this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this);
+6
View File
@@ -3,6 +3,12 @@ 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.
## Unreleased
### Improved
- Now chunk waiting timeout is adaptive
- Fetch chunks on demand now respects network conditions. Wait until the replication and network requests have been finished.
## 0.25.75
13th June, 2026
+109 -95
View File
@@ -1,14 +1,14 @@
import * as acorn from 'acorn';
import fs from 'fs';
import * as acorn from "acorn";
import fs from "fs";
// Parse command line arguments
const args = process.argv.slice(2);
let file = 'main.js';
let file = "main.js";
let target = 2018;
let ios = null;
// Help menu
if (args.includes('--help') || args.includes('-h')) {
if (args.includes("--help") || args.includes("-h")) {
console.log(`Usage: node utils/check-compatibility.js [options]
Options:
--file <path> Path to the bundle file to check (default: main.js)
@@ -27,13 +27,13 @@ Options:
}
for (let i = 0; i < args.length; i++) {
if (args[i] === '--file' && args[i + 1]) {
if (args[i] === "--file" && args[i + 1]) {
file = args[i + 1];
i++;
} else if (args[i] === '--target' && args[i + 1]) {
} else if (args[i] === "--target" && args[i + 1]) {
target = parseInt(args[i + 1], 10);
i++;
} else if (args[i] === '--ios' && args[i + 1]) {
} else if (args[i] === "--ios" && args[i + 1]) {
ios = parseFloat(args[i + 1]);
i++;
}
@@ -69,22 +69,22 @@ if (ios !== null) {
// Override defaults with explicit command line options if specified
for (let i = 0; i < args.length; i++) {
if (args[i] === '--allow-dynamic-import') allowDynamicImport = true;
else if (args[i] === '--no-allow-dynamic-import') allowDynamicImport = false;
else if (args[i] === '--allow-bigint') allowBigInt = true;
else if (args[i] === '--no-allow-bigint') allowBigInt = false;
else if (args[i] === '--allow-numeric-separator') allowNumericSeparator = true;
else if (args[i] === '--no-allow-numeric-separator') allowNumericSeparator = false;
else if (args[i] === '--allow-class-fields') allowClassFields = true;
else if (args[i] === '--no-allow-class-fields') allowClassFields = false;
else if (args[i] === '--allow-class-static-blocks') allowClassStaticBlocks = true;
else if (args[i] === '--no-allow-class-static-blocks') allowClassStaticBlocks = false;
else if (args[i] === '--allow-regexp-lookbehind') allowRegexpLookbehind = true;
else if (args[i] === '--no-allow-regexp-lookbehind') allowRegexpLookbehind = false;
else if (args[i] === '--allow-regexp-indices') allowRegexpIndices = true;
else if (args[i] === '--no-allow-regexp-indices') allowRegexpIndices = false;
else if (args[i] === '--allow-regexp-v-flag') allowRegexpVFlag = true;
else if (args[i] === '--no-allow-regexp-v-flag') allowRegexpVFlag = false;
if (args[i] === "--allow-dynamic-import") allowDynamicImport = true;
else if (args[i] === "--no-allow-dynamic-import") allowDynamicImport = false;
else if (args[i] === "--allow-bigint") allowBigInt = true;
else if (args[i] === "--no-allow-bigint") allowBigInt = false;
else if (args[i] === "--allow-numeric-separator") allowNumericSeparator = true;
else if (args[i] === "--no-allow-numeric-separator") allowNumericSeparator = false;
else if (args[i] === "--allow-class-fields") allowClassFields = true;
else if (args[i] === "--no-allow-class-fields") allowClassFields = false;
else if (args[i] === "--allow-class-static-blocks") allowClassStaticBlocks = true;
else if (args[i] === "--no-allow-class-static-blocks") allowClassStaticBlocks = false;
else if (args[i] === "--allow-regexp-lookbehind") allowRegexpLookbehind = true;
else if (args[i] === "--no-allow-regexp-lookbehind") allowRegexpLookbehind = false;
else if (args[i] === "--allow-regexp-indices") allowRegexpIndices = true;
else if (args[i] === "--no-allow-regexp-indices") allowRegexpIndices = false;
else if (args[i] === "--allow-regexp-v-flag") allowRegexpVFlag = true;
else if (args[i] === "--no-allow-regexp-v-flag") allowRegexpVFlag = false;
}
if (!fs.existsSync(file)) {
@@ -92,35 +92,35 @@ if (!fs.existsSync(file)) {
process.exit(1);
}
const code = fs.readFileSync(file, 'utf8');
const code = fs.readFileSync(file, "utf8");
let ast;
const targetInfo = ios !== null ? `iOS ${ios}` : `ES${target}`;
console.log(`Parsing '${file}' to inspect compatibility (target ${targetInfo})...`);
console.log(`Rules:
Dynamic Import: ${allowDynamicImport ? 'Allowed' : 'Prohibited'}
BigInt: ${allowBigInt ? 'Allowed' : 'Prohibited'}
Numeric Separators: ${allowNumericSeparator ? 'Allowed' : 'Prohibited'}
Class Fields: ${allowClassFields ? 'Allowed' : 'Prohibited'}
Class Static Block: ${allowClassStaticBlocks ? 'Allowed' : 'Prohibited'}
RegExp Lookbehind: ${allowRegexpLookbehind ? 'Allowed' : 'Prohibited'}
RegExp Indices (d): ${allowRegexpIndices ? 'Allowed' : 'Prohibited'}
RegExp Unicode (v): ${allowRegexpVFlag ? 'Allowed' : 'Prohibited'}
Dynamic Import: ${allowDynamicImport ? "Allowed" : "Prohibited"}
BigInt: ${allowBigInt ? "Allowed" : "Prohibited"}
Numeric Separators: ${allowNumericSeparator ? "Allowed" : "Prohibited"}
Class Fields: ${allowClassFields ? "Allowed" : "Prohibited"}
Class Static Block: ${allowClassStaticBlocks ? "Allowed" : "Prohibited"}
RegExp Lookbehind: ${allowRegexpLookbehind ? "Allowed" : "Prohibited"}
RegExp Indices (d): ${allowRegexpIndices ? "Allowed" : "Prohibited"}
RegExp Unicode (v): ${allowRegexpVFlag ? "Allowed" : "Prohibited"}
`);
try {
ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'script' });
ast = acorn.parse(code, { ecmaVersion: "latest", sourceType: "script" });
} catch (err) {
console.error(`Syntax Error: Failed to parse '${file}' due to a syntax issue:`);
console.error(err.message);
if (err.pos !== undefined) {
const line = code.substring(0, err.pos).split('\n').length;
const line = code.substring(0, err.pos).split("\n").length;
console.error(`Location: line ${line}, character ${err.pos}`);
const start = Math.max(0, err.pos - 50);
const end = Math.min(code.length, err.pos + 50);
console.error('Context around error:');
console.error("Context around error:");
console.error(code.substring(start, end));
console.error(' '.repeat(err.pos - start) + '^');
console.error(" ".repeat(err.pos - start) + "^");
}
process.exit(1);
}
@@ -131,14 +131,14 @@ const violations = [];
function hasLookbehind(pattern) {
let index = 0;
while (true) {
const match = pattern.indexOf('(?<=', index);
const match2 = pattern.indexOf('(?<!', index);
const pos = (match !== -1 && match2 !== -1) ? Math.min(match, match2) : (match !== -1 ? match : match2);
const match = pattern.indexOf("(?<=", index);
const match2 = pattern.indexOf("(?<!", index);
const pos = match !== -1 && match2 !== -1 ? Math.min(match, match2) : match !== -1 ? match : match2;
if (pos === -1) break;
let backslashes = 0;
for (let i = pos - 1; i >= 0; i--) {
if (pattern[i] === '\\') backslashes++;
if (pattern[i] === "\\") backslashes++;
else break;
}
if (backslashes % 2 === 0) {
@@ -150,178 +150,192 @@ function hasLookbehind(pattern) {
}
function checkNode(node) {
if (!node || typeof node !== 'object') return;
if (!node || typeof node !== "object") return;
if (node.type) {
// 1. Optional catch binding (ES2019 / iOS 11.3+)
if (node.type === 'CatchClause' && !node.param) {
if (node.type === "CatchClause" && !node.param) {
if (target < 2019 && ios === null) {
violations.push({
feature: 'Optional catch binding (ES2019 / iOS 11.3+)',
feature: "Optional catch binding (ES2019 / iOS 11.3+)",
pos: node.start,
node
node,
});
} else if (ios !== null && ios < 11.3) {
violations.push({
feature: 'Optional catch binding (iOS 11.3+)',
feature: "Optional catch binding (iOS 11.3+)",
pos: node.start,
node
node,
});
}
}
// 2. Dynamic import (ES2020 / iOS 11.3+)
if (node.type === 'ImportExpression') {
if (node.type === "ImportExpression") {
if (!allowDynamicImport) {
violations.push({
feature: 'Dynamic import (ES2020 / iOS 11.3+)',
feature: "Dynamic import (ES2020 / iOS 11.3+)",
pos: node.start,
node
node,
});
}
}
// 3. import.meta (ES2020 / iOS 11.3+)
if (node.type === 'MetaProperty' && node.meta && node.meta.name === 'import') {
if (node.type === "MetaProperty" && node.meta && node.meta.name === "import") {
if (!allowDynamicImport) {
violations.push({
feature: 'import.meta (ES2020 / iOS 11.3+)',
feature: "import.meta (ES2020 / iOS 11.3+)",
pos: node.start,
node
node,
});
}
}
// 4. Optional chaining (ES2020 / iOS 13.4+)
if (node.type === 'ChainExpression') {
if (node.type === "ChainExpression") {
const isProhibited = ios !== null ? ios < 13.4 : target < 2020;
if (isProhibited) {
violations.push({
feature: 'Optional chaining (ES2020 / iOS 13.4+)',
feature: "Optional chaining (ES2020 / iOS 13.4+)",
pos: node.start,
node
node,
});
}
}
// 5. Nullish coalescing (ES2020 / iOS 13.4+)
if (node.type === 'LogicalExpression' && node.operator === '??') {
if (node.type === "LogicalExpression" && node.operator === "??") {
const isProhibited = ios !== null ? ios < 13.4 : target < 2020;
if (isProhibited) {
violations.push({
feature: 'Nullish coalescing (ES2020 / iOS 13.4+)',
feature: "Nullish coalescing (ES2020 / iOS 13.4+)",
pos: node.start,
node
node,
});
}
}
// 6. BigInt literal (ES2020 / iOS 14.0+)
if (node.type === 'Literal' && node.bigint !== undefined) {
if (node.type === "Literal" && node.bigint !== undefined) {
if (!allowBigInt) {
violations.push({
feature: 'BigInt literal (ES2020 / iOS 14.0+)',
feature: "BigInt literal (ES2020 / iOS 14.0+)",
pos: node.start,
node
node,
});
}
}
// 7. Logical assignment (ES2021 / iOS 14.0+)
if (node.type === 'AssignmentExpression' && ['||=', '&&=', '??='].includes(node.operator)) {
if (node.type === "AssignmentExpression" && ["||=", "&&=", "??="].includes(node.operator)) {
const isProhibited = ios !== null ? ios < 14.0 : target < 2021;
if (isProhibited) {
violations.push({
feature: `Logical assignment operator '${node.operator}' (ES2021 / iOS 14.0+)`,
pos: node.start,
node
node,
});
}
}
// 8. Numeric separators (ES2021 / iOS 14.0+)
if (node.type === 'Literal' && typeof node.value === 'number' && node.raw && node.raw.includes('_')) {
if (node.type === "Literal" && typeof node.value === "number" && node.raw && node.raw.includes("_")) {
if (!allowNumericSeparator) {
violations.push({
feature: 'Numeric separator (ES2021 / iOS 14.0+)',
feature: "Numeric separator (ES2021 / iOS 14.0+)",
pos: node.start,
node
node,
});
}
}
// 9. Class Fields (ES2022 / iOS 14.0+ public, iOS 14.5+ private/static)
if (node.type === 'PropertyDefinition') {
if (node.type === "PropertyDefinition") {
if (!allowClassFields) {
const requiredVersion = node.key.type === 'PrivateIdentifier' || node.static ? 'iOS 14.5+' : 'iOS 14.0+';
const requiredVersion =
node.key.type === "PrivateIdentifier" || node.static ? "iOS 14.5+" : "iOS 14.0+";
violations.push({
feature: `Class field definition '${node.key.name || node.key.value || '#private'}' (ES2022 / ${requiredVersion})`,
feature: `Class field definition '${node.key.name || node.key.value || "#private"}' (ES2022 / ${requiredVersion})`,
pos: node.start,
node
node,
});
}
}
// 10. Class Static Initialization Blocks (ES2022 / iOS 16.4+)
if (node.type === 'StaticBlock') {
if (node.type === "StaticBlock") {
if (!allowClassStaticBlocks) {
violations.push({
feature: 'Class static initialization block (ES2022 / iOS 16.4+)',
feature: "Class static initialization block (ES2022 / iOS 16.4+)",
pos: node.start,
node
node,
});
}
}
// 11. RegExp lookbehind assertions (ES2018 / iOS 16.4+)
if (node.type === 'Literal' && node.regex) {
if (node.type === "Literal" && node.regex) {
if (!allowRegexpLookbehind && hasLookbehind(node.regex.pattern)) {
violations.push({
feature: 'RegExp Lookbehind assertion (iOS 16.4+)',
feature: "RegExp Lookbehind assertion (iOS 16.4+)",
pos: node.start,
node
node,
});
}
if (!allowRegexpIndices && node.regex.flags.includes('d')) {
if (!allowRegexpIndices && node.regex.flags.includes("d")) {
violations.push({
feature: "RegExp 'd' (indices) flag (ES2022 / iOS 15.0+)",
pos: node.start,
node
node,
});
}
if (!allowRegexpVFlag && node.regex.flags.includes('v')) {
if (!allowRegexpVFlag && node.regex.flags.includes("v")) {
violations.push({
feature: "RegExp 'v' (Unicode properties) flag (ES2024 / iOS 17.0+)",
pos: node.start,
node
node,
});
}
}
if ((node.type === 'NewExpression' || node.type === 'CallExpression') && node.callee && node.callee.name === 'RegExp') {
if (!allowRegexpLookbehind && node.arguments[0] && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string') {
if (
(node.type === "NewExpression" || node.type === "CallExpression") &&
node.callee &&
node.callee.name === "RegExp"
) {
if (
!allowRegexpLookbehind &&
node.arguments[0] &&
node.arguments[0].type === "Literal" &&
typeof node.arguments[0].value === "string"
) {
if (hasLookbehind(node.arguments[0].value)) {
violations.push({
feature: 'RegExp Lookbehind assertion (iOS 16.4+)',
feature: "RegExp Lookbehind assertion (iOS 16.4+)",
pos: node.start,
node
node,
});
}
}
if (node.arguments[1] && node.arguments[1].type === 'Literal' && typeof node.arguments[1].value === 'string') {
if (
node.arguments[1] &&
node.arguments[1].type === "Literal" &&
typeof node.arguments[1].value === "string"
) {
const flags = node.arguments[1].value;
if (!allowRegexpIndices && flags.includes('d')) {
if (!allowRegexpIndices && flags.includes("d")) {
violations.push({
feature: "RegExp 'd' (indices) flag (ES2022 / iOS 15.0+)",
pos: node.start,
node
node,
});
}
if (!allowRegexpVFlag && flags.includes('v')) {
if (!allowRegexpVFlag && flags.includes("v")) {
violations.push({
feature: "RegExp 'v' (Unicode properties) flag (ES2024 / iOS 17.0+)",
pos: node.start,
node
node,
});
}
}
@@ -329,13 +343,13 @@ function checkNode(node) {
}
for (const key in node) {
if (key === 'loc' || key === 'start' || key === 'end') continue;
if (key === "loc" || key === "start" || key === "end") continue;
const val = node[key];
if (Array.isArray(val)) {
for (const child of val) {
checkNode(child);
}
} else if (val && typeof val === 'object') {
} else if (val && typeof val === "object") {
checkNode(val);
}
}
@@ -347,14 +361,14 @@ checkNode(ast);
if (violations.length > 0) {
console.error(`\nCompatibility Check Failed: Found ${violations.length} prohibited features.`);
violations.forEach((v, index) => {
const line = code.substring(0, v.pos).split('\n').length;
const line = code.substring(0, v.pos).split("\n").length;
console.error(`\n[${index + 1}] Prohibited feature: ${v.feature}`);
console.error(`Location: line ${line}, character ${v.pos}`);
const start = Math.max(0, v.pos - 50);
const end = Math.min(code.length, v.pos + 50);
console.error('Context around feature:');
console.error("Context around feature:");
console.error(code.substring(start, end));
console.error(' '.repeat(v.pos - start) + '^');
console.error(" ".repeat(v.pos - start) + "^");
});
process.exit(1);
}