Files
NetMesh/application/state/pluginImporterTransaction.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

109 lines
4.5 KiB
TypeScript

import { STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION } from '../../infrastructure/config/storageKeys';
type TransactionStorage = {
read<T>(key: string): T | null;
readString(key: string): string | null;
write<T>(key: string, value: T): boolean;
writeString(key: string, value: string): boolean;
remove(key: string): void;
};
type PreviousEntry = { key: string; value: string | null };
type PreparedJournal = {
version: 1;
phase: 'prepared';
previous: PreviousEntry[];
};
type CommittedJournal = { version: 1; phase: 'committed' };
type ImportJournal = PreparedJournal | CommittedJournal;
const restorePrevious = (storage: TransactionStorage, previous: PreviousEntry[]) => {
for (const entry of previous) {
if (entry.value === null) {
storage.remove(entry.key);
if (storage.readString(entry.key) !== null) {
throw new Error(`Vault importer rollback failed for ${entry.key}`);
}
} else if (
!storage.writeString(entry.key, entry.value)
|| storage.readString(entry.key) !== entry.value
) {
throw new Error(`Vault importer rollback failed for ${entry.key}`);
}
}
};
const parseJournal = (value: unknown, allowedKeys: ReadonlySet<string>): ImportJournal | null => {
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
const journal = value as Partial<ImportJournal>;
if (journal.version !== 1 || (journal.phase !== 'prepared' && journal.phase !== 'committed')) return null;
if (journal.phase === 'committed') return { version: 1, phase: 'committed' };
const previous = (journal as Partial<PreparedJournal>).previous;
if (!Array.isArray(previous) || previous.length === 0 || previous.length > allowedKeys.size) return null;
const seen = new Set<string>();
for (const entry of previous) {
if (!entry || typeof entry !== 'object' || Array.isArray(entry)
|| typeof entry.key !== 'string' || !allowedKeys.has(entry.key) || seen.has(entry.key)
|| (entry.value !== null && typeof entry.value !== 'string')) return null;
seen.add(entry.key);
}
return { version: 1, phase: 'prepared', previous: previous as PreviousEntry[] };
};
export function recoverPluginImporterTransaction(
storage: TransactionStorage,
allowedKeys: ReadonlySet<string>,
): 'none' | 'rolled-back' | 'committed' | 'discarded' {
if (storage.readString(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION) === null) return 'none';
const raw = storage.read<unknown>(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
if (raw === null) {
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
return 'discarded';
}
const journal = parseJournal(raw, allowedKeys);
if (!journal) {
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
return 'discarded';
}
if (journal.phase === 'prepared') restorePrevious(storage, journal.previous);
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
return journal.phase === 'prepared' ? 'rolled-back' : 'committed';
}
export function commitPluginImporterTransaction(
storage: TransactionStorage,
writes: ReadonlyArray<readonly [key: string, value: unknown]>,
): void {
if (storage.readString(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION) !== null) {
throw new Error('An unfinished Vault import recovery record already exists');
}
const keys = new Set(writes.map(([key]) => key));
if (keys.size !== writes.length || keys.has(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION)) {
throw new Error('Vault importer transaction keys are invalid');
}
const previous = writes.map(([key]) => ({ key, value: storage.readString(key) }));
const prepared: PreparedJournal = { version: 1, phase: 'prepared', previous };
if (!storage.write(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION, prepared)) {
throw new Error('Vault storage rejected the importer transaction journal');
}
try {
for (const [key, value] of writes) {
const expected = JSON.stringify(value);
if (expected === undefined
|| !storage.write(key, value)
|| storage.readString(key) !== expected) {
throw new Error(`Vault storage rejected importer transaction key ${key}`);
}
}
const committed: CommittedJournal = { version: 1, phase: 'committed' };
if (!storage.write(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION, committed)) {
throw new Error('Vault storage rejected the importer transaction commit marker');
}
} catch (error) {
restorePrevious(storage, previous);
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
throw error;
}
storage.remove(STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION);
}