import { Trie as BaseTrie } from './baseTrie' ; import { ScratchReadStream } from './scratchReadStream' ; import { ScratchDB } from './scratch' ; import { DB } from './db' ; const WriteStream = require ( 'level-ws' ) ; /**
 * Adds checkpointing to the  BaseTrie export class CheckpointTrie extends BaseTrie { _mainDB : DB ; _scratch : ScratchDB | null ; _checkpoints : Buffer [ ] ; constructor ( ... args ) { super ( ... args ) ; this . _mainDB = this . db ; this . _scratch = null ; this . _checkpoints = [ ] ; } /**
     * Is the trie during a checkpoint phase?
     */ get isCheckpoint ( ) { return this . _checkpoints . length > 0 ; } /**
     * Creates a checkpoint that can later be reverted to or committed.
     * After this is called, no changes to the trie will be permanently saved until `commit` is called.
     * To override the checkpointing mechanism use `_maindb.put` to write directly write to db.
     */ checkpoint ( ) { const wasCheckpoint = this . isCheckpoint ; this . _checkpoints . push ( this . root ) ; if ( ! wasCheckpoint && this . isCheckpoint ) { this . _enterCpMode ( ) ; } } throws async commit ( ) { if ( ! this . isCheckpoint ) { throw new Error ( 'trying to commit when not checkpointed' ) ; } await this . lock . wait ( ) ; this . _checkpoints . pop ( ) ; if ( ! this . isCheckpoint ) { await this . _exitCpMode ( true ) ; } this . lock . signal ( ) ; } /**
     * Reverts the trie to the state it was at when `checkpoint` was first called.
     * If during a nested checkpoint, sets root to most recent checkpoint, and sets
     * parent checkpoint as current.
     */ async revert ( ) { await this . lock . wait ( ) ; if ( this . isCheckpoint ) { this . root = this . _checkpoints . pop ( ) ! ; if ( ! this . isCheckpoint ) { await this . _exitCpMode ( false ) ; } } this . lock . signal ( ) ; } param includeCheckpoints copy ( includeCheckpoints = true ) { const db = this . _mainDB . copy ( ) ; const trie = new CheckpointTrie ( db . _leveldb , this . root ) ; if ( includeCheckpoints && this . isCheckpoint ) { trie . _checkpoints = this . _checkpoints . slice ( ) ; trie . _scratch = this . _scratch ! . copy ( ) ; trie . db = trie . _scratch ; } return trie ; } private _enterCpMode ( ) { this . _scratch = new ScratchDB ( this . _mainDB ) ; this . db = this . _scratch ; } private async _exitCpMode ( commitState ) { return new Promise ( ( resolve ) => { const scratch = this . _scratch as ScratchDB ; this . _scratch = null ; this . db = this . _mainDB ; if ( commitState ) { this . _createScratchReadStream ( scratch ) . pipe ( WriteStream ( this . db . _leveldb ) ) . on ( 'close' , resolve ) ; } else { process . nextTick ( resolve ) ; } } ) ; } private _createScratchReadStream ( scratchDb ? ) { const scratch = scratchDb || this . _scratch ; if ( ! scratch ) { throw new Error ( 'No scratch found to use' ) ; } const trie = new BaseTrie ( scratch . _leveldb , this . root ) ; trie . db = scratch ; return new ScratchReadStream ( trie ) ; } }