# All files document.ts

93.54% Statements 116/124
77.77% Branches 21/27
88.88% Functions 8/9
93.54% Lines 116/124

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    1251x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    335x
    335x
    335x
    335x
    4x
    4x
    335x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    4x
    4x
    4x
    4x
    4x
    4x
    1x
    1x
    4x
    4x
    4x
    4x
    4x

    4x
    4x
    1x
    1x
    240x
    3x
    3x
    237x
    237x
    237x
    1x
    1x
    240x
    240x
    237x
    237x
    237x
    237x
    236x
    236x
    236x
    236x
    236x
    237x
    1x
    1x
    1x
    1x
    1x

    240x
    3x
    3x
    3x
    3x
    3x
    3x

    3x
    3x
    240x
    1x
    1x
    1x
    240x
    1x
    1x

    1x
    1x
    316x
    316x
    316x
    316x
    2208x
    1818x
    1818x
    316x
    316x
    316x
    1x
    1x
    76x
    76x
    76x
    76x
    76x
    76x
    1x
    1x
    240x
    240x
    240x
    1x
     import { InsertOneOptions, DeleteResult, ObjectId, UpdateOptions } from 'mongodb'

    import { getDbInstance } from './database'
    import { DocumentException } from './documentException'
    import { IDocument } from './interfaces'
    import { ISerializable, SerializationStrategy, Serialize } from './serializer'

    const defaultExcludes = ['collectionName', 'includes', 'excludes']

    export abstract class Document<TDocument extends IDocument>
      implements IDocument, ISerializable
    {
      // tslint:disable-next-line: semicolon
      public '_id': ObjectId;
      [index: string]: unknown

      constructor(
        public collectionName: string,
        document?: Partial<TDocument>
      ) {
        if (document) {
          this.fillData(document)
        }
      }

      protected abstract getCalculatedPropertiesToInclude(): string[]

      protected abstract getPropertiesToExclude(): string[]

      protected abstract fillData(data?: Partial<TDocument>): void

      protected hydrateInterface<TInterface extends TObject, TObject extends object>(
        objectType: new () => TObject,
        hydrator: (data: Partial<TInterface>) => TObject,
        element: Partial<TInterface>
      ) {
        return element instanceof objectType ? element : hydrator(element)
      }

      protected hydrateInterfaceArray<TInterface extends TObject, TObject extends object>(
        objectType: new () => TObject,
        hydrator: (data: Partial<TInterface>) => TObject,
        objectArray: Partial<TInterface>[]
      ): TObject[] | undefined {
        if (!objectArray || objectArray.length === 0) {
          return undefined
        }
        return objectArray.map((e) => this.hydrateInterface(objectType, hydrator, e))
      }

      private hasObjectId(): boolean {
        if (this._id && this._id.getTimestamp()) {
          return ObjectId.isValid(this._id)
        }

        return false
      }

      async save(options?: InsertOneOptions | UpdateOptions): Promise<boolean> {
        try {
          if (!this.hasObjectId()) {
            try {
              const result = await getDbInstance()
                .collection(this.collectionName)
                .insertOne(this, options)

              if (result.acknowledged) {
                this._id = result.insertedId
              }
              return result.acknowledged
            } catch (ex: unknown) {
              console.error(ex instanceof Error ? ex.message : ex)

              if (ex instanceof Error || typeof ex === 'string') {
                throw new DocumentException(ex)
              }
              return false
            }
          } else {
            const result = await getDbInstance()
              .collection(this.collectionName)
              .updateOne({ _id: this._id }, { $set: this }, options)
            return (
              result.modifiedCount === 1 ||
              result.matchedCount === 1 ||
              result.upsertedCount === 1
            )
          }
        } catch (ex) {
          console.error(ex)
          return false
        }
      }

      delete(): Promise<DeleteResult> {
        const collection = getDbInstance().collection(this.collectionName)
        return collection.deleteOne({ _id: this._id })
      }

      private fieldsToSerialize(excludes: string[] = [], includes: string[] = []) {
        excludes = defaultExcludes.concat(excludes)

        const keys: string[] = []
        Object.keys(this).forEach((item) => {
          if (!(excludes.indexOf(item) > -1)) {
            keys.push(item)
          }
        })
        return keys.concat(includes)
      }

      toJSON(): object {
        const fields = this.fieldsToSerialize(
          this.getPropertiesToExclude(),
          this.getCalculatedPropertiesToInclude()
        )
        return Serialize(SerializationStrategy.JSON, this, fields)
      }

      toBSON(): object {
        const fields = this.fieldsToSerialize(this.getCalculatedPropertiesToInclude())
        return Serialize(SerializationStrategy.BSON, this, fields)
      }
    }