import * as _ from 'lodash' ; import * as mongodb from 'mongodb' ; import { CollectionWithPendingLocks } from './dbWithPendingLocks' ; export type Query < T > = mongodb . FilterQuery < T > ; export interface Context { reqType : 'vote' | 'commit' | 'abort' | undefined ; runId : string ; reqId : string ; } /**
 * The empty Context to use when it isn't applicable,
 * e.g. when populating a Collection with initial objects on initialization.
 */ export const EMPTY_CONTEXT = { reqType : undefined , runId : '' , reqId : '' } ; export function isSuccessfulContext ( context ) { return context . reqType === undefined || context . reqType === 'commit' ; } export class ConceptDbError extends Error { public readonly errorCode : number ; constructor ( message , errorCode ) { super ( message ) ; Object . setPrototypeOf ( this , new . target . prototype ) ; this . name = this . constructor . name ; Error . captureStackTrace ( this , this . constructor ) ; this . errorCode = errorCode ; } } export class ConceptDbNotFoundError extends ConceptDbError { public static readonly ERROR_CODE = 404 ; constructor ( collectionName ) { const entityName = collectionName . substr ( 0 , collectionName . length - 1 ) ; super ( `${ _ . capitalize ( entityName ) } not found` , ConceptDbNotFoundError . ERROR_CODE ) ; } } export class ConceptDbConcurrentUpdateError extends ConceptDbError { public static readonly ERROR_CODE = 500 ; constructor ( ) { super ( 'An error has occurred. Please try again later.' , ConceptDbConcurrentUpdateError . ERROR_CODE ) ; } } export class ConceptDbUnknownUpdateError extends ConceptDbError { public static readonly ERROR_CODE = 500 ; constructor ( ) { super ( 'An unknown error has occurred. Please contact an administrator.' , ConceptDbUnknownUpdateError . ERROR_CODE ) ; } } export class ConceptDbDuplicateKeyError extends ConceptDbError { public static readonly ERROR_CODE = 400 ; constructor ( ) { super ( 'Write violates the collection\'s uniqueness constraints' , ConceptDbDuplicateKeyError . ERROR_CODE ) ; } } export interface UpsertOptions { upsert ? : boolean ; setOnInsert ? : Object ; } /**
 * Wrapper around the MongoDb Node.js driver
 * to add support for Déjà Vu's transaction handling,
 * exposing only the relevant methods for DV concept servers.
 *
 * Each of the methods exposed are individually guaranteed to be atomic.
 * To perform operations across Collection<T>s in a transaction,
 * use ConceptDb.inTransaction()
 *
 * http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html
 */ export interface Collection < T extends Object > { /** http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#aggregate */ aggregate ( pipeline , options ? ) : mongodb . AggregationCursor < T > ; /** http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#createIndex */ createIndex ( fieldOrSpec , options ? ) : Promise < string > ; /** http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#countDocuments */ countDocuments ( query ? , options ? ) : Promise < number > ; throws deleteMany ( context , filter , options ? ) : Promise < boolean > ; throws deleteOne ( context , filter ) : Promise < boolean > ; /** http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#find */ find ( query ? ) : Promise < T [ ] > ; /** http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#find */ findCursor ( query ? ) : Promise < mongodb . Cursor < T > > ; throws findOne ( query , options ? ) : Promise < T > ; param updateFn param options param validationFn return throws findOneAndUpdateWithFn ( context , filter , updateFn , options ? , validationFn ? ) : Promise < T | undefined > ; throws insertMany ( context , docs , options ? ) : Promise < T [ ] > ; throws insertOne ( context , doc , options ? ) : Promise < T > ; throws updateMany ( context , filter , update , options ? ) : Promise < boolean > ; throws updateOne ( context , filter , update , options ? ) : Promise < boolean > ; } export class ConceptDb { private readonly _client : mongodb . MongoClient ; private readonly _db : mongodb . Db ; private readonly _collections : Map < string , Collection < any > > = new Map < string , Collection < any > > ( ) ; constructor ( client , db ) { this . _client = client ; this . _db = db ; } /**
     * Get the collection with the given name from the database.
     */ collection < T > ( name ) { if ( ! this . _collections . has ( name ) ) { this . _collections . set ( name , new CollectionWithPendingLocks < T > ( this . _db , name ) ) ; } return this . _collections . get ( name ) ; } param fn return async inTransaction ( fn ) { let returnVal = undefined ; this . _client . withSession ( async ( session ) => { session . startTransaction ( ) ; try { returnVal = await fn ( ) ; await session . commitTransaction ( ) ; return ; } catch ( err ) { await session . abortTransaction ( ) ; throw err ; } } ) ; return returnVal ; } }