return paths

This commit is contained in:
Andras Schmelczer 2026-04-25 08:40:40 +01:00
parent c9cf3239db
commit aecbcd1d2c
12 changed files with 20 additions and 136 deletions

View file

@ -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, };

View file

@ -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, };

View file

@ -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;

View file

@ -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<ServerConfig, "getConfig"> {
public async getConfig(): Promise<ServerConfigData> {
@ -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],

View file

@ -64,7 +64,7 @@ export class FileOperations {
*
* If a file is already there, it is moved aside to a `conflict-<uuid>-<name>`
* 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.

View file

@ -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, }

View file

@ -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, }

View file

@ -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;

View file

@ -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);
});
});

View file

@ -1,5 +1,3 @@
import type { RelativePath } from "./types";
// Local-only files displaced by `FileOperations.ensureClearPath` are named
// `conflict-<uuid>-<originalName>`. 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);
}