All files / src last-write-wins.ts

97.22% Statements 35/36
78.26% Branches 18/23
100% Functions 5/5
100% Lines 33/33

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      4x                                                     4x       16x 44x 44x 9x             4x                       15x 15x   15x 15x 15x 8x 8x 8x 8x     15x     4x   4x       8x 7x     8x 14x 14x   7x 7x           8x       11x   11x 18x 18x 15x       11x        
// import Debug from 'debug';
import {EntityProperties} from "./entity";
import {CollapsedDelta, LosslessViewOne} from "./lossless";
import {Lossy} from './lossy';
import {DomainEntityID, PropertyID, PropertyTypes, Timestamp, ViewMany} from "./types";
// const debug = Debug('rz:lossy:last-write-wins');
 
type TimestampedProperty = {
  value: PropertyTypes,
  timeUpdated: Timestamp
};
 
type TimestampedProperties = {
  [key: PropertyID]: TimestampedProperty
};
 
export type LossyViewOne<T = TimestampedProperties> = {
  id: DomainEntityID;
  properties: T;
};
 
export type LossyViewMany<T = TimestampedProperties> = ViewMany<LossyViewOne<T>>;
 
export type ResolvedViewOne = LossyViewOne<EntityProperties>;
export type ResolvedViewMany = ViewMany<ResolvedViewOne>;
 
type Accumulator = LossyViewMany<TimestampedProperties>;
type Result = LossyViewMany<EntityProperties>;
 
// Extract a particular value from a delta's pointers
export function valueFromCollapsedDelta(
  key: string,
  delta: CollapsedDelta
): string | number | undefined {
  for (const pointer of delta.pointers) {
    for (const [k, value] of Object.entries(pointer)) {
      if (k === key && (typeof value === "string" || typeof value === "number")) {
        return value;
      }
    }
  }
}
 
// Resolve a value for an entity by last write wins
export function lastValueFromDeltas(
  key: string,
  deltas?: CollapsedDelta[]
): {
  delta?: CollapsedDelta,
  value?: string | number,
  timeUpdated?: number
} | undefined {
  const res: {
    delta?: CollapsedDelta,
    value?: string | number,
    timeUpdated?: number
  } = {};
  res.timeUpdated = 0;
 
  for (const delta of deltas || []) {
    const value = valueFromCollapsedDelta(key, delta);
    if (value === undefined) continue;
    Iif (res.timeUpdated && delta.timeCreated < res.timeUpdated) continue;
    res.delta = delta;
    res.value = value;
    res.timeUpdated = delta.timeCreated;
  }
 
  return res;
}
 
export class LastWriteWins extends Lossy<Accumulator, Result> {
  initializer(): Accumulator {
    return {};
  }
 
  reducer(acc: Accumulator, cur: LosslessViewOne): Accumulator {
    if (!acc[cur.id]) {
      acc[cur.id] = {id: cur.id, properties: {}};
    }
 
    for (const [key, deltas] of Object.entries(cur.propertyDeltas)) {
      const {value, timeUpdated} = lastValueFromDeltas(key, deltas) || {};
      if (!value || !timeUpdated) continue;
 
      if (timeUpdated > (acc[cur.id].properties[key]?.timeUpdated || 0)) {
        acc[cur.id].properties[key] = {
          value,
          timeUpdated
        };
      }
    }
    return acc;
  };
 
  resolver(cur: Accumulator): Result {
    const res: Result = {};
 
    for (const [id, ent] of Object.entries(cur)) {
      res[id] = {id, properties: {}};
      for (const [key, {value}] of Object.entries(ent.properties)) {
        res[id].properties[key] = value;
      }
    }
 
    return res;
  };
}