Home Reference Source Test

src/event/Event.ts

import Long from 'long'
import uuid = require('uuid/v4')
import {uuidFromBuffer, uuidToBuffer} from '../protobuf/uuidBufferConvert'
import * as eventstoreError from '../errors'
import * as model from '../protobuf/model'
import {JSONValue} from '../JSON'

/** protobuf shorthand */
const protobuf = model.eventstore.proto

/**
 * Represents a single event
 *
 * @export
 * @class Event
 */
export class Event {
  /** streamId */
  public streamId: string | null = null
  /** event number in stream */
  public eventNumber: Long | number | null = null
  /** unique event uuid4 */
  protected eventId: string = uuid()
  /** event type = name of event */
  protected eventType: string
  /** flag if data is type of json */
  protected dataContentType: number = 1
  /** flag if metadata is type of json */
  protected metadataContentType: number = 1
  /** raw buffer representation of data */
  public rawData: Uint8Array | null = null
  /** raw buffer representation of metadata */
  public rawMetadata: Uint8Array | null = null
  /** js object  representation of data */
  protected objectData: {[k: string]: JSONValue} | null = null
  /** js object  representation of metadata */
  protected objectMetadata:
    | {
        $correlationId?: string
      } & {[k: string]: JSONValue}
    | null = null
  /** creation date as timestamp */
  protected objectCreated: number | Long | null = null
  /** creation date as timestamp */
  protected objectCreatedEpoch: number | Long | null = null
  /** events correlation uuid4 */
  protected objectCorrelationId: string | null = null

  /** indicates if event was written to eventstore */
  protected frozen: boolean = false
  /** indicates if its an event or a link which must be resolved */
  protected isResolved = true

  /**
   * Creates an instance of Event.
   */
  public constructor(eventType: string, data?: {}, metadata?: {}) {
    this.eventType = eventType
    this.objectData = data ? data : null
    this.objectMetadata = metadata ? metadata : null
  }

  /**
   * Freezes event instance.
   * Done when this event is already stored at eventstore.
   */
  public freeze(): void {
    this.frozen = true
    Object.freeze(this.objectData)
    Object.freeze(this.objectMetadata)
  }

  /**
   * Returns true if event is not stored at eventstore and false if event was written to eventstore
   */
  public isNew(): boolean {
    return !this.frozen
  }

  /**
   * Helper function to throw while changing an event which is already stored in eventstore
   */
  protected throwIfNotNewEvent(fieldName: string): void {
    if (this.frozen) {
      throw eventstoreError.newOperationError(
        `Changing of ${fieldName} is not allowed for stored events`
      )
    }
  }

  /**
   * Setter for event id
   */
  public set id(newId: string) {
    this.throwIfNotNewEvent('eventId')
    this.eventId = newId
  }

  /**
   * Getter for event id
   */
  public get id(): string {
    return this.eventId
  }

  /**
   * Returns true if event is a link and not a full resolved event
   */
  public isLink(): boolean {
    return !this.isResolved
  }

  /**
   * Getter for event data
   */
  public get data(): {[k: string]: JSONValue} | string {
    if (this.objectData) {
      return this.objectData
    }
    if (this.rawData && !this.isLink()) {
      this.objectData = JSON.parse(Buffer.from(this.rawData).toString())
    } else if (this.rawData && this.isLink()) {
      return Buffer.from(this.rawData).toString()
    }
    return this.objectData || {}
  }

  /**
   * Sets data
   */
  public set data(newData: {[k: string]: JSONValue} | string) {
    this.throwIfNotNewEvent('eventData')
    //add as new object to prevent unwanted changes
    this.objectData = typeof newData === 'string' ? JSON.parse(newData) : {...newData}
  }

  /**
   * Getter for event metadata
   */
  public get metadata(): {$correlationId?: string} & {[k: string]: JSONValue} | null | string {
    if (this.objectMetadata) {
      return this.objectMetadata
    }
    if (this.rawMetadata) {
      this.objectMetadata = JSON.parse(Buffer.from(this.rawMetadata).toString())
    }
    return this.objectMetadata
  }

  /**
   * Setter for event metadata
   */
  public set metadata(
    newMetadata: {$correlationId?: string} & {[k: string]: JSONValue} | null | string
  ) {
    this.throwIfNotNewEvent('eventMetadata')
    //add as new object to prevent unwanted changes
    const $correlationId = this.objectCorrelationId ? this.objectCorrelationId : undefined
    this.objectMetadata =
      typeof newMetadata === 'string' ? { ...JSON.parse(newMetadata), $correlationId} : {...newMetadata, $correlationId}
  }

  /**
   * Setter for event correlationId
   */
  public set correlationId(newCorrelationId: string | null) {
    this.throwIfNotNewEvent('correlationId')
    if (!newCorrelationId) {
      this.objectCorrelationId = newCorrelationId
      if (this.objectMetadata && this.objectMetadata.$correlationId) {
        delete this.objectMetadata.$correlationId
      }
      return
    }
    this.objectCorrelationId = newCorrelationId
    if (this.metadata && typeof this.metadata !== 'string' && this.metadata.$correlationId) {
      this.metadata.$correlationId = this.objectCorrelationId
    } else {
      this.metadata = {$correlationId: this.objectCorrelationId}
    }
  }

  /**
   * Getter for event correlationId
   */
  public get correlationId(): string | null {
    if (this.metadata && typeof this.metadata !== 'string') {
      this.objectCorrelationId = this.metadata.$correlationId || null
    }
    return this.objectCorrelationId
  }

  /**
   * Returns a new new instance of Event with correlation id set to currents events correlation id or id
   */
  public causesEvent(eventType: string, data?: {}, metadata?: {}): Event {
    const childEvent = new Event(eventType, data, metadata)
    childEvent.correlationId = this.correlationId || this.id
    return childEvent
  }

  /**
   * Returns a new instance of Event from protobuf result
   */
  public static fromRaw(rawEvent: model.eventstore.proto.IEventRecord | null | undefined): Event {
    if (!rawEvent) {
      throw eventstoreError.newProtocolError('No event or link was given at Event.fromRaw')
    }
    const event = new Event(rawEvent.eventType)
    if (rawEvent.eventType === '$>' || rawEvent.eventType === '$@') {
      //is linked event
      event.isResolved = false
    }
    event.streamId = rawEvent.eventStreamId
    event.eventNumber = rawEvent.eventNumber
    event.eventId = uuidFromBuffer(Buffer.from(rawEvent.eventId))
    event.dataContentType = rawEvent.dataContentType
    event.rawData = rawEvent.data
    event.rawMetadata = rawEvent.metadata || null
    event.metadataContentType = rawEvent.metadataContentType
    event.objectCreated = rawEvent.created ? rawEvent.created : null
    event.objectCreatedEpoch = rawEvent.createdEpoch ? rawEvent.createdEpoch : null
    event.freeze()
    return event
  }

  /**
   * Returns protobuf representation of this event
   */
  public toRaw(): model.eventstore.proto.NewEvent {
    const newEvent = {
      eventId: uuidToBuffer(this.eventId),
      eventType: this.eventType,
      data: Buffer.from(JSON.stringify(this.data)),
      metadata: this.metadata ? Buffer.from(JSON.stringify(this.metadata)) : null
    }
    return protobuf.NewEvent.fromObject(newEvent)
  }

  /**
   * Setter for name
   */
  public set name(newName: string) {
    this.throwIfNotNewEvent('name')
    this.eventType = newName
  }

  /**
   * Getter for event name
   */
  public get name(): string {
    return this.eventType
  }
}