-
-
Notifications
You must be signed in to change notification settings - Fork 0
fix(undo): hot-exit + Revert reconstruct exact undo/redo state (closes #425, 1.5.1) #434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
6bfc012
feat(undo): persist futureStack across hot-exit serialization
zknpr 67ee127
feat(undo): restore reconciler brings a hot-exit-restored DB to the l…
zknpr 44eb7bd
feat(undo): reconcile restored database state on hot-exit open
zknpr 7341f48
chore(release): 1.5.1 — json_patch undo fidelity + hot-exit futureSta…
zknpr 6c05167
fix(undo): clamp getEntriesUndoneSinceCheckpoint + document branch-ca…
zknpr b1b5ecb
feat(undo): branch-aware checkpoint — persist abandoned saved edits (…
zknpr 9df2bdc
feat(undo): atomic branch-aware restore + revert helpers
zknpr 5098f84
feat(undo): revert() restores the saved state via revertDatabaseToSaved
zknpr dcd45b4
docs(changelog): fold branch-case + revert hot-exit into 1.5.1 (close…
zknpr c641c5d
fix(undo): drop incompatible restore/revert SAVEPOINT; native redo vi…
zknpr 8a35473
fix(undo): don't invalidate in-flight save checkpoints on branch edits
zknpr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import type { DatabaseOperations, LabeledModification } from './types'; | ||
| import type { ModificationTracker } from './undo-history'; | ||
|
|
||
| /** | ||
| * Bring a freshly opened database to the live timeline state after hot-exit restore. | ||
| * | ||
| * WASM restore opens the database bytes from the saved checkpoint, so the database | ||
| * must either replay entries that were recorded after that checkpoint or revert | ||
| * entries that were saved and then undone before shutdown. Native SQLite writes | ||
| * edits and undos directly to the on-disk file, so the opened database is already | ||
| * at the live state and only the tracker needs to retain redo state. | ||
| * | ||
| * @param databaseOps - Database operation facade for the restored database | ||
| * @param tracker - Deserialized hot-exit modification tracker | ||
| * @param engineKind - Active database engine type | ||
| * @param signal - Optional cancellation signal checked between revert/replay steps | ||
| */ | ||
| export async function reconcileRestoredDatabase( | ||
| databaseOps: DatabaseOperations, | ||
| tracker: ModificationTracker<LabeledModification>, | ||
| engineKind: 'wasm' | 'native', | ||
| signal?: AbortSignal | ||
| ): Promise<void> { | ||
| if (engineKind === 'native') { | ||
| return; | ||
| } | ||
|
|
||
| const revertSeq = tracker.getCheckpointRevertSequence(); | ||
| const forward = tracker.getUncommittedEntries(); | ||
| if (revertSeq.length === 0 && forward.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| // Revert saved-then-undone entries first to move the restored bytes back to | ||
| // the common-prefix state, then replay the live entries from that boundary. | ||
| // | ||
| // Each engine operation is individually atomic (it opens its own | ||
| // transaction). We deliberately do NOT wrap the sequence in an outer | ||
| // SAVEPOINT: row/column undos (`undoRowDelete` -> `insertRowBatch`, | ||
| // `undoColumnDrop`) issue their own `BEGIN TRANSACTION`, which SQLite rejects | ||
| // while an outer transaction is open — wrapping them would break restoring | ||
| // deleted rows/columns entirely. A mid-sequence failure instead propagates to | ||
| // the caller (`DatabaseDocument.create`), which opens the document read-only | ||
| // and re-restores from the unchanged backup on the next open. | ||
| for (const entry of revertSeq) { | ||
| signal?.throwIfAborted(); | ||
| await databaseOps.undoModification(entry); | ||
| } | ||
| if (forward.length > 0) { | ||
| await databaseOps.applyModifications(forward, signal); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Bring the live database and tracker back to the last saved checkpoint state. | ||
| * | ||
| * Forward entries are undone from the live timeline, then saved-undone entries | ||
| * are re-applied in original application order. The tracker is rolled back only | ||
| * after the database mutations succeed so a failed revert does not desynchronize | ||
| * the in-memory history from the live database. | ||
| * | ||
| * @param databaseOps - Database operation facade for the open database | ||
| * @param tracker - Modification tracker for the open document | ||
| * @param signal - Optional cancellation signal used by replay/discard helpers | ||
| */ | ||
| export async function revertDatabaseToSaved( | ||
| databaseOps: DatabaseOperations, | ||
| tracker: ModificationTracker<LabeledModification>, | ||
| signal?: AbortSignal | ||
| ): Promise<void> { | ||
| const forward = tracker.getUncommittedEntries(); | ||
| const redo = [...tracker.getCheckpointRevertSequence()].reverse(); | ||
|
|
||
| if (forward.length === 0 && redo.length === 0) { | ||
| tracker.rollbackToCheckpoint(); | ||
| return; | ||
| } | ||
|
|
||
| // Discard live edits first, then re-apply the saved entries the user had | ||
| // undone so the final database matches the checkpoint. | ||
| // | ||
| // Re-apply via `redoModification`, NOT `applyModifications`: the native engine | ||
| // implements replay in `redoModification` and treats `applyModifications` as a | ||
| // no-op (`src/nativeWorker.ts`), so using `applyModifications` here would | ||
| // silently leave a native database at the undone state while the tracker is | ||
| // marked clean. As in restore, the sequence is per-operation atomic rather | ||
| // than wrapped in a SAVEPOINT (row/column undos open their own transaction). | ||
| if (forward.length > 0) { | ||
| await databaseOps.discardModifications(forward, signal); | ||
| } | ||
| for (const entry of redo) { | ||
| signal?.throwIfAborted(); | ||
| await databaseOps.redoModification(entry); | ||
| } | ||
|
|
||
| // Roll the tracker back to the checkpoint only after the database mutations | ||
| // succeed, so a failed revert does not desynchronize history from the data. | ||
| tracker.rollbackToCheckpoint(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.