import assert from "assert" ; import { Storage , StorageListOptions , StorageListResult , StoredKey , StoredValue , addAll , } from "@miniflare/shared" ; import { listFilterMatch } from "@miniflare/storage-memory" ; const collator = new Intl . Collator ( ) ; export class ShadowStorage extends Storage { readonly readSet ? : Set < string > ; readonly copies = new Map < string , StoredValue | undefined > ( ) ; constructor ( protected readonly inner , recordReads = true ) { super ( ) ; if ( recordReads ) this . readSet = new Set < string > ( ) ; } async has ( key ) { return ( await this . hasMany ( [ key ] ) ) > 0 ; } async hasMany ( keys ) { if ( this . readSet ) addAll ( this . readSet , keys ) ; if ( this . copies . size === 0 ) return this . inner . hasMany ( keys ) ; let count = 0 ; const innerHasKeys = [ ] ; for ( const key of keys ) { if ( this . copies . has ( key ) ) { if ( this . copies . get ( key ) !== undefined ) count ++ ; } else { innerHasKeys . push ( key ) ; } } count += await this . inner . hasMany ( innerHasKeys ) ; return count ; } async get ( key ) { return ( await this . getMany ( [ key ] ) ) [ 0 ] ; } async getMany ( keys ) { if ( this . readSet ) addAll ( this . readSet , keys ) ; if ( this . copies . size === 0 ) return this . inner . getMany ( keys , true ) ; const result = new Array < StoredValue | undefined > ( keys . length ) ; const innerGetKeys = [ ] ; const innerGetIndices = [ ] ; for ( let i = 0 ; i < keys . length ; i ++ ) { const key = keys [ i ] ; if ( this . copies . has ( key ) ) { const copy = this . copies . get ( key ) ; result [ i ] = copy && { value : copy . value . slice ( ) } ; } else { innerGetKeys . push ( key ) ; innerGetIndices . push ( i ) ; } } assert . strictEqual ( innerGetKeys . length , innerGetIndices . length ) ; if ( innerGetKeys . length === 0 ) return result ; const innerGetResult = await this . inner . getMany ( innerGetKeys , true ) ; assert . strictEqual ( innerGetKeys . length , innerGetResult . length ) ; for ( let i = 0 ; i < innerGetKeys . length ; i ++ ) { result [ innerGetIndices [ i ] ] = innerGetResult [ i ] ; } return result ; } put ( key , value ) { this . copies . set ( key , { value : value . value . slice ( ) } ) ; } async delete ( key ) { return ( await this . deleteMany ( [ key ] ) ) > 0 ; } async deleteMany ( keys ) { const deleted = await this . hasMany ( keys ) ; for ( const key of keys ) this . copies . set ( key , undefined ) ; return deleted ; } async list ( options ? ) { if ( this . copies . size === 0 ) { const { keys } = await this . inner . list ( options , true as any ) ; if ( this . readSet ) { addAll ( this . readSet , keys . map ( ( { name } ) => name ) ) ; } return { keys , cursor : "" } ; } const matchingCopies = new Map < string , StoredValue | undefined > ( ) ; let deletedMatchingCopies = 0 ; for ( const [ key , value ] of this . copies . entries ( ) ) { if ( listFilterMatch ( options , key ) ) { matchingCopies . set ( key , value ) ; if ( value === undefined ) deletedMatchingCopies ++ ; } } let { keys } = await this . inner . list ( { ... options , limit : options ?. limit && options . limit + deletedMatchingCopies , } , true ) ; keys = keys . filter ( ( stored ) => { if ( matchingCopies . has ( stored . name ) ) { const matching = matchingCopies . get ( stored . name ) ; matchingCopies . delete ( stored . name ) ; if ( matching === undefined ) return false ; } return true ; } ) ; for ( const [ key , value ] of matchingCopies . entries ( ) ) { if ( value ) keys . push ( { name : key } ) ; } const direction = options ?. reverse ? - 1 : 1 ; keys . sort ( ( a , b ) => direction * collator . compare ( a . name , b . name ) ) ; if ( options ?. limit ) keys = keys . slice ( 0 , options . limit ) ; if ( this . readSet ) { addAll ( this . readSet , keys . map ( ( { name } ) => name ) ) ; } return { keys , cursor : "" } ; } }