lgtm
This commit is contained in:
parent
cc44b66fcd
commit
1163da826e
45 changed files with 192 additions and 292 deletions
|
|
@ -1,8 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { DocumentWithCursors } from "./DocumentWithCursors";
|
import type { DocumentWithCursors } from "./DocumentWithCursors";
|
||||||
|
|
||||||
export type ClientCursors = {
|
export type ClientCursors = { userName: string, deviceId: string, documentsWithCursors: Array<DocumentWithCursors>, };
|
||||||
userName: string;
|
|
||||||
deviceId: string;
|
|
||||||
documentsWithCursors: Array<DocumentWithCursors>;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type CreateDocumentVersion = {
|
export type CreateDocumentVersion = { relative_path: string, last_seen_vault_update_id: number, content: Array<number>, };
|
||||||
relative_path: string;
|
|
||||||
last_seen_vault_update_id: number;
|
|
||||||
content: Array<number>;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { DocumentWithCursors } from "./DocumentWithCursors";
|
import type { DocumentWithCursors } from "./DocumentWithCursors";
|
||||||
|
|
||||||
export type CursorPositionFromClient = {
|
export type CursorPositionFromClient = { documentsWithCursors: Array<DocumentWithCursors>, };
|
||||||
documentsWithCursors: Array<DocumentWithCursors>;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { ClientCursors } from "./ClientCursors";
|
import type { ClientCursors } from "./ClientCursors";
|
||||||
|
|
||||||
export type CursorPositionFromServer = { clients: Array<ClientCursors> };
|
export type CursorPositionFromServer = { clients: Array<ClientCursors>, };
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type CursorSpan = { start: number; end: number };
|
export type CursorSpan = { start: number, end: number, };
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,4 @@ import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutCont
|
||||||
/**
|
/**
|
||||||
* Response to a create/update document request.
|
* Response to a create/update document request.
|
||||||
*/
|
*/
|
||||||
export type DocumentUpdateResponse =
|
export type DocumentUpdateResponse = { "type": "FastForwardUpdate" } & DocumentVersionWithoutContent | { "type": "MergingUpdate" } & DocumentVersion;
|
||||||
| ({ type: "FastForwardUpdate" } & DocumentVersionWithoutContent)
|
|
||||||
| ({ type: "MergingUpdate" } & DocumentVersion);
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type DocumentVersion = {
|
export type DocumentVersion = { vaultUpdateId: number, documentId: string, relativePath: string, updatedDate: string, contentBase64: string, isDeleted: boolean, userId: string, deviceId: string, };
|
||||||
vaultUpdateId: number;
|
|
||||||
documentId: string;
|
|
||||||
relativePath: string;
|
|
||||||
updatedDate: string;
|
|
||||||
contentBase64: string;
|
|
||||||
isDeleted: boolean;
|
|
||||||
userId: string;
|
|
||||||
deviceId: string;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type DocumentVersionWithoutContent = {
|
export type DocumentVersionWithoutContent = { vaultUpdateId: number, documentId: string, relativePath: string, updatedDate: string, isDeleted: boolean, userId: string, deviceId: string, contentSize: number,
|
||||||
vaultUpdateId: number;
|
/**
|
||||||
documentId: string;
|
* True iff this is the first version of the document
|
||||||
relativePath: string;
|
*/
|
||||||
updatedDate: string;
|
isNewFile: boolean, };
|
||||||
isDeleted: boolean;
|
|
||||||
userId: string;
|
|
||||||
deviceId: string;
|
|
||||||
contentSize: number;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { CursorSpan } from "./CursorSpan";
|
import type { CursorSpan } from "./CursorSpan";
|
||||||
|
|
||||||
export type DocumentWithCursors = {
|
export type DocumentWithCursors = { vaultUpdateId: number | null, documentId: string, relativePath: string, cursors: Array<CursorSpan>, };
|
||||||
vaultUpdateId: number | null;
|
|
||||||
documentId: string;
|
|
||||||
relativePath: string;
|
|
||||||
cursors: Array<CursorSpan>;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,8 @@ import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutCont
|
||||||
/**
|
/**
|
||||||
* Response to a fetch latest documents request.
|
* Response to a fetch latest documents request.
|
||||||
*/
|
*/
|
||||||
export type FetchLatestDocumentsResponse = {
|
export type FetchLatestDocumentsResponse = { latestDocuments: Array<DocumentVersionWithoutContent>,
|
||||||
latestDocuments: Array<DocumentVersionWithoutContent>;
|
/**
|
||||||
/**
|
* The update ID of the latest document in the response.
|
||||||
* The update ID of the latest document in the response.
|
*/
|
||||||
*/
|
lastUpdateId: bigint, };
|
||||||
lastUpdateId: bigint;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,4 @@ import type { VaultInfo } from "./VaultInfo";
|
||||||
/**
|
/**
|
||||||
* Response to listing vaults accessible to the authenticated user.
|
* Response to listing vaults accessible to the authenticated user.
|
||||||
*/
|
*/
|
||||||
export type ListVaultsResponse = {
|
export type ListVaultsResponse = { vaults: Array<VaultInfo>, hasMore: boolean, userName: string, };
|
||||||
vaults: Array<VaultInfo>;
|
|
||||||
hasMore: boolean;
|
|
||||||
userName: string;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,22 @@
|
||||||
/**
|
/**
|
||||||
* Response to a ping request.
|
* Response to a ping request.
|
||||||
*/
|
*/
|
||||||
export type PingResponse = {
|
export type PingResponse = {
|
||||||
/**
|
/**
|
||||||
* Semantic version of the server.
|
* Semantic version of the server.
|
||||||
*/
|
*/
|
||||||
serverVersion: string;
|
serverVersion: string,
|
||||||
/**
|
/**
|
||||||
* Whether the client is authenticated based on the sent Authorization
|
* Whether the client is authenticated based on the sent Authorization
|
||||||
* header.
|
* header.
|
||||||
*/
|
*/
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean,
|
||||||
/**
|
/**
|
||||||
* List of file extensions that are allowed to be merged.
|
* List of file extensions that are allowed to be merged.
|
||||||
*/
|
*/
|
||||||
mergeableFileExtensions: Array<string>;
|
mergeableFileExtensions: Array<string>,
|
||||||
/**
|
/**
|
||||||
* API version ensuring backwards & forwards compatibility between the client
|
* API version ensuring backwards & forwards compatibility between the client
|
||||||
* and server.
|
* and server.
|
||||||
*/
|
*/
|
||||||
supportedApiVersion: number;
|
supportedApiVersion: number, };
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type SerializedError = {
|
export type SerializedError = { errorType: string, message: string, causes: Array<string>, };
|
||||||
errorType: string;
|
|
||||||
message: string;
|
|
||||||
causes: Array<string>;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type UpdateTextDocumentVersion = {
|
export type UpdateTextDocumentVersion = { parentVersionId: number, relativePath: string | null, content: Array<number | string>, };
|
||||||
parentVersionId: number;
|
|
||||||
relativePath: string;
|
|
||||||
content: Array<number | string>;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,4 @@ import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutCont
|
||||||
/**
|
/**
|
||||||
* Response to a vault history request (paginated).
|
* Response to a vault history request (paginated).
|
||||||
*/
|
*/
|
||||||
export type VaultHistoryResponse = {
|
export type VaultHistoryResponse = { versions: Array<DocumentVersionWithoutContent>, hasMore: boolean, };
|
||||||
versions: Array<DocumentVersionWithoutContent>;
|
|
||||||
hasMore: boolean;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,4 @@
|
||||||
/**
|
/**
|
||||||
* Summary of a single vault returned by the list-vaults endpoint.
|
* Summary of a single vault returned by the list-vaults endpoint.
|
||||||
*/
|
*/
|
||||||
export type VaultInfo = {
|
export type VaultInfo = { name: string, documentCount: number, createdAt: string | null, };
|
||||||
name: string;
|
|
||||||
documentCount: number;
|
|
||||||
createdAt: string | null;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,4 @@
|
||||||
import type { CursorPositionFromClient } from "./CursorPositionFromClient";
|
import type { CursorPositionFromClient } from "./CursorPositionFromClient";
|
||||||
import type { WebSocketHandshake } from "./WebSocketHandshake";
|
import type { WebSocketHandshake } from "./WebSocketHandshake";
|
||||||
|
|
||||||
export type WebSocketClientMessage =
|
export type WebSocketClientMessage = { "type": "handshake" } & WebSocketHandshake | { "type": "cursorPositions" } & CursorPositionFromClient;
|
||||||
| ({ type: "handshake" } & WebSocketHandshake)
|
|
||||||
| ({ type: "cursorPositions" } & CursorPositionFromClient);
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type WebSocketHandshake = {
|
export type WebSocketHandshake = { token: string, deviceId: string, lastSeenVaultUpdateId: number | null, };
|
||||||
token: string;
|
|
||||||
deviceId: string;
|
|
||||||
lastSeenVaultUpdateId: number | null;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,4 @@
|
||||||
import type { CursorPositionFromServer } from "./CursorPositionFromServer";
|
import type { CursorPositionFromServer } from "./CursorPositionFromServer";
|
||||||
import type { WebSocketVaultUpdate } from "./WebSocketVaultUpdate";
|
import type { WebSocketVaultUpdate } from "./WebSocketVaultUpdate";
|
||||||
|
|
||||||
export type WebSocketServerMessage =
|
export type WebSocketServerMessage = { "type": "vaultUpdate" } & WebSocketVaultUpdate | { "type": "cursorPositions" } & CursorPositionFromServer;
|
||||||
| ({ type: "vaultUpdate" } & WebSocketVaultUpdate)
|
|
||||||
| ({ type: "cursorPositions" } & CursorPositionFromServer);
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutContent";
|
import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutContent";
|
||||||
|
|
||||||
export type WebSocketVaultUpdate = { document: DocumentVersionWithoutContent };
|
export type WebSocketVaultUpdate = { document: DocumentVersionWithoutContent, };
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,20 @@ export enum MoveOnConflict {
|
||||||
NEW = "NEW"
|
NEW = "NEW"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outcome of a `move`/`create`. `actualPath` is where the new file
|
||||||
|
* ended up (which may differ from the requested path under
|
||||||
|
* `MoveOnConflict.NEW` if the target was occupied). `displacedTo` is
|
||||||
|
* set only when an existing file at the requested path was bumped to
|
||||||
|
* a `conflict-…` path under `MoveOnConflict.EXISTING`; the caller
|
||||||
|
* uses it to repoint any tracking for the displaced doc before its
|
||||||
|
* own follow-up `setDocument` clobbers the old slot.
|
||||||
|
*/
|
||||||
|
export interface FileOpResult {
|
||||||
|
actualPath: RelativePath;
|
||||||
|
displacedTo?: RelativePath;
|
||||||
|
}
|
||||||
|
|
||||||
export class FileOperations {
|
export class FileOperations {
|
||||||
private readonly fs: SafeFileSystemOperations;
|
private readonly fs: SafeFileSystemOperations;
|
||||||
|
|
||||||
|
|
@ -67,29 +81,27 @@ export class FileOperations {
|
||||||
*
|
*
|
||||||
* If a file with the same name already exists, it is moved before creating the new one.
|
* If a file with the same name already exists, it is moved before creating the new one.
|
||||||
* Parent directories are created if necessary.
|
* Parent directories are created if necessary.
|
||||||
*
|
|
||||||
* Returns the actual path the file was created at.
|
|
||||||
*/
|
*/
|
||||||
public async create(
|
public async create(
|
||||||
path: RelativePath,
|
path: RelativePath,
|
||||||
newContent: Uint8Array,
|
newContent: Uint8Array,
|
||||||
moveOnConflict: MoveOnConflict
|
moveOnConflict: MoveOnConflict
|
||||||
): Promise<RelativePath> {
|
): Promise<FileOpResult> {
|
||||||
const actualPath = await this.ensureClearPath(path, moveOnConflict);
|
const result = await this.ensureClearPath(path, moveOnConflict);
|
||||||
// ensureClearPath leaves actualPath empty: either the file never
|
// ensureClearPath leaves actualPath empty: either the file never
|
||||||
// existed, or it was just renamed away. The upcoming write therefore
|
// existed, or it was just renamed away. The upcoming write therefore
|
||||||
// looks like a fresh create to the watcher.
|
// looks like a fresh create to the watcher.
|
||||||
this.expectedFsEvents.expectCreate(actualPath);
|
this.expectedFsEvents.expectCreate(result.actualPath);
|
||||||
try {
|
try {
|
||||||
await this.fs.write(
|
await this.fs.write(
|
||||||
actualPath,
|
result.actualPath,
|
||||||
this.toNativeLineEndings(newContent)
|
this.toNativeLineEndings(newContent)
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.expectedFsEvents.unexpectCreate(actualPath);
|
this.expectedFsEvents.unexpectCreate(result.actualPath);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return actualPath;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -215,37 +227,36 @@ export class FileOperations {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Returns the actual path the file got moved to.
|
|
||||||
public async move(
|
public async move(
|
||||||
oldPath: RelativePath,
|
oldPath: RelativePath,
|
||||||
newPath: RelativePath,
|
newPath: RelativePath,
|
||||||
moveOnConflict: MoveOnConflict
|
moveOnConflict: MoveOnConflict
|
||||||
): Promise<RelativePath> {
|
): Promise<FileOpResult> {
|
||||||
if (oldPath === newPath) {
|
if (oldPath === newPath) {
|
||||||
return oldPath;
|
return { actualPath: oldPath };
|
||||||
}
|
}
|
||||||
|
|
||||||
const actualPath = await this.ensureClearPath(newPath, moveOnConflict);
|
const cleared = await this.ensureClearPath(newPath, moveOnConflict);
|
||||||
this.expectedFsEvents.expectRename(oldPath, actualPath);
|
this.expectedFsEvents.expectRename(oldPath, cleared.actualPath);
|
||||||
try {
|
try {
|
||||||
await this.fs.rename(oldPath, actualPath);
|
await this.fs.rename(oldPath, cleared.actualPath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.expectedFsEvents.unexpectRename(oldPath, actualPath);
|
this.expectedFsEvents.unexpectRename(oldPath, cleared.actualPath);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
await this.deletingEmptyParentDirectoriesOfDeletedFile(oldPath);
|
await this.deletingEmptyParentDirectoriesOfDeletedFile(oldPath);
|
||||||
return actualPath;
|
return cleared;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ensureClearPath(
|
private async ensureClearPath(
|
||||||
path: RelativePath,
|
path: RelativePath,
|
||||||
moveOnConflict: MoveOnConflict
|
moveOnConflict: MoveOnConflict
|
||||||
): Promise<RelativePath> {
|
): Promise<FileOpResult> {
|
||||||
if (await this.fs.exists(path)) {
|
if (await this.fs.exists(path)) {
|
||||||
const conflictPath = FileOperations.buildConflictPath(path);
|
const conflictPath = FileOperations.buildConflictPath(path);
|
||||||
|
|
||||||
if (moveOnConflict === MoveOnConflict.NEW) {
|
if (moveOnConflict === MoveOnConflict.NEW) {
|
||||||
return conflictPath;
|
return { actualPath: conflictPath };
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
|
|
@ -266,7 +277,7 @@ export class FileOperations {
|
||||||
`No existing file at ${path}, creating parent directories if needed`
|
`No existing file at ${path}, creating parent directories if needed`
|
||||||
);
|
);
|
||||||
await this.createParentDirectories(path);
|
await this.createParentDirectories(path);
|
||||||
return path;
|
return { actualPath: path };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async deletingEmptyParentDirectoriesOfDeletedFile(
|
private async deletingEmptyParentDirectoriesOfDeletedFile(
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export class SyncService {
|
||||||
response: Response,
|
response: Response,
|
||||||
operation: string
|
operation: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (response.ok) {return;}
|
if (response.ok) { return; }
|
||||||
const message = `Failed to ${operation}: ${await SyncService.errorFromResponse(response)}`;
|
const message = `Failed to ${operation}: ${await SyncService.errorFromResponse(response)}`;
|
||||||
// 429 is the only 4xx the server uses for *transient* contention
|
// 429 is the only 4xx the server uses for *transient* contention
|
||||||
// (`WriteBusyError` → HTTP 429). Every other 4xx means the request
|
// (`WriteBusyError` → HTTP 429). Every other 4xx means the request
|
||||||
|
|
@ -154,17 +154,17 @@ export class SyncService {
|
||||||
}: {
|
}: {
|
||||||
parentVersionId: VaultUpdateId;
|
parentVersionId: VaultUpdateId;
|
||||||
documentId: DocumentId;
|
documentId: DocumentId;
|
||||||
relativePath: RelativePath;
|
relativePath: RelativePath | undefined;
|
||||||
content: (number | string)[];
|
content: (number | string)[];
|
||||||
}): Promise<DocumentUpdateResponse> {
|
}): Promise<DocumentUpdateResponse> {
|
||||||
return this.retryForever(async () => {
|
return this.retryForever(async () => {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Updating text document ${documentId} with parent version ${parentVersionId} and relative path ${relativePath}, content [${content.join(", ")}]`
|
`Updating text document ${documentId} with parent version ${parentVersionId} and relative path ${relativePath ?? "<unchanged>"}, content [${content.join(", ")}]`
|
||||||
);
|
);
|
||||||
|
|
||||||
const request: UpdateTextDocumentVersion = {
|
const request: UpdateTextDocumentVersion = {
|
||||||
parentVersionId,
|
parentVersionId,
|
||||||
relativePath,
|
relativePath: relativePath ?? null,
|
||||||
content
|
content
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -199,16 +199,18 @@ export class SyncService {
|
||||||
}: {
|
}: {
|
||||||
parentVersionId: VaultUpdateId;
|
parentVersionId: VaultUpdateId;
|
||||||
documentId: DocumentId;
|
documentId: DocumentId;
|
||||||
relativePath: RelativePath;
|
relativePath: RelativePath | undefined;
|
||||||
contentBytes: Uint8Array;
|
contentBytes: Uint8Array;
|
||||||
}): Promise<DocumentUpdateResponse> {
|
}): Promise<DocumentUpdateResponse> {
|
||||||
return this.retryForever(async () => {
|
return this.retryForever(async () => {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Updating binary document ${documentId} with parent version ${parentVersionId} and relative path ${relativePath}`
|
`Updating binary document ${documentId} with parent version ${parentVersionId} and relative path ${relativePath ?? "<unchanged>"}`
|
||||||
);
|
);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("parent_version_id", parentVersionId.toString());
|
formData.append("parent_version_id", parentVersionId.toString());
|
||||||
formData.append("relative_path", relativePath);
|
if (relativePath !== undefined) {
|
||||||
|
formData.append("relative_path", relativePath);
|
||||||
|
}
|
||||||
formData.append(
|
formData.append(
|
||||||
"content",
|
"content",
|
||||||
new Blob([new Uint8Array(contentBytes)])
|
new Blob([new Uint8Array(contentBytes)])
|
||||||
|
|
@ -239,14 +241,12 @@ export class SyncService {
|
||||||
|
|
||||||
public async delete({
|
public async delete({
|
||||||
documentId,
|
documentId,
|
||||||
relativePath
|
|
||||||
}: {
|
}: {
|
||||||
documentId: DocumentId;
|
documentId: DocumentId;
|
||||||
relativePath: RelativePath;
|
|
||||||
}): Promise<DocumentVersionWithoutContent> {
|
}): Promise<DocumentVersionWithoutContent> {
|
||||||
return this.retryForever(async () => {
|
return this.retryForever(async () => {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Delete document with id ${documentId} and relative path ${relativePath}`
|
`Delete document with id ${documentId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// The server identifies the document by its URL path; no body
|
// The server identifies the document by its URL path; no body
|
||||||
|
|
@ -265,7 +265,7 @@ export class SyncService {
|
||||||
(await response.json()) as DocumentVersionWithoutContent; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
(await response.json()) as DocumentVersionWithoutContent; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Deleted document ${relativePath} with id ${documentId}`
|
`Deleted document with id ${documentId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { DocumentWithCursors } from "./DocumentWithCursors";
|
import type { DocumentWithCursors } from "./DocumentWithCursors";
|
||||||
|
|
||||||
export interface ClientCursors {
|
export interface ClientCursors { userName: string, deviceId: string, documentsWithCursors: DocumentWithCursors[], }
|
||||||
userName: string;
|
|
||||||
deviceId: string;
|
|
||||||
documentsWithCursors: DocumentWithCursors[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export interface CreateDocumentVersion {
|
export interface CreateDocumentVersion { relative_path: string, last_seen_vault_update_id: number, content: number[], }
|
||||||
relative_path: string;
|
|
||||||
last_seen_vault_update_id: number;
|
|
||||||
content: number[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { DocumentWithCursors } from "./DocumentWithCursors";
|
import type { DocumentWithCursors } from "./DocumentWithCursors";
|
||||||
|
|
||||||
export interface CursorPositionFromClient {
|
export interface CursorPositionFromClient { documentsWithCursors: DocumentWithCursors[], }
|
||||||
documentsWithCursors: DocumentWithCursors[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { ClientCursors } from "./ClientCursors";
|
import type { ClientCursors } from "./ClientCursors";
|
||||||
|
|
||||||
export interface CursorPositionFromServer {
|
export interface CursorPositionFromServer { clients: ClientCursors[], }
|
||||||
clients: ClientCursors[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export interface CursorSpan {
|
export interface CursorSpan { start: number, end: number, }
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,4 @@ import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutCont
|
||||||
/**
|
/**
|
||||||
* Response to a create/update document request.
|
* Response to a create/update document request.
|
||||||
*/
|
*/
|
||||||
export type DocumentUpdateResponse =
|
export type DocumentUpdateResponse = { "type": "FastForwardUpdate" } & DocumentVersionWithoutContent | { "type": "MergingUpdate" } & DocumentVersion;
|
||||||
| ({ type: "FastForwardUpdate" } & DocumentVersionWithoutContent)
|
|
||||||
| ({ type: "MergingUpdate" } & DocumentVersion);
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export interface DocumentVersion {
|
export interface DocumentVersion { vaultUpdateId: number, documentId: string, relativePath: string, updatedDate: string, contentBase64: string, isDeleted: boolean, userId: string, deviceId: string, }
|
||||||
vaultUpdateId: number;
|
|
||||||
documentId: string;
|
|
||||||
relativePath: string;
|
|
||||||
updatedDate: string;
|
|
||||||
contentBase64: string;
|
|
||||||
isDeleted: boolean;
|
|
||||||
userId: string;
|
|
||||||
deviceId: string;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export interface DocumentVersionWithoutContent {
|
export interface DocumentVersionWithoutContent { vaultUpdateId: number, documentId: string, relativePath: string, updatedDate: string, isDeleted: boolean, userId: string, deviceId: string, contentSize: number,
|
||||||
vaultUpdateId: number;
|
/**
|
||||||
documentId: string;
|
* True iff this is the first version of the document
|
||||||
relativePath: string;
|
*/
|
||||||
updatedDate: string;
|
isNewFile: boolean, }
|
||||||
isDeleted: boolean;
|
|
||||||
userId: string;
|
|
||||||
deviceId: string;
|
|
||||||
contentSize: number;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { CursorSpan } from "./CursorSpan";
|
import type { CursorSpan } from "./CursorSpan";
|
||||||
|
|
||||||
export interface DocumentWithCursors {
|
export interface DocumentWithCursors { vaultUpdateId: number | null, documentId: string, relativePath: string, cursors: CursorSpan[], }
|
||||||
vaultUpdateId: number | null;
|
|
||||||
documentId: string;
|
|
||||||
relativePath: string;
|
|
||||||
cursors: CursorSpan[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,8 @@ import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutCont
|
||||||
/**
|
/**
|
||||||
* Response to a fetch latest documents request.
|
* Response to a fetch latest documents request.
|
||||||
*/
|
*/
|
||||||
export interface FetchLatestDocumentsResponse {
|
export interface FetchLatestDocumentsResponse { latestDocuments: DocumentVersionWithoutContent[],
|
||||||
latestDocuments: DocumentVersionWithoutContent[];
|
/**
|
||||||
/**
|
* The update ID of the latest document in the response.
|
||||||
* The update ID of the latest document in the response.
|
*/
|
||||||
*/
|
lastUpdateId: bigint, }
|
||||||
lastUpdateId: bigint;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,4 @@ import type { VaultInfo } from "./VaultInfo";
|
||||||
/**
|
/**
|
||||||
* Response to listing vaults accessible to the authenticated user.
|
* Response to listing vaults accessible to the authenticated user.
|
||||||
*/
|
*/
|
||||||
export interface ListVaultsResponse {
|
export interface ListVaultsResponse { vaults: VaultInfo[], hasMore: boolean, userName: string, }
|
||||||
vaults: VaultInfo[];
|
|
||||||
hasMore: boolean;
|
|
||||||
userName: string;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,22 @@
|
||||||
/**
|
/**
|
||||||
* Response to a ping request.
|
* Response to a ping request.
|
||||||
*/
|
*/
|
||||||
export interface PingResponse {
|
export interface PingResponse {
|
||||||
/**
|
/**
|
||||||
* Semantic version of the server.
|
* Semantic version of the server.
|
||||||
*/
|
*/
|
||||||
serverVersion: string;
|
serverVersion: string,
|
||||||
/**
|
/**
|
||||||
* Whether the client is authenticated based on the sent Authorization
|
* Whether the client is authenticated based on the sent Authorization
|
||||||
* header.
|
* header.
|
||||||
*/
|
*/
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean,
|
||||||
/**
|
/**
|
||||||
* List of file extensions that are allowed to be merged.
|
* List of file extensions that are allowed to be merged.
|
||||||
*/
|
*/
|
||||||
mergeableFileExtensions: string[];
|
mergeableFileExtensions: string[],
|
||||||
/**
|
/**
|
||||||
* API version ensuring backwards & forwards compatibility between the client
|
* API version ensuring backwards & forwards compatibility between the client
|
||||||
* and server.
|
* and server.
|
||||||
*/
|
*/
|
||||||
supportedApiVersion: number;
|
supportedApiVersion: number, }
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export interface SerializedError {
|
export interface SerializedError { errorType: string, message: string, causes: string[], }
|
||||||
errorType: string;
|
|
||||||
message: string;
|
|
||||||
causes: string[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export interface UpdateTextDocumentVersion {
|
export interface UpdateTextDocumentVersion { parentVersionId: number, relativePath: string | null, content: (number | string)[], }
|
||||||
parentVersionId: number;
|
|
||||||
relativePath: string;
|
|
||||||
content: (number | string)[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,4 @@ import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutCont
|
||||||
/**
|
/**
|
||||||
* Response to a vault history request (paginated).
|
* Response to a vault history request (paginated).
|
||||||
*/
|
*/
|
||||||
export interface VaultHistoryResponse {
|
export interface VaultHistoryResponse { versions: DocumentVersionWithoutContent[], hasMore: boolean, }
|
||||||
versions: DocumentVersionWithoutContent[];
|
|
||||||
hasMore: boolean;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,4 @@
|
||||||
/**
|
/**
|
||||||
* Summary of a single vault returned by the list-vaults endpoint.
|
* Summary of a single vault returned by the list-vaults endpoint.
|
||||||
*/
|
*/
|
||||||
export interface VaultInfo {
|
export interface VaultInfo { name: string, documentCount: number, createdAt: string | null, }
|
||||||
name: string;
|
|
||||||
documentCount: number;
|
|
||||||
createdAt: string | null;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,4 @@
|
||||||
import type { CursorPositionFromClient } from "./CursorPositionFromClient";
|
import type { CursorPositionFromClient } from "./CursorPositionFromClient";
|
||||||
import type { WebSocketHandshake } from "./WebSocketHandshake";
|
import type { WebSocketHandshake } from "./WebSocketHandshake";
|
||||||
|
|
||||||
export type WebSocketClientMessage =
|
export type WebSocketClientMessage = { "type": "handshake" } & WebSocketHandshake | { "type": "cursorPositions" } & CursorPositionFromClient;
|
||||||
| ({ type: "handshake" } & WebSocketHandshake)
|
|
||||||
| ({ type: "cursorPositions" } & CursorPositionFromClient);
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export interface WebSocketHandshake {
|
export interface WebSocketHandshake { token: string, deviceId: string, lastSeenVaultUpdateId: number | null, }
|
||||||
token: string;
|
|
||||||
deviceId: string;
|
|
||||||
lastSeenVaultUpdateId: number | null;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,4 @@
|
||||||
import type { CursorPositionFromServer } from "./CursorPositionFromServer";
|
import type { CursorPositionFromServer } from "./CursorPositionFromServer";
|
||||||
import type { WebSocketVaultUpdate } from "./WebSocketVaultUpdate";
|
import type { WebSocketVaultUpdate } from "./WebSocketVaultUpdate";
|
||||||
|
|
||||||
export type WebSocketServerMessage =
|
export type WebSocketServerMessage = { "type": "vaultUpdate" } & WebSocketVaultUpdate | { "type": "cursorPositions" } & CursorPositionFromServer;
|
||||||
| ({ type: "vaultUpdate" } & WebSocketVaultUpdate)
|
|
||||||
| ({ type: "cursorPositions" } & CursorPositionFromServer);
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutContent";
|
import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutContent";
|
||||||
|
|
||||||
export interface WebSocketVaultUpdate {
|
export interface WebSocketVaultUpdate { document: DocumentVersionWithoutContent, }
|
||||||
document: DocumentVersionWithoutContent;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ function fakeRemoteVersion(
|
||||||
userId: "user",
|
userId: "user",
|
||||||
deviceId: "device",
|
deviceId: "device",
|
||||||
contentSize: 100,
|
contentSize: 100,
|
||||||
|
isNewFile: true,
|
||||||
...overrides
|
...overrides
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -446,19 +446,8 @@ export class Syncer {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const documentId = await event.documentId;
|
const documentId = await event.documentId;
|
||||||
|
|
||||||
const doc = this.queue.getDocumentByDocumentId(documentId);
|
|
||||||
if (doc === undefined) {
|
|
||||||
// Already deleted (e.g. a remote delete drained ahead of
|
|
||||||
// this redundant local one). Nothing to do.
|
|
||||||
this.logger.debug(
|
|
||||||
`Skipping local-delete for ${documentId} — doc no longer tracked`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await this.syncService.delete({
|
const response = await this.syncService.delete({
|
||||||
documentId,
|
documentId,
|
||||||
relativePath: doc.path
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Don't remove the doc from the queue or advance lastSeenUpdateId
|
// Don't remove the doc from the queue or advance lastSeenUpdateId
|
||||||
|
|
@ -471,7 +460,7 @@ export class Syncer {
|
||||||
status: SyncStatus.SUCCESS,
|
status: SyncStatus.SUCCESS,
|
||||||
details: {
|
details: {
|
||||||
type: SyncType.DELETE,
|
type: SyncType.DELETE,
|
||||||
relativePath: doc.path
|
relativePath: event.path
|
||||||
},
|
},
|
||||||
message: "Successfully deleted file on the server",
|
message: "Successfully deleted file on the server",
|
||||||
author: response.userId,
|
author: response.userId,
|
||||||
|
|
@ -499,8 +488,21 @@ export class Syncer {
|
||||||
const contentBytes = await this.operations.read(diskPath);
|
const contentBytes = await this.operations.read(diskPath);
|
||||||
const contentHash = await hash(contentBytes);
|
const contentHash = await hash(contentBytes);
|
||||||
|
|
||||||
|
// For a user-driven rename the user's intent is `event.originalPath`
|
||||||
|
// — that's the rename target. For a content-only edit the user is
|
||||||
|
// agnostic to the path; sending one would be wrong if a remote
|
||||||
|
// rename processed first, because the server would interpret the
|
||||||
|
// user's (now-stale) path as a rename back. So content-only PUTs
|
||||||
|
// omit the path and the server keeps the doc at its current
|
||||||
|
// server-known location.
|
||||||
|
const renameTarget = event.isUserRename
|
||||||
|
? event.originalPath
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const hashChanged = contentHash !== record.remoteHash;
|
const hashChanged = contentHash !== record.remoteHash;
|
||||||
const pathChanged = record.remoteRelativePath !== event.originalPath;
|
const pathChanged =
|
||||||
|
renameTarget !== undefined &&
|
||||||
|
record.remoteRelativePath !== renameTarget;
|
||||||
|
|
||||||
if (!hashChanged && !pathChanged) {
|
if (!hashChanged && !pathChanged) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
|
|
@ -511,12 +513,16 @@ export class Syncer {
|
||||||
|
|
||||||
const response = await this.sendUpdate({
|
const response = await this.sendUpdate({
|
||||||
record,
|
record,
|
||||||
relativePath: event.originalPath,
|
relativePath: renameTarget,
|
||||||
contentBytes
|
contentBytes
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.isDeleted) {
|
if (response.isDeleted) {
|
||||||
await this.processRemoteDelete(diskPath, { ...response, contentSize: 0 });
|
await this.processRemoteDelete(diskPath, {
|
||||||
|
...response,
|
||||||
|
contentSize: 0,
|
||||||
|
isNewFile: false
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -716,6 +722,14 @@ export class Syncer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!remoteVersion.isNewFile) {
|
||||||
|
this.queue.lastSeenUpdateId = remoteVersion.vaultUpdateId;
|
||||||
|
this.logger.debug(
|
||||||
|
`Ignoring stale RemoteChange for untracked, non-new document ${remoteVersion.documentId}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return this.processRemoteCreateForNewDocument(remoteVersion);
|
return this.processRemoteCreateForNewDocument(remoteVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -889,13 +903,15 @@ export class Syncer {
|
||||||
contentBytes
|
contentBytes
|
||||||
}: {
|
}: {
|
||||||
record: DocumentRecord;
|
record: DocumentRecord;
|
||||||
relativePath: RelativePath;
|
// `undefined` for content-only edits; the server keeps the doc's
|
||||||
|
// current path. A string is sent only on a user-driven rename.
|
||||||
|
relativePath: RelativePath | undefined;
|
||||||
contentBytes: Uint8Array;
|
contentBytes: Uint8Array;
|
||||||
}): Promise<DocumentUpdateResponse> {
|
}): Promise<DocumentUpdateResponse> {
|
||||||
const isText =
|
const isText =
|
||||||
!isBinary(contentBytes) &&
|
!isBinary(contentBytes) &&
|
||||||
isFileTypeMergable(
|
isFileTypeMergable(
|
||||||
relativePath,
|
relativePath ?? record.remoteRelativePath,
|
||||||
(await this.serverConfig.getConfig()).mergeableFileExtensions
|
(await this.serverConfig.getConfig()).mergeableFileExtensions
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,35 +35,36 @@ export enum SyncEventType {
|
||||||
export type FileSyncEvent =
|
export type FileSyncEvent =
|
||||||
| { type: SyncEventType.LocalCreate; path: RelativePath }
|
| { type: SyncEventType.LocalCreate; path: RelativePath }
|
||||||
| {
|
| {
|
||||||
type: SyncEventType.LocalUpdate;
|
type: SyncEventType.LocalUpdate;
|
||||||
path: RelativePath;
|
path: RelativePath;
|
||||||
oldPath?: RelativePath; // oldPath is undefined for content changes
|
oldPath?: RelativePath; // oldPath is undefined for content changes
|
||||||
}
|
}
|
||||||
| { type: SyncEventType.LocalDelete; path: RelativePath }
|
| { type: SyncEventType.LocalDelete; path: RelativePath }
|
||||||
| {
|
| {
|
||||||
type: SyncEventType.RemoteChange;
|
type: SyncEventType.RemoteChange;
|
||||||
remoteVersion: DocumentVersionWithoutContent;
|
remoteVersion: DocumentVersionWithoutContent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SyncEvent =
|
export type SyncEvent =
|
||||||
| {
|
| {
|
||||||
type: SyncEventType.LocalCreate;
|
type: SyncEventType.LocalCreate;
|
||||||
path: RelativePath; // current path on disk
|
path: RelativePath; // current path on disk
|
||||||
originalPath: RelativePath; // original path on disk when the event was queued
|
originalPath: RelativePath; // original path on disk when the event was queued
|
||||||
resolvers: PromiseWithResolvers<DocumentId>;
|
resolvers: PromiseWithResolvers<DocumentId>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: SyncEventType.LocalUpdate;
|
type: SyncEventType.LocalUpdate;
|
||||||
documentId: DocumentId | Promise<DocumentId>; // if it's a promise, the promise is fulfilled once the document's create event is processed
|
documentId: DocumentId | Promise<DocumentId>; // if it's a promise, the promise is fulfilled once the document's create event is processed
|
||||||
path: RelativePath; // current path on disk
|
path: RelativePath; // current path on disk
|
||||||
originalPath: RelativePath; // original path on disk when the event was queued
|
originalPath: RelativePath; // original path on disk when the event was queued
|
||||||
// no need to store the old path in case of a rename; the server will figure it out from the parent's path
|
isUserRename: boolean; // true iff this event was queued because the user renamed the file
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: SyncEventType.LocalDelete;
|
type: SyncEventType.LocalDelete;
|
||||||
documentId: DocumentId | Promise<DocumentId>; // if it's a promise, the promise is fulfilled once the document's create event is processed
|
documentId: DocumentId | Promise<DocumentId>; // if it's a promise, the promise is fulfilled once the document's create event is processed
|
||||||
}
|
path: RelativePath; // only used for showing on the UI
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: SyncEventType.RemoteChange;
|
type: SyncEventType.RemoteChange;
|
||||||
remoteVersion: DocumentVersionWithoutContent;
|
remoteVersion: DocumentVersionWithoutContent;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue