From aecbcd1d2c1abfd0947149094327060020127408 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sat, 25 Apr 2026 08:40:40 +0100 Subject: [PATCH] return paths --- .../lib/types/DocumentUpdateMergedContent.ts | 9 --- .../src/lib/types/DocumentUpdateMetadata.ts | 9 --- .../src/lib/types/DocumentUpdateResponse.ts | 6 +- .../file-operations/file-operations.test.ts | 8 +-- .../src/file-operations/file-operations.ts | 2 +- .../types/DocumentUpdateMergedContent.ts | 9 --- .../services/types/DocumentUpdateMetadata.ts | 9 --- .../services/types/DocumentUpdateResponse.ts | 6 +- .../src/sync-operations/conflict-path.test.ts | 12 ++-- .../src/sync-operations/conflict-path.ts | 13 ---- sync-server/src/app_state/database/models.rs | 66 ------------------- sync-server/src/server/responses.rs | 7 +- 12 files changed, 20 insertions(+), 136 deletions(-) delete mode 100644 frontend/history-ui/src/lib/types/DocumentUpdateMergedContent.ts delete mode 100644 frontend/history-ui/src/lib/types/DocumentUpdateMetadata.ts delete mode 100644 frontend/sync-client/src/services/types/DocumentUpdateMergedContent.ts delete mode 100644 frontend/sync-client/src/services/types/DocumentUpdateMetadata.ts diff --git a/frontend/history-ui/src/lib/types/DocumentUpdateMergedContent.ts b/frontend/history-ui/src/lib/types/DocumentUpdateMergedContent.ts deleted file mode 100644 index 5fca495f..00000000 --- a/frontend/history-ui/src/lib/types/DocumentUpdateMergedContent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Like [`DocumentVersion`] but without the `relative_path`. - * Used only in create/update responses when the server had to merge the - * client's content with a newer remote version and therefore must echo - * the merged content back. - */ -export type DocumentUpdateMergedContent = { vaultUpdateId: number, documentId: string, updatedDate: string, contentBase64: string, isDeleted: boolean, userId: string, deviceId: string, }; diff --git a/frontend/history-ui/src/lib/types/DocumentUpdateMetadata.ts b/frontend/history-ui/src/lib/types/DocumentUpdateMetadata.ts deleted file mode 100644 index 393713fb..00000000 --- a/frontend/history-ui/src/lib/types/DocumentUpdateMetadata.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Like [`DocumentVersionWithoutContent`] but without the `relative_path`. - * Used only in create/update responses where the client already tracks - * the path locally (the server is the source of truth for the - * document identity, not its path). - */ -export type DocumentUpdateMetadata = { vaultUpdateId: number, documentId: string, updatedDate: string, isDeleted: boolean, userId: string, deviceId: string, contentSize: number, }; diff --git a/frontend/history-ui/src/lib/types/DocumentUpdateResponse.ts b/frontend/history-ui/src/lib/types/DocumentUpdateResponse.ts index 4e2ef3ab..51e0b37c 100644 --- a/frontend/history-ui/src/lib/types/DocumentUpdateResponse.ts +++ b/frontend/history-ui/src/lib/types/DocumentUpdateResponse.ts @@ -1,8 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { DocumentUpdateMergedContent } from "./DocumentUpdateMergedContent"; -import type { DocumentUpdateMetadata } from "./DocumentUpdateMetadata"; +import type { DocumentVersion } from "./DocumentVersion"; +import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutContent"; /** * Response to a create/update document request. */ -export type DocumentUpdateResponse = { "type": "FastForwardUpdate" } & DocumentUpdateMetadata | { "type": "MergingUpdate" } & DocumentUpdateMergedContent; +export type DocumentUpdateResponse = { "type": "FastForwardUpdate" } & DocumentVersionWithoutContent | { "type": "MergingUpdate" } & DocumentVersion; diff --git a/frontend/sync-client/src/file-operations/file-operations.test.ts b/frontend/sync-client/src/file-operations/file-operations.test.ts index 5a8f5af6..a69a5429 100644 --- a/frontend/sync-client/src/file-operations/file-operations.test.ts +++ b/frontend/sync-client/src/file-operations/file-operations.test.ts @@ -8,7 +8,7 @@ import { assertSetContainsExactly } from "../utils/assert-set-contains-exactly"; import type { FileSystemOperations } from "./filesystem-operations"; import type { TextWithCursors } from "reconcile-text"; import type { ServerConfig, ServerConfigData } from "../services/server-config"; -import { isConflictPath } from "../sync-operations/conflict-path"; +import { CONFLICT_PATH_REGEX } from "../sync-operations/conflict-path"; class MockServerConfig implements Pick { public async getConfig(): Promise { @@ -108,7 +108,7 @@ function singleConflictPath( `expected exactly one conflict-path entry, got ${JSON.stringify(conflicts)}` ); assert.ok( - isConflictPath(conflicts[0]), + CONFLICT_PATH_REGEX.test(conflicts[0]), `expected ${conflicts[0]} to match the conflict-path pattern` ); return conflicts[0]; @@ -195,7 +195,7 @@ describe("File operations", () => { (name) => name !== ".gitignore" && name !== ".config.json" ); assert.equal(conflicts.length, 2); - assert.ok(conflicts.every(isConflictPath)); + assert.ok(conflicts.every((c) => CONFLICT_PATH_REGEX.test(c))); assert.ok(conflicts.some((c) => c.endsWith("-.gitignore"))); assert.ok(conflicts.some((c) => c.endsWith("-.config.json"))); }); @@ -209,7 +209,7 @@ describe("File operations", () => { const conflicts = Array.from(fs.names).filter((n) => n !== "x"); assert.equal(conflicts.length, 2); - assert.ok(conflicts.every(isConflictPath)); + assert.ok(conflicts.every((c) => CONFLICT_PATH_REGEX.test(c))); assert.notEqual( conflicts[0], conflicts[1], diff --git a/frontend/sync-client/src/file-operations/file-operations.ts b/frontend/sync-client/src/file-operations/file-operations.ts index e2ffd4a5..530a6bcc 100644 --- a/frontend/sync-client/src/file-operations/file-operations.ts +++ b/frontend/sync-client/src/file-operations/file-operations.ts @@ -64,7 +64,7 @@ export class FileOperations { * * If a file is already there, it is moved aside to a `conflict--` * path in the same directory. The sync layer treats conflict-named files - * as invisible (see `isConflictPath`), so no events are enqueued and no + * as invisible (see `CONFLICT_PATH_REGEX`), so no events are enqueued and no * document records are touched — any pre-existing record or pending * events for the displaced path are left behind for the caller to * overwrite as part of whatever operation prompted the displacement. diff --git a/frontend/sync-client/src/services/types/DocumentUpdateMergedContent.ts b/frontend/sync-client/src/services/types/DocumentUpdateMergedContent.ts deleted file mode 100644 index 4e0e4af4..00000000 --- a/frontend/sync-client/src/services/types/DocumentUpdateMergedContent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Like [`DocumentVersion`] but without the `relative_path`. - * Used only in create/update responses when the server had to merge the - * client's content with a newer remote version and therefore must echo - * the merged content back. - */ -export interface DocumentUpdateMergedContent { vaultUpdateId: number, documentId: string, updatedDate: string, contentBase64: string, isDeleted: boolean, userId: string, deviceId: string, } diff --git a/frontend/sync-client/src/services/types/DocumentUpdateMetadata.ts b/frontend/sync-client/src/services/types/DocumentUpdateMetadata.ts deleted file mode 100644 index 325d896a..00000000 --- a/frontend/sync-client/src/services/types/DocumentUpdateMetadata.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Like [`DocumentVersionWithoutContent`] but without the `relative_path`. - * Used only in create/update responses where the client already tracks - * the path locally (the server is the source of truth for the - * document identity, not its path). - */ -export interface DocumentUpdateMetadata { vaultUpdateId: number, documentId: string, updatedDate: string, isDeleted: boolean, userId: string, deviceId: string, contentSize: number, } diff --git a/frontend/sync-client/src/services/types/DocumentUpdateResponse.ts b/frontend/sync-client/src/services/types/DocumentUpdateResponse.ts index 4e2ef3ab..51e0b37c 100644 --- a/frontend/sync-client/src/services/types/DocumentUpdateResponse.ts +++ b/frontend/sync-client/src/services/types/DocumentUpdateResponse.ts @@ -1,8 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { DocumentUpdateMergedContent } from "./DocumentUpdateMergedContent"; -import type { DocumentUpdateMetadata } from "./DocumentUpdateMetadata"; +import type { DocumentVersion } from "./DocumentVersion"; +import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutContent"; /** * Response to a create/update document request. */ -export type DocumentUpdateResponse = { "type": "FastForwardUpdate" } & DocumentUpdateMetadata | { "type": "MergingUpdate" } & DocumentUpdateMergedContent; +export type DocumentUpdateResponse = { "type": "FastForwardUpdate" } & DocumentVersionWithoutContent | { "type": "MergingUpdate" } & DocumentVersion; diff --git a/frontend/sync-client/src/sync-operations/conflict-path.test.ts b/frontend/sync-client/src/sync-operations/conflict-path.test.ts index ba39c238..7f7bf67c 100644 --- a/frontend/sync-client/src/sync-operations/conflict-path.test.ts +++ b/frontend/sync-client/src/sync-operations/conflict-path.test.ts @@ -1,6 +1,6 @@ import { describe, it } from "node:test"; import assert from "node:assert"; -import { buildConflictFileName, isConflictPath } from "./conflict-path"; +import { buildConflictFileName, CONFLICT_PATH_REGEX } from "./conflict-path"; describe("buildConflictFileName", () => { it("truncates to the filesystem byte limit while preserving the extension", () => { @@ -59,20 +59,20 @@ describe("buildConflictFileName", () => { }); }); -describe("isConflictPath", () => { +describe("CONFLICT_PATH_REGEX", () => { it("does not misclassify user-authored names that start with `conflict-`", () => { - assert.strictEqual(isConflictPath("conflict-resolution.md"), false); + assert.strictEqual(CONFLICT_PATH_REGEX.test("conflict-resolution.md"), false); }); it("only inspects the final path segment", () => { assert.strictEqual( - isConflictPath( + CONFLICT_PATH_REGEX.test( "conflict-12345678-1234-1234-1234-123456789abc-x/note.md" ), false ); assert.strictEqual( - isConflictPath( + CONFLICT_PATH_REGEX.test( "a/b/conflict-12345678-1234-1234-1234-123456789abc-note.md" ), true @@ -80,6 +80,6 @@ describe("isConflictPath", () => { }); it("round-trips with buildConflictFileName", () => { - assert.strictEqual(isConflictPath(buildConflictFileName("note.md")), true); + assert.strictEqual(CONFLICT_PATH_REGEX.test(buildConflictFileName("note.md")), true); }); }); diff --git a/frontend/sync-client/src/sync-operations/conflict-path.ts b/frontend/sync-client/src/sync-operations/conflict-path.ts index bd1d7c0b..7a634bb4 100644 --- a/frontend/sync-client/src/sync-operations/conflict-path.ts +++ b/frontend/sync-client/src/sync-operations/conflict-path.ts @@ -1,5 +1,3 @@ -import type { RelativePath } from "./types"; - // Local-only files displaced by `FileOperations.ensureClearPath` are named // `conflict--`. The UUID is a full RFC-4122 v4 value so // a user-authored filename that happens to start with `conflict-` doesn't @@ -55,14 +53,3 @@ function truncateFileNameToByteLimit( } return truncatedStem + extension; } - -/** - * Is `path`'s final segment a conflict-displaced filename? - * - * Any sync code that would otherwise create/update/delete/sync the path - * should short-circuit when this returns true: conflict-displaced files are - * strictly local and must stay invisible to the server. - */ -export function isConflictPath(path: RelativePath): boolean { - return CONFLICT_PATH_REGEX.test(path); -} diff --git a/sync-server/src/app_state/database/models.rs b/sync-server/src/app_state/database/models.rs index 80b628e8..b0e19c49 100644 --- a/sync-server/src/app_state/database/models.rs +++ b/sync-server/src/app_state/database/models.rs @@ -78,72 +78,6 @@ pub struct DocumentVersion { pub device_id: DeviceId, } -/// Like [`DocumentVersionWithoutContent`] but without the `relative_path`. -/// Used only in create/update responses where the client already tracks -/// the path locally (the server is the source of truth for the -/// document identity, not its path). -#[derive(TS, Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct DocumentUpdateMetadata { - #[ts(type = "number")] - pub vault_update_id: VaultUpdateId, - - pub document_id: DocumentId, - pub updated_date: DateTime, - pub is_deleted: bool, - pub user_id: UserId, - pub device_id: DeviceId, - - #[ts(type = "number")] - pub content_size: u64, -} - -impl From for DocumentUpdateMetadata { - fn from(value: StoredDocumentVersion) -> Self { - Self { - vault_update_id: value.vault_update_id, - document_id: value.document_id, - updated_date: value.updated_date, - is_deleted: value.is_deleted, - user_id: value.user_id, - device_id: value.device_id, - content_size: value.content.len() as u64, - } - } -} - -/// Like [`DocumentVersion`] but without the `relative_path`. -/// Used only in create/update responses when the server had to merge the -/// client's content with a newer remote version and therefore must echo -/// the merged content back. -#[derive(TS, Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct DocumentUpdateMergedContent { - #[ts(type = "number")] - pub vault_update_id: VaultUpdateId, - - pub document_id: DocumentId, - pub updated_date: DateTime, - pub content_base64: String, - pub is_deleted: bool, - pub user_id: UserId, - pub device_id: DeviceId, -} - -impl From for DocumentUpdateMergedContent { - fn from(value: StoredDocumentVersion) -> Self { - Self { - vault_update_id: value.vault_update_id, - document_id: value.document_id, - updated_date: value.updated_date, - content_base64: STANDARD.encode(&value.content), - is_deleted: value.is_deleted, - user_id: value.user_id, - device_id: value.device_id, - } - } -} - /// Row struct for vault history queries (used by `sqlx::query_as!`) #[derive(Debug)] pub struct VaultHistoryRow { diff --git a/sync-server/src/server/responses.rs b/sync-server/src/server/responses.rs index 18158e65..f5b30782 100644 --- a/sync-server/src/server/responses.rs +++ b/sync-server/src/server/responses.rs @@ -3,8 +3,7 @@ use serde::{self, Serialize}; use ts_rs::TS; use crate::app_state::database::models::{ - DocumentUpdateMergedContent, DocumentUpdateMetadata, DocumentVersionWithoutContent, - VaultUpdateId, + DocumentVersion, DocumentVersionWithoutContent, VaultUpdateId, }; /// Response to a ping request. @@ -75,9 +74,9 @@ pub enum DocumentUpdateResponse { /// Returned when the created/updated document's content is the same as was /// sent in the create/update request and thus the response doesn't contain /// the content because the client must already have it. - FastForwardUpdate(DocumentUpdateMetadata), + FastForwardUpdate(DocumentVersionWithoutContent), /// Returned when the created/updated document's content is different from /// what was sent in the create/update request. - MergingUpdate(DocumentUpdateMergedContent), + MergingUpdate(DocumentVersion), }