.
This commit is contained in:
parent
4c002f9acd
commit
b07aa589ff
1 changed files with 728 additions and 530 deletions
|
|
@ -1,38 +1,29 @@
|
|||
import type { CursorPosition } from "reconcile-text";
|
||||
import { upgradeState } from "./upgrade-state";
|
||||
import type { StoredSnapshot } from "../snapshot";
|
||||
import type { DocumentUpdateResponse } from "../services/types/DocumentUpdateResponse";
|
||||
import type { DocumentVersionWithoutContent } from "../services/types/DocumentVersionWithoutContent";
|
||||
import type { FileManifest } from "../services/types/FileManifest";
|
||||
import type { FileManifestUpdateResponse } from "../services/types/FileManifestUpdateResponse";
|
||||
import type { PushFileManifest } from "../services/types/PushFileManifest";
|
||||
import type { PutFileContent } from "../services/types/PutFileContent";
|
||||
import type { VaultSnapshot } from "../services/types/VaultSnapshot";
|
||||
import type { FileManifestEntries } from "../types/file-manifest-entries";
|
||||
|
||||
export type VaultUpdateId = number;
|
||||
export type DocumentId = string;
|
||||
export type RelativePath = string;
|
||||
|
||||
export interface StoredSnapshot {
|
||||
contentBase64: string;
|
||||
hash: string;
|
||||
cursors?: CursorPosition[];
|
||||
}
|
||||
|
||||
export interface DocumentMetadata {
|
||||
parentVersionId: number;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export interface DocumentState {
|
||||
/** Local clean base retained while rebasing after a server restore. */
|
||||
recoveryBase?: StoredSnapshot;
|
||||
base?: DocumentVersionWithoutContent & { hash: string };
|
||||
/** A null version retains only the clean hash after a server history reset. */
|
||||
base?: { vaultUpdateId: number | null; hash: string };
|
||||
/** Last observed disk hash; an empty string means a notification awaits scanning. */
|
||||
observedHash?: string;
|
||||
materialized: boolean;
|
||||
/** First contact with existing local content: there is no common ancestor. */
|
||||
bootstrap?: boolean;
|
||||
/** Do not resubmit an unchanged payload rejected by the server. */
|
||||
rejected?: { hash: string; message: string };
|
||||
/** A remote version still to be incorporated, including excluded downloads. */
|
||||
remote?: DocumentVersionWithoutContent;
|
||||
}
|
||||
|
||||
export type PendingRequest = { rejection?: string } & (
|
||||
|
|
@ -51,26 +42,19 @@ export type PendingRequest = { rejection?: string } & (
|
|||
);
|
||||
|
||||
export interface EngineState {
|
||||
/** Persisted reset intent; clear the transport checkpoint before bootstrap. */
|
||||
historyRecovery?: boolean;
|
||||
vaultKey: string;
|
||||
initialized: boolean;
|
||||
fileManifest: FileManifest;
|
||||
local: FileManifestEntries;
|
||||
documents: Record<string, DocumentState>;
|
||||
lastSeenUpdateId: number;
|
||||
remoteHeads: Record<string, DocumentVersionWithoutContent>;
|
||||
bootstrap?: VaultSnapshot;
|
||||
/** Absent until a vault snapshot is applied; also marks a durable history reset. */
|
||||
lastSeenUpdateId?: number;
|
||||
pending?: PendingRequest;
|
||||
/** A rejected retry does not prove an earlier attempt failed to commit. */
|
||||
unconfirmed?: PendingRequest[];
|
||||
/** Commits consumption of the saved notification queue with the identity map. */
|
||||
lastAppliedLocalChangeId?: string;
|
||||
/** Physical exclusions, including files with no sync identity. */
|
||||
protectedPaths?: string[];
|
||||
/** Original server paths while local files are excluded; null means absent. */
|
||||
excluded?: Record<string, string | null>;
|
||||
rejectedManifest?: { entries: FileManifestEntries; message: string };
|
||||
}
|
||||
|
||||
export type StoredDatabase = EngineState;
|
||||
|
|
@ -85,21 +69,17 @@ export interface DocumentRecord {
|
|||
export function emptyState(vaultKey: string): StoredDatabase {
|
||||
return {
|
||||
vaultKey,
|
||||
initialized: false,
|
||||
fileManifest: { fileManifestId: 0, entries: {} },
|
||||
local: {},
|
||||
documents: {},
|
||||
remoteHeads: {},
|
||||
lastSeenUpdateId: 0
|
||||
documents: {}
|
||||
};
|
||||
}
|
||||
|
||||
function canRebindEmpty(state: Partial<StoredDatabase> | undefined): boolean {
|
||||
return (
|
||||
state !== undefined &&
|
||||
!state.initialized &&
|
||||
state.lastSeenUpdateId === undefined &&
|
||||
!state.pending &&
|
||||
!state.bootstrap &&
|
||||
(state.unconfirmed?.length ?? 0) === 0 &&
|
||||
Object.keys(state.local ?? {}).length === 0
|
||||
);
|
||||
|
|
@ -114,19 +94,16 @@ export class Database {
|
|||
vaultKey: string,
|
||||
private readonly loadData: () => Promise<StoredDatabase | undefined>
|
||||
) {
|
||||
const hasState = initial && Object.keys(initial).length > 0;
|
||||
if (
|
||||
hasState &&
|
||||
initial.vaultKey !== vaultKey &&
|
||||
!canRebindEmpty(initial)
|
||||
) {
|
||||
const state =
|
||||
initial !== undefined && Object.keys(initial).length > 0
|
||||
? upgradeState(initial as StoredDatabase)
|
||||
: emptyState(vaultKey);
|
||||
if (state.vaultKey !== vaultKey && !canRebindEmpty(state)) {
|
||||
throw new Error(
|
||||
"Incompatible vault identity. Preserve it for recovery and initialize a separate state store."
|
||||
);
|
||||
}
|
||||
this.state = hasState
|
||||
? { ...(initial as StoredDatabase), vaultKey }
|
||||
: emptyState(vaultKey);
|
||||
this.state = { ...state, vaultKey };
|
||||
}
|
||||
|
||||
/** Bind an empty database to the configured vault. */
|
||||
|
|
@ -154,7 +131,7 @@ export class Database {
|
|||
public async recoverPersistence(): Promise<void> {
|
||||
if (!this.needsReload) return;
|
||||
const saved = await this.loadData();
|
||||
if (saved) this.state = saved;
|
||||
if (saved) this.state = upgradeState(saved);
|
||||
this.needsReload = false;
|
||||
}
|
||||
|
||||
|
|
@ -173,10 +150,13 @@ export class Database {
|
|||
return {
|
||||
documentId: id,
|
||||
relativePath,
|
||||
metadata: base && {
|
||||
parentVersionId: base.vaultUpdateId,
|
||||
hash: base.hash
|
||||
}
|
||||
metadata:
|
||||
base?.vaultUpdateId != null
|
||||
? {
|
||||
parentVersionId: base.vaultUpdateId,
|
||||
hash: base.hash
|
||||
}
|
||||
: undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue