return paths
This commit is contained in:
parent
c9cf3239db
commit
aecbcd1d2c
12 changed files with 20 additions and 136 deletions
|
|
@ -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, };
|
||||
|
|
@ -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, };
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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, }
|
||||
|
|
@ -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, }
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Utc>,
|
||||
pub is_deleted: bool,
|
||||
pub user_id: UserId,
|
||||
pub device_id: DeviceId,
|
||||
|
||||
#[ts(type = "number")]
|
||||
pub content_size: u64,
|
||||
}
|
||||
|
||||
impl From<StoredDocumentVersion> 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<Utc>,
|
||||
pub content_base64: String,
|
||||
pub is_deleted: bool,
|
||||
pub user_id: UserId,
|
||||
pub device_id: DeviceId,
|
||||
}
|
||||
|
||||
impl From<StoredDocumentVersion> 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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue