import { ICollection } from "@fluidframework/server-services-core" ; import * as charwise from "charwise" ; import * as _ from "lodash" ; export interface ICollectionProperty { indexes : string [ ] ; limit ? : number ; } /**
 * Helper function to read a sublevel stream and return a promise for an array of the results.
 */ async function readStream < T > ( stream ) { const entries = [ ] ; return new Promise < T [ ] > ( ( resolve , reject ) => { stream . on ( "data" , ( data ) => { entries . push ( data ) ; } ) ; stream . on ( "end" , ( ) => { resolve ( entries ) ; } ) ; stream . on ( "error" , ( error ) => { reject ( error ) ; } ) ; } ) ; } export class Collection < T > implements ICollection < T > { constructor ( private readonly db , private readonly property ) { } public aggregate ( group , options ? ) { throw new Error ( "Method Not Implemented" ) ; } public async updateMany ( filter , set , addToSet ) { throw new Error ( "Method Not Implemented" ) ; } public async distinct ( key , query ) { throw new Error ( "Method Not Implemented" ) ; } public async find ( query , sort ? ) { return this . findInternal ( query , sort ) ; } public async findAll ( ) { return readStream ( this . db . createValueStream ( ) ) ; } public findOne ( query ) { return this . findOneInternal ( query ) ; } public async update ( filter , set , addToSet ) { const value = await this . findOneInternal ( filter ) ; if ( ! value ) { return Promise . reject ( new Error ( "Not found" ) ) ; } else { _ . extend ( value , set ) ; return this . insertOne ( value ) ; } } public async upsert ( filter , set , addToSet ) { const value = await this . findOneInternal ( filter ) ; if ( ! value ) { return this . insertOne ( set ) ; } else { _ . extend ( value , set ) ; return this . insertOne ( value ) ; } } public async insertOne ( value ) { return this . insertOneInternal ( value ) ; } public async findOrCreate ( query , value ) { const existing = await this . findOneInternal ( query ) ; if ( existing ) { return { value : existing , existing : true } ; } else { const item = await this . insertOneInternal ( value ) ; return { value : item , existing : false } ; } } public async insertMany ( values , ordered ) { const batchValues = [ ] ; values . forEach ( ( value ) => { batchValues . push ( { type : "put" , key : this . getKey ( value ) , value , } ) ; } ) ; return this . db . batch ( batchValues ) ; } public async deleteOne ( filter ) { return this . db . del ( this . getKey ( filter ) ) ; } public async deleteMany ( filter ) { throw new Error ( "Method not implemented." ) ; } public async createIndex ( index , unique ) { return ; } private async insertOneInternal ( value ) { await new Promise < void > ( ( resolve , reject ) => { this . db . put ( this . getKey ( value ) , value , ( error ) => { if ( error ) { reject ( error ) ; } else { resolve ( ) ; } } ) ; } ) ; return value ; } private async findOneInternal ( query ) { const values = await this . findInternal ( query ) ; return values . length > 0 ? values [ 0 ] : null ; } private getKey ( value ) { function getValueByKey ( propertyBag , key ) { const keys = key . split ( "." ) ; let v = propertyBag ; keys . forEach ( ( splitKey ) => { v = v [ splitKey ] ; } ) ; return v ; } const values = [ ] ; this . property . indexes . forEach ( ( key ) => { const innerValue = getValueByKey ( value , key ) ; values . push ( isNaN ( innerValue ) ? innerValue : charwise . encode ( Number ( innerValue ) ) ) ; } ) ; return values . join ( "!" ) ; } private async findInternal ( query , sort ? ) { const isRange = this . property . limit !== undefined ; const indexes = this . property . indexes ; const indexLen = isRange ? indexes . length - 1 : indexes . length ; const queryValues = [ ] ; for ( let i = 0 ; i < indexLen ; ++ i ) { const queryValue = query [ indexes [ i ] ] ; if ( queryValue !== undefined ) { queryValues . push ( isNaN ( queryValue ) ? queryValue : charwise . encode ( Number ( queryValue ) ) ) ; } } const key = queryValues . join ( "!" ) ; if ( isRange ) { const rangeKey = indexes [ indexes . length - 1 ] ; const from = query [ rangeKey ] && query [ rangeKey ] . $gt > 0 ? Number ( query [ rangeKey ] . $gt ) + 1 : 1 ; const to = query [ rangeKey ] && query [ rangeKey ] . $lt > 0 ? Number ( query [ rangeKey ] . $lt ) - 1 : from + this . property . limit - 1 ; const gte = `${ key }!${ charwise . encode ( Number ( from ) ) }` ; const lte = `${ key }!${ charwise . encode ( Number ( to ) ) }` ; const valueStream = this . db . createValueStream ( { gte , lte , limit : this . property . limit , } ) ; return readStream ( valueStream ) ; } else { return new Promise < T [ ] > ( ( resolve , reject ) => { this . db . get ( key , ( err , val ) => { if ( err ) { if ( err . notFound ) { resolve ( [ ] ) ; } else { reject ( err ) ; } } else { resolve ( [ val ] ) ; } } ) ; } ) ; } } }