{"version":3,"file":"app-585670c5.981ca8458d389c9a476a.bundle.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAGA;AAGA;AAGA;AAGA;AAGA;AAGA;AAGA;AAGA;AAGA;AAGA;AAGA;AAGA;;AAKA;AAAA;AAaA;AAHA;AAAA;AAUA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAAA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AAHA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;AC9UA;;AAEA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAIA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;;AAIA;AACA;AACA;;;;;;;;;;;;;;;;;;AChGA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;;ACtGA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AAIA;AAHA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAIA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;AClHA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AAOA;AANA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;;AAIA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AC9HA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;;ACxFA;;AAEA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AC3GA;;AAEA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;;ACvFA;;AAEA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;AC/EA;;AAEA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;ACvGA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;AC1DA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;AC9FA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;AC/DA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;AC3DA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;AClGA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AC9BA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;AClFA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AAIA;AAFA;AAAA;AAAA;AACA;AAAA;AAEA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;;AC9CA;;AAEA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;;;;;;;;;;;;;;;AC3CA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;AClEA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;ACnCA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;ACrCA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;ACxDA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;ACzEA;;AAEA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;ACnEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAGA;AACA;;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAGA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;AChFA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;;;;;;;;;;;;;;;;AClEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAGA;AACA;AAKA;AAHA;AAAA;AAIA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;ACtDA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAKA;AACA;;AAGA;AACA;AACA;AACA;AACA;AASA;AARA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;AChGA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;ACjCA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAGA;AAAA;AAOA;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;ACrCA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;;;;;;;;;;;;;;;ACxCA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;ACzCA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAGA;AAAA;AAWA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;AClEA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;AC/CA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AAAA;AASA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;ACzGA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;ACxDA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;ACpEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;AAGA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;AC/EA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;AAGA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;AChFA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;AAGA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;ACvEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;AAGA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;ACzEA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAKA;AAAA;AASA;AAMA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;ACnEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;ACrDA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;ACtGA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAGA;AAAA;AAOA;AAIA;AACA;AACA;AAEA;AAOA;AANA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAOA;AANA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;ACjEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAOA;AANA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;AC5GA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAOA;AANA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AAEA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;AClGA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAGA;AAAA;AAIA;AACA;AACA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;ACnDA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;AC3DA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;ACzCA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAKA;AAAA;AAQA;AAKA;AACA;AACA;AACA;;AAGA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;AChDA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAOA;AANA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;AC9GA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAGA;AAAA;AAOA;AAHA;AAAA;AAIA;AAEA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;;;ACjDA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAMA;AACA;AACA;AACA;AAOA;AANA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5HA;;AAEA;AACA;;AAEA;AACA;AACA;;AAKA;AACA;AAIA;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAGA;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AAMA;AAJA;AAAA;AACA;AAIA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AAHA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AADA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;AC3QA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;;;;;;;;;;;;;;;ACtCA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;;;;;;;;;;;;;;;ACnCA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;;;;;;;;;;;;;;;;ACzCA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;AC3CA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;;;;;;;;;;;;;;;;;;;;ACnCA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAMA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AAGA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;ACjGA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAMA;AACA;AACA;AACA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AAAA;AACA;AAGA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAGA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;AC/GA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAIA;AACA;AACA;AAGA;AACA;AACA;AAGA;;;;;;;;;;;;;;;;;;;ACpDA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;;;;;;;;;;;;;;;;;;;AC1EA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAKA;AAJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;;;;;;;;;;;;;;;AChFA;;AAEA;AACA;;AAKA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;ACxCA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAGA;AAAA;AASA;AAMA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;ACxDA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;ACzDA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AAAA;AASA;AAMA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;AC3DA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;AC/CA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;AC5CA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA","sources":["webpack://latinera/./sources/services/activity.js","webpack://latinera/./sources/services/activity/create-starting-analysis-activity-from-existing-sentence.js","webpack://latinera/./sources/services/activity/create-starting-analysis-activity-from-new-sentence.js","webpack://latinera/./sources/services/activity/create-starting-task-analysis-activity-from-existing-sentence.js","webpack://latinera/./sources/services/activity/create-starting-task-analysis-activity-from-new-sentence.js","webpack://latinera/./sources/services/activity/create-task-activity.js","webpack://latinera/./sources/services/activity/create-task-analysis-activity.js","webpack://latinera/./sources/services/activity/delete-analysis-activity.js","webpack://latinera/./sources/services/activity/delete-task-activity.js","webpack://latinera/./sources/services/activity/delete-task-analysis-activity.js","webpack://latinera/./sources/services/activity/get-activities-by-filter.js","webpack://latinera/./sources/services/activity/get-activities-by-task.js","webpack://latinera/./sources/services/activity/get-activity-by-key.js","webpack://latinera/./sources/services/activity/get-activity-items-by-filter.js","webpack://latinera/./sources/services/activity/get-current-activities.js","webpack://latinera/./sources/services/activity/get-last-opened-activity-id.js","webpack://latinera/./sources/services/activity/get-previous-activities.js","webpack://latinera/./sources/services/activity/get-recent-activities.js","webpack://latinera/./sources/services/activity/get-task-activity-meta-data-by-key.js","webpack://latinera/./sources/services/activity/reuse-activity.js","webpack://latinera/./sources/services/activity/set-last-opened-activity-id.js","webpack://latinera/./sources/services/activity/set-task-activity-meta-data-by-key.js","webpack://latinera/./sources/services/activity/unuse-activity.js","webpack://latinera/./sources/services/activity/update-analysis-activity.js","webpack://latinera/./sources/services/activity/update-task-activity.js","webpack://latinera/./sources/services/activity/update-task-analysis-activity.js","webpack://latinera/./sources/services/activity/use-activity.js","webpack://latinera/./sources/services/api.js","webpack://latinera/./sources/services/api/fetch.js","webpack://latinera/./sources/services/api/is-alive.js","webpack://latinera/./sources/services/application.js","webpack://latinera/./sources/services/application/get-run-mode.js","webpack://latinera/./sources/services/application/set-run-mode.js","webpack://latinera/./sources/services/card.js","webpack://latinera/./sources/services/card/create-card.js","webpack://latinera/./sources/services/definition.js","webpack://latinera/./sources/services/definition/create-definition.js","webpack://latinera/./sources/services/definition/get-definitions-by-lemma.js","webpack://latinera/./sources/services/definition/report-definition.js","webpack://latinera/./sources/services/definition/report-lemma-definition.js","webpack://latinera/./sources/services/definition/unreport-definition.js","webpack://latinera/./sources/services/definition/unreport-lemma-definition.js","webpack://latinera/./sources/services/element.js","webpack://latinera/./sources/services/element/create-element.js","webpack://latinera/./sources/services/element/get-child-elements-by-element.js","webpack://latinera/./sources/services/entity.js","webpack://latinera/./sources/services/entity/get-entities-by-keys.js","webpack://latinera/./sources/services/entity/get-entity-by-key.js","webpack://latinera/./sources/services/event.js","webpack://latinera/./sources/services/event/publish.js","webpack://latinera/./sources/services/event/subscribe.js","webpack://latinera/./sources/services/generator.js","webpack://latinera/./sources/services/generator/get-generators-by-element.js","webpack://latinera/./sources/services/graphql.js","webpack://latinera/./sources/services/graphql/query.js","webpack://latinera/./sources/services/index.js","webpack://latinera/./sources/services/index/delete-all-collection-documents.js","webpack://latinera/./sources/services/index/delete-all-database-documents.js","webpack://latinera/./sources/services/index/delete-document-by-key.js","webpack://latinera/./sources/services/index/get-all-collection-documents.js","webpack://latinera/./sources/services/index/get-all-database-documents-count.js","webpack://latinera/./sources/services/index/get-document-by-key.js","webpack://latinera/./sources/services/index/get-documents-by-keys.js","webpack://latinera/./sources/services/index/prune-expired-documents.js","webpack://latinera/./sources/services/index/save-document.js","webpack://latinera/./sources/services/index/save-documents.js","webpack://latinera/./sources/services/index/set-pruning-interval.js","webpack://latinera/./sources/services/inflection.js","webpack://latinera/./sources/services/inflection/get-inflections-by-filter.js","webpack://latinera/./sources/services/lemma.js","webpack://latinera/./sources/services/lemma/create-lemma.js","webpack://latinera/./sources/services/lemma/delete-lemma.js","webpack://latinera/./sources/services/lemma/get-lemma-inflection-status.js"],"sourcesContent":["// Define the \"activity\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport EntityService from \"services/entity\";\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\nimport SentenceService from \"services/sentence\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { getActivityByKey } from \"./activity/get-activity-by-key\";\nimport { getActivitiesByTask } from \"./activity/get-activities-by-task\";\nimport { createTaskActivity } from \"./activity/create-task-activity\";\nimport { updateTaskActivity } from \"./activity/update-task-activity\";\nimport { deleteTaskActivity } from \"./activity/delete-task-activity\";\nimport { useActivity } from \"./activity/use-activity\";\nimport { reuseActivity } from \"./activity/reuse-activity\";\nimport { unuseActivity } from \"./activity/unuse-activity\";\nimport { getActivitiesByFilter } from \"./activity/get-activities-by-filter\";\nimport { getCurrentActivities } from \"./activity/get-current-activities\";\nimport { getPreviousActivities } from \"./activity/get-previous-activities\";\nimport { getRecentActivities } from \"./activity/get-recent-activities\";\nimport { \n getActivityItemsByFilter \n} from \"./activity/get-activity-items-by-filter\";\nimport {\n createStartingAnalysisActivityFromNewSentence\n} from \"./activity/create-starting-analysis-activity-from-new-sentence\";\nimport {\n createStartingAnalysisActivityFromExistingSentence\n} from \"./activity/create-starting-analysis-activity-from-existing-sentence\";\nimport {\n createTaskAnalysisActivity\n} from \"./activity/create-task-analysis-activity\";\nimport {\n createStartingTaskAnalysisActivityFromNewSentence\n} from \"./activity/create-starting-task-analysis-activity-from-new-sentence\";\nimport {\n createStartingTaskAnalysisActivityFromExistingSentence\n} from \"./activity/create-starting-task-analysis-activity-from-existing-sentence\";\nimport {\n updateAnalysisActivity\n} from \"./activity/update-analysis-activity\";\nimport {\n updateTaskAnalysisActivity\n} from \"./activity/update-task-analysis-activity\";\nimport { \n getLastOpenedActivityId \n} from \"./activity/get-last-opened-activity-id\";\nimport { \n setLastOpenedActivityId \n} from \"./activity/set-last-opened-activity-id\";\nimport { \n getTaskActivityMetaDataByKey \n} from \"./activity/get-task-activity-meta-data-by-key\";\nimport { \n setTaskActivityMetaDataByKey \n} from \"./activity/set-task-activity-meta-data-by-key\";\nimport {\n deleteAnalysisActivity\n} from \"./activity/delete-analysis-activity\";\nimport {\n deleteTaskAnalysisActivity\n} from \"./activity/delete-task-analysis-activity\";\n\n\n// Export the \"ActivityService\" class\n@inject(\n EntityService,\n GraphQLService,\n IndexService,\n SentenceService,\n UserService\n)\nexport default class ActivityService {\n\n // Attributes\n sortActivitiesByAscendingIndex = true;\n\n constructor(\n entityService,\n graphQLService,\n indexService,\n sentenceService,\n userService\n ) {\n this.entityService = entityService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.sentenceService = sentenceService;\n this.userService = userService;\n }\n\n\n // Core methods\n async getActivityByKey({ \n activityKey = \"\",\n indexCollectionName = \"\",\n fetchFromIndex = true,\n saveToIndex = true\n }) {\n return await getActivityByKey.call(this, {\n activityKey,\n indexCollectionName,\n fetchFromIndex,\n saveToIndex\n });\n }\n\n async getActivityItemByKey({\n activityKey = \"\",\n indexCollectionName = \"\",\n fetchFromIndex = true,\n saveToIndex = true\n }) {\n return await this.entityService.getEntityByKey({\n entityName: \"activityItem\",\n entityKey: activityKey || \"\",\n indexCollectionName,\n fetchFromIndex,\n saveToIndex\n });\n }\n\n async getActivitiesByKeys({\n activitiesKeys = [],\n indexCollectionName = \"\",\n fetchFromIndex = true,\n saveToIndex = true\n }) {\n return await this.entityService.getEntitiesByKeys({\n entityName: \"activity\",\n entitiesKeys: activitiesKeys,\n indexCollectionName,\n fetchFromIndex,\n saveToIndex\n });\n }\n\n async getActivityItemsByKeys({\n activitiesKeys = [], \n indexCollectionName = \"\",\n fetchFromIndex = true,\n saveToIndex = true\n }) {\n return await this.entityService.getEntitiesByKeys({\n entityName: \"activityItem\",\n entitiesKeys: activitiesKeys,\n indexCollectionName,\n fetchFromIndex,\n saveToIndex\n });\n }\n\n async getActivitiesByFilter(filterData) {\n return await getActivitiesByFilter.call(this, filterData);\n }\n\n async getActivityItemsByFilter(filterData) {\n return await getActivityItemsByFilter.call(this, filterData);\n }\n\n async getActivitiesByTask({ \n taskKey = \"\" \n }) {\n return await getActivitiesByTask.call(this, { taskKey });\n }\n\n async getCurrentActivities() {\n return await getCurrentActivities.call(this);\n }\n\n async getPreviousActivities() {\n return await getPreviousActivities.call(this);\n }\n\n async getRecentActivities(filterData = {}) {\n return await getRecentActivities.call(this, filterData);\n }\n\n async createStartingAnalysisActivityFromNewSentence({ createSentenceData }) {\n return await createStartingAnalysisActivityFromNewSentence\n .call(this, { createSentenceData });\n }\n\n async createStartingAnalysisActivityFromExistingSentence({ inSentenceKey }) {\n return await createStartingAnalysisActivityFromExistingSentence\n .call(this, { inSentenceKey });\n }\n\n async createTaskActivity({ \n createTaskActivityData = null\n }) {\n return await createTaskActivity.call(this, { createTaskActivityData });\n }\n\n async createTaskAnalysisActivity({\n taskActivityKey = \"\",\n createTaskAnalysisActivityData = null\n }) {\n return await createTaskAnalysisActivity.call(this, {\n taskActivityKey,\n createTaskAnalysisActivityData\n });\n }\n\n async createStartingTaskAnalysisActivityFromNewSentence({\n taskActivityKey = \"\",\n taskIndex,\n createSentenceData = null\n }) {\n return await createStartingTaskAnalysisActivityFromNewSentence.call(this, {\n taskActivityKey,\n taskIndex,\n createSentenceData\n });\n }\n\n async createStartingTaskAnalysisActivityFromExistingSentence({\n taskActivityKey = \"\",\n taskIndex,\n inSentenceKey = \"\"\n }) {\n return createStartingTaskAnalysisActivityFromExistingSentence\n .call(this, {\n taskActivityKey,\n taskIndex,\n inSentenceKey\n });\n }\n\n async updateAnalysisActivity({\n analysisActivityKey = \"\",\n updateAnalysisActivityData = null\n }) {\n return await updateAnalysisActivity.call(this, {\n analysisActivityKey,\n updateAnalysisActivityData\n });\n }\n\n async updateTaskActivity({\n taskActivityKey = \"\",\n updateTaskActivityData = null\n }) {\n return await updateTaskActivity.call(this, {\n taskActivityKey,\n updateTaskActivityData\n });\n }\n\n async updateTaskAnalysisActivity({\n taskActivityKey = \"\",\n analysisActivityKey = \"\",\n updateAnalysisActivityData = null\n }) {\n return await updateTaskAnalysisActivity.call(this, {\n taskActivityKey,\n analysisActivityKey,\n updateAnalysisActivityData\n });\n }\n\n async getLastOpenedActivityId() {\n return await getLastOpenedActivityId.call(this);\n }\n\n async setLastOpenedActivityId({\n lastOpenedActivityId = \"\"\n }) {\n return await setLastOpenedActivityId.call(this, { lastOpenedActivityId });\n }\n\n async getTaskActivityMetaDataByKey({\n taskActivityKey = \"\"\n }) {\n return await getTaskActivityMetaDataByKey.call(this, { taskActivityKey });\n }\n\n async setTaskActivityMetaDataByKey({\n taskActivityKey = \"\",\n taskActivityMetaData = null\n }) {\n return await setTaskActivityMetaDataByKey.call(this, { \n taskActivityKey,\n taskActivityMetaData\n });\n }\n\n async useActivity({\n activityKey = \"\",\n nextActivityKey // optional\n }) {\n return await useActivity.call(this, { activityKey, nextActivityKey });\n }\n\n async reuseActivity({\n activityKey = \"\",\n nextActivityKey // optional\n }) {\n return await reuseActivity.call(this, { activityKey, nextActivityKey });\n }\n\n async unuseActivity({ \n activityKey = \"\" \n }) {\n return await unuseActivity.call(this, { activityKey });\n }\n\n async deleteAnalysisActivity({ \n analysisActivityKey = \"\"\n }) {\n return await deleteAnalysisActivity.call(this, { analysisActivityKey });\n }\n\n async deleteTaskActivity({\n taskActivityKey = \"\"\n }) {\n return await deleteTaskActivity.call(this, { taskActivityKey });\n }\n\n async deleteTaskAnalysisActivity({\n taskActivityKey,\n analysisActivityKey\n }) {\n return await deleteTaskAnalysisActivity.call(this, {\n taskActivityKey,\n analysisActivityKey\n });\n }\n\n}\n","// Define the \"createStartingAnalysisActivityFromExistingSentence\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateCreateStartingAnalysisActivityFromExistingSentenceMutationData\n} from \"queries/activity\";\n\n\n// createStartingAnalysisActivityFromExistingSentence: async\n// returns a promise which either resolves with the starting analysis\n// activity object created on the server/index from the specified existing\n// sentence (by key), or rejects with an error\nexport async function createStartingAnalysisActivityFromExistingSentence({\n inSentenceKey = \"\"\n}) {\n if (typeOf(inSentenceKey) !== \"string\") {\n throw new Error(`Parameter \"inSentenceKey\" should be a sentence key`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Create starting analysis activity on server\n const queryData =\n generateCreateStartingAnalysisActivityFromExistingSentenceMutationData({\n inSentenceKey\n });\n let createdStartingAnalysisActivityData;\n try {\n ({\n createStartingAnalysisActivityFromExistingSentence:\n createdStartingAnalysisActivityData\n } = await this.graphQLService.query({ queryData }));\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating starting analysis activity ` +\n `from existing sentence on server`);\n }\n const {\n key: createdStartingAnalysisActivityKey\n } = createdStartingAnalysisActivityData;\n console.debug(`Created starting analysis activity ` +\n `\"${createdStartingAnalysisActivityKey}\"` +\n `from existing sentence \"${inSentenceKey}\" on server`);\n\n // Update user credit\n this.userService.chargeUserCreditForActivityCreation();\n\n // Update current activities in the index, if any\n const cacheKey = computeHash(`current-activities`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: currentActivitiesKeys = [] } = cacheData || {};\n const newCurrentActivitiesKeys = this.sortActivitiesByAscendingIndex ?\n [ ...currentActivitiesKeys, createdStartingAnalysisActivityKey ] :\n [ createdStartingAnalysisActivityKey, ...currentActivitiesKeys ];\n if (currentActivitiesKeys.length > 0) {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: newCurrentActivitiesKeys\n }\n });\n }\n\n // Update the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: createdStartingAnalysisActivityData\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating starting analysis activity ` +\n `\"${createdStartingAnalysisActivityKey}\" ` +\n `from existing sentence \"${inSentenceKey}\" on index`);\n }\n console.debug(`Created starting analysis activity ` +\n `\"${createdStartingAnalysisActivityKey}\"` +\n `from existing sentence \"${inSentenceKey}\" on index`);\n\n // Return created starting analysis activity's data\n return createdStartingAnalysisActivityData;\n}\n","// Define the \"createStartingAnalysisActivityFromNewSentence\" service method\n\n// Import utility modules\nimport { computeCreateSentenceData } from \"utilities/sentence\";\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateCreateStartingAnalysisActivityFromNewSentenceMutationData\n} from \"queries/activity\";\n\n\n// createStartingAnalysisActivityFromNewSentence: async\n// returns a promise which either resolves with the starting analysis\n// activity object created on the server/index from the specified new\n// sentence data, or rejects with an error\nexport async function createStartingAnalysisActivityFromNewSentence({\n createSentenceData: {\n sentenceData = null,\n authorKey = \"\"\n }\n}) {\n if (typeOf(sentenceData) !== \"object\") {\n throw new Error(`Parameter \"sentenceData\" should be an object`);\n } else if (typeOf(authorKey) !== \"string\") {\n throw new Error(`Parameter \"authorKey\" should be a key string`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Create starting analysis activity on server\n const createSentenceData = computeCreateSentenceData({\n sentenceData,\n authorKey\n });\n const queryData =\n generateCreateStartingAnalysisActivityFromNewSentenceMutationData({\n createSentenceData\n });\n let createdStartingAnalysisActivityData;\n try {\n ({\n createStartingAnalysisActivityFromNewSentence:\n createdStartingAnalysisActivityData\n } = await this.graphQLService.query({ queryData }));\n } catch(error) {\n throw new Error(`Error creating starting analysis activity ` +\n `from new sentence on server`);\n }\n const {\n key: createdStartingAnalysisActivityKey\n } = createdStartingAnalysisActivityData;\n console.debug(`Created starting analysis activity ` +\n `\"${createdStartingAnalysisActivityKey}\" from new sentence on server`);\n\n // Update user credit\n this.userService.chargeUserCreditForActivityCreation();\n\n // Update current activities on the index\n const cacheKey = computeHash(`current-activities`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: currentActivitiesKeys = [] } = cacheData || {};\n const newCurrentActivitiesKeys = this.sortActivitiesByAscendingIndex ?\n [ ...currentActivitiesKeys, createdStartingAnalysisActivityKey ] :\n [ createdStartingAnalysisActivityKey, ...currentActivitiesKeys ];\n if (currentActivitiesKeys.length > 0) {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: newCurrentActivitiesKeys\n }\n });\n }\n\n // Update the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: createdStartingAnalysisActivityData\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating starting analysis activity ` +\n `\"${createdStartingAnalysisActivityKey}\" from sentence on index`);\n }\n console.debug(`Created starting analysis activity ` +\n `\"${createdStartingAnalysisActivityKey}\" from new sentence on index`);\n\n // Return created starting analysis activity's data\n return createdStartingAnalysisActivityData;\n}\n","// Define the \"createStartingTaskAnalysisActivityFromExistingSentence\"\n// service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateCreateStartingTaskAnalysisActivityFromExistingSentenceMutationData\n} from \"queries/activity\";\n\n\n// createStartingTaskAnalysisActivityFromExistingSentence: async\n// returns a promise which either resolves with the starting task analysis\n// activity object created on the server/index from the specified existing\n// sentence (by key), or rejects with an error\nexport async function createStartingTaskAnalysisActivityFromExistingSentence({\n taskActivityKey = \"\",\n taskIndex, // optional\n inSentenceKey = \"\"\n}) {\n if (typeOf(inSentenceKey) !== \"string\") {\n throw new Error(`Parameter \"inSentenceKey\" should be a sentence key`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Create starting analysis activity on server\n const queryData =\n generateCreateStartingTaskAnalysisActivityFromExistingSentenceMutationData({\n taskActivityKey,\n taskIndex,\n inSentenceKey\n });\n let createdStartingTaskAnalysisActivityData;\n try {\n ({\n createStartingTaskAnalysisActivityFromExistingSentence:\n createdStartingTaskAnalysisActivityData\n } = await this.graphQLService.query({ queryData }));\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating starting task analysis activity ` +\n `from existing sentence on server`);\n }\n const {\n key: createdStartingTaskAnalysisActivityKey\n } = createdStartingTaskAnalysisActivityData;\n console.debug(`Created starting task analysis activity ` +\n `\"${createdStartingTaskAnalysisActivityKey}\" ` +\n `from existing sentence \"${inSentenceKey}\" on server`);\n\n // Update user credit\n this.userService.chargeUserCreditForActivityCreation();\n\n // Get updated task activity data\n const updatedTaskActivityData = await this.getActivityItemByKey({\n activityKey: taskActivityKey, \n fetchFromIndex: false \n });\n if (updatedTaskActivityData) {\n const { task: updatedTaskData = null } = updatedTaskActivityData;\n const {\n key: taskKey = \"\",\n activities: analysisActivitiesData = []\n } = updatedTaskData || {};\n const cacheKey = computeHash(`activities-by-task-${taskKey}`);\n const analysisActivitiesKeys = analysisActivitiesData\n .map(({ key: analysisActivityKey = \"\" }) => analysisActivityKey);\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: updatedTaskActivityData\n }),\n this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: analysisActivitiesKeys\n }\n })\n ]);\n }\n\n // Update the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: createdStartingTaskAnalysisActivityData\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating starting task analysis activity ` +\n `\"${createdStartingTaskAnalysisActivityKey}\" ` +\n `from existing sentence \"${inSentenceKey}\" on index`);\n }\n console.debug(`Created starting task analysis activity ` +\n `\"${createdStartingTaskAnalysisActivityKey}\" ` +\n `from existing sentence \"${inSentenceKey}\" on index`);\n\n // Return created starting task analysis activity data and updated\n // task activity data\n return {\n analysisActivityData: createdStartingTaskAnalysisActivityData,\n taskActivityData: updatedTaskActivityData\n };\n}\n","// Define the \"createStartingTaskAnalysisActivityFromNewSentence\" service\n// method\n\n// Import utility modules\nimport { computeCreateSentenceData } from \"utilities/sentence\";\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateCreateStartingTaskAnalysisActivityFromNewSentenceMutationData\n} from \"queries/activity\";\n\n\n// createStartingTaskAnalysisActivityFromNewSentence: async\n// returns a promise which either resolves with the starting task analysis\n// activity object created on the server/index from the specified new\n// sentence data, or rejects with an error\nexport async function createStartingTaskAnalysisActivityFromNewSentence({\n taskActivityKey = \"\",\n taskIndex, // optional\n createSentenceData: {\n sentenceData = null,\n authorKey // optional\n }\n}) {\n if (typeOf(taskActivityKey) !== \"string\" || !taskActivityKey) {\n throw new Error(`Parameter \"taskActivityKey\" should be a key string`);\n } else if (typeOf(sentenceData) !== \"object\") {\n throw new Error(`Parameter \"sentenceData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Create starting analysis activity on server\n const createSentenceData = computeCreateSentenceData({\n sentenceData,\n authorKey\n });\n const queryData =\n generateCreateStartingTaskAnalysisActivityFromNewSentenceMutationData({\n taskActivityKey,\n taskIndex,\n createSentenceData\n });\n let createdStartingTaskAnalysisActivityData;\n try {\n ({\n createStartingTaskAnalysisActivityFromNewSentence:\n createdStartingTaskAnalysisActivityData\n } = await this.graphQLService.query({ queryData }));\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating starting task analysis activity ` +\n `from new sentence on server`);\n }\n const {\n key: createdStartingTaskAnalysisActivityKey,\n analysis: { inSentence: { key: inSentenceKey } }\n } = createdStartingTaskAnalysisActivityData;\n console.debug(`Created starting task analysis activity ` +\n `\"${createdStartingTaskAnalysisActivityKey}\" ` +\n `from new sentence on server`);\n\n // Update user credit\n this.userService.chargeUserCreditForActivityCreation();\n\n // Get updated task activity data\n const updatedTaskActivityData = await this.getActivityItemByKey({\n activityKey: taskActivityKey, \n fetchFromIndex: false \n });\n if (updatedTaskActivityData) {\n const { task: newTaskData = null } = updatedTaskActivityData;\n const {\n key: taskKey = \"\",\n activities: analysisActivitiesData = []\n } = newTaskData || {};\n const cacheKey = computeHash(`activities-by-task-${taskKey}`);\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: updatedTaskActivityData\n }),\n this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: analysisActivitiesData.map(({ key }) => key)\n }\n })\n ]);\n }\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: createdStartingTaskAnalysisActivityData\n }),\n this.sentenceService.getSentenceByKey(inSentenceKey)\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating starting task analysis activity ` +\n `\"${createdStartingTaskAnalysisActivityKey}\" ` +\n `from new sentence on index`);\n }\n console.debug(`Created starting task analysis activity ` +\n `\"${createdStartingTaskAnalysisActivityKey}\" ` +\n `from new sentence on index`);\n\n // Return created starting task analysis activity data and updated task\n // activity data\n return {\n analysisActivityData: createdStartingTaskAnalysisActivityData,\n taskActivityData: updatedTaskActivityData\n };\n}\n","// Define the \"createTaskActivity\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateCreateTaskActivityMutationData } from \"queries/activity\";\n\n\n// createTaskActivity: async\n// returns a promise which either resolves with the task activity data\n// object created on the server/index, or rejects with an error\nexport async function createTaskActivity({\n createTaskActivityData = null\n}) {\n if (typeOf(createTaskActivityData) !== \"object\") {\n throw new Error(`Parameter \"createTaskActivityData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Create task activity on server\n const queryData = generateCreateTaskActivityMutationData({\n createTaskActivityData\n });\n let createdTaskActivityData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ createTaskActivity: createdTaskActivityData } = responseData);\n } catch(error) {\n throw new Error(`Error creating task activity on server`);\n }\n const {\n key: createdTaskActivityKey,\n task: createdTaskData\n } = createdTaskActivityData;\n console.debug(`Created task activity \"${createdTaskActivityKey}\" ` +\n `on server`);\n\n // Update current activities in the index, if any\n const cacheKey = computeHash(`current-activities`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: currentActivitiesKeys = [] } = cacheData || {};\n const newCurrentActivitiesKeys = this.sortActivitiesByAscendingIndex ?\n [ ...currentActivitiesKeys, createdTaskActivityKey ] :\n [ createdTaskActivityKey, ...currentActivitiesKeys ];\n if (currentActivitiesKeys.length > 0) {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: newCurrentActivitiesKeys\n }\n });\n }\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: createdTaskActivityData\n }),\n this.indexService.saveDocument({\n collectionName: \"tasks\",\n documentData: createdTaskData\n })\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating task activity on index`);\n }\n console.debug(`Created task activity \"${createdTaskActivityKey}\" ` +\n `on index`);\n\n // Return created task activity's data\n return createdTaskActivityData;\n}\n","// Define the \"createTaskAnalysisActivity\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateCreateTaskAnalysisActivityMutationData\n} from \"queries/activity\";\n\n\n// createTaskAnalysisActivity: async\n// returns a promise which either resolves with the analysis activity data\n// object created on the server/index, or rejects with an error\nexport async function createTaskAnalysisActivity({\n taskActivityKey = \"\",\n taskIndex,\n createTaskAnalysisActivityData = null\n}) {\n if (typeOf(taskActivityKey) !== \"string\" || !taskActivityKey) {\n throw new Error(`Parameter \"taskActivityKey\" should be an activity key`);\n } else if (typeOf(taskIndex) !== \"number\") {\n throw new Error(`Parameter \"taskIndex\" should be a number`);\n } else if (typeOf(createTaskAnalysisActivityData) !== \"object\") {\n throw new Error(`Parameter \"createTaskAnalysisActivityData\" should be ` +\n `an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Create task analysis activity on server\n const queryData = generateCreateTaskAnalysisActivityMutationData({\n taskActivityKey,\n taskIndex,\n createTaskAnalysisActivityData\n });\n let createdAnalysisActivityData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ createTaskAnalysisActivity: createdAnalysisActivityData } = responseData);\n } catch(error) {\n throw new Error(`Error creating task analysis activity on server`);\n }\n const { key: createdAnalysisActivityKey } = createdAnalysisActivityData;\n console.debug(`Created analysis activity \"${createdAnalysisActivityKey}\" ` +\n `within task activity \"${taskActivityKey}\" on server`);\n\n // Get created task activity data\n const createdTaskActivityData = await this.getActivityItemByKey({\n activityKey: taskActivityKey, \n fetchFromIndex: false \n });\n if (createdTaskActivityData) {\n const { task: createdTaskData = null } = createdTaskActivityData;\n const {\n key: taskKey = \"\",\n activities: analysisActivitiesData = []\n } = createdTaskData || {};\n const analysisActivitiesKeys = analysisActivitiesData\n .map(({ key: analysisActivityKey = \"\" }) => analysisActivityKey);\n const cacheKey = computeHash(`activities-by-task-${taskKey}`);\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: createdTaskActivityData\n }),\n this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: analysisActivitiesKeys\n }\n })\n ]);\n }\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: createdAnalysisActivityData\n })\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating analysis activity ` +\n `\"${createdAnalysisActivityKey}\" within task activity ` +\n `\"${taskActivityKey}\" on server/index`);\n }\n console.debug(`Created analysis activity \"${createdAnalysisActivityKey}\" ` +\n `within task activity \"${taskActivityKey}\" on index`);\n\n // Return created task analysis activity data and update task activity\n // data\n return {\n analysisActivityData: createdAnalysisActivityData,\n taskActivityData: createdTaskActivityData\n };\n}\n","// Define the \"deleteAnalysisActivity\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateDeleteAnalysisActivityMutationData\n} from \"queries/activity\";\n\n\n// deleteAnalysisActivity: async\n// returns a promise which either resolves with the key of the analysis\n// activity that has been deleted from the server/index, or rejects with\n// an error\nexport async function deleteAnalysisActivity({\n analysisActivityKey = \"\"\n}) {\n if (typeOf(analysisActivityKey) !== \"string\" || !analysisActivityKey) {\n throw new Error(`Parameter \"analysisActivityKey\" should be an activity key`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Delete analysis activity on the server\n const queryData =\n generateDeleteAnalysisActivityMutationData({ analysisActivityKey });\n let deletedAnalysisActivityKey;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ deleteAnalysisActivity: deletedAnalysisActivityKey } = responseData);\n } catch(error) {\n throw new Error(`Error deleting analysis activity ` +\n `\"${analysisActivityKey}\" from server`);\n }\n console.debug(`Deleted analysis activity \"${analysisActivityKey}\" ` +\n `from server`);\n\n // Update current activities in the index, if any\n const cacheKey = computeHash(`current-activities`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: currentActivitiesKeys = [] } = cacheData || {};\n const remainingCurrentActivitiesKeys = currentActivitiesKeys\n .filter(activityKey => activityKey !== deletedAnalysisActivityKey);\n if (currentActivitiesKeys.length > 0) {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: remainingCurrentActivitiesKeys\n }\n });\n }\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.deleteDocumentByKey({\n collectionName: \"activities\",\n documentKey: deletedAnalysisActivityKey\n }),\n this.indexService.deleteDocumentByKey({\n collectionName: \"activityItems\",\n documentKey: deletedAnalysisActivityKey\n })\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error deleting analysis activity ` +\n `\"${analysisActivityKey}\" from the index`);\n }\n console.debug(`Deleted analysis activity \"${analysisActivityKey}\" ` +\n `from the index`);\n\n // Return deleted analysis activity's key\n return analysisActivityKey;\n}\n","// Define the \"deleteTask\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateDeleteTaskActivityMutationData\n} from \"queries/activity\";\n\n\n// deleteTaskActivity: async\n// returns a promise which either resolves with the key of the task\n// activity that has been deleted from the server/index, or rejects with\n// an error\nexport async function deleteTaskActivity({\n taskActivityKey = \"\"\n}) {\n if (typeOf(taskActivityKey) !== \"string\" || !taskActivityKey) {\n throw new Error(`Parameter \"taskActivityKey\" should be an activity key`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Delete task activity on the server\n const queryData = generateDeleteTaskActivityMutationData({ taskActivityKey });\n let deletedTaskActivityKey;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ deleteTaskActivity: deletedTaskActivityKey } = responseData);\n } catch(error) {\n console.error(error);\n throw new Error(`Error deleting task activity \"${taskActivityKey}\" ` +\n `from server`);\n }\n console.debug(`Deleted task activity \"${taskActivityKey}\" from server`);\n\n // Update current activities in the index, if any\n const cacheKey = computeHash(`current-activities`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: currentActivitiesKeys = [] } = cacheData || {};\n const remainingCurrentActivitiesKeys = currentActivitiesKeys\n .filter(activityKey => activityKey !== deletedTaskActivityKey);\n if (currentActivitiesKeys.length > 0) {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: remainingCurrentActivitiesKeys\n }\n });\n }\n\n // Update the index\n try {\n await this.indexService.deleteDocumentByKey({\n collectionName: \"activityItems\",\n documentKey: deletedTaskActivityKey\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error deleting task activity \"${taskActivityKey}\" ` +\n `from index`);\n }\n console.debug(`Deleted task activity \"${taskActivityKey}\" from index`);\n\n // Return deleted task activity's data\n return taskActivityKey;\n}\n","// Define the \"deleteTaskAnalysisActivity\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateDeleteTaskAnalysisActivityMutationData\n} from \"queries/activity\";\n\n\n// deleteTaskAnalysisActivity: async\n// returns a promise which either resolves with the key of the analysis\n// activity that has been deleted from the server/index, or rejects with\n// an error\nexport async function deleteTaskAnalysisActivity({\n taskActivityKey = \"\",\n analysisActivityKey = \"\"\n}) {\n if (typeOf(taskActivityKey) !== \"string\" || !taskActivityKey) {\n throw new Error(`Parameter \"taskActivityKey\" should be an activity key`);\n } else if (typeOf(analysisActivityKey) !== \"string\" || !analysisActivityKey) {\n throw new Error(`Parameter \"analysisActivityKey\" should be an activity key`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Delete task analysis activity on server\n const queryData = generateDeleteTaskAnalysisActivityMutationData({\n taskActivityKey,\n analysisActivityKey\n });\n try {\n await this.graphQLService.query({ queryData });\n } catch(error) {\n console.error(error);\n throw new Error(`Error deleting analysis activity ${analysisActivityKey}\" ` +\n `\"from task activity \"${taskActivityKey}\" on server`);\n }\n console.debug(`Deleted analysis activity \"${analysisActivityKey}\" ` +\n `from task activity \"${taskActivityKey}\" on server`);\n\n // Get new task activity data\n const updatedTaskActivityData = await this.getActivityItemByKey({\n activityKey: taskActivityKey, \n fetchFromIndex: false \n });\n if (updatedTaskActivityData) {\n const {\n task: {\n key: taskKey = \"\",\n activities: analysisActivitiesData = []\n } = {}\n } = updatedTaskActivityData;\n const cacheKey = computeHash(`activities-by-task-${taskKey}`);\n const analysisActivitiesKeys = analysisActivitiesData\n .map(({ key: analysisActivityKey = \"\" }) => analysisActivityKey);\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: updatedTaskActivityData\n }),\n this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: analysisActivitiesKeys\n }\n })\n ]);\n }\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.deleteDocumentByKey({\n collectionName: \"activityItems\",\n documentKey: taskActivityKey\n }),\n this.indexService.deleteDocumentByKey({\n collectionName: \"activityItems\",\n documentKey: analysisActivityKey\n })\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error deleting analysis activity ` +\n `\"${analysisActivityKey}\" from task activity \"${taskActivityKey}\" ` +\n `on the index`);\n }\n console.debug(`Deleted analysis activity \"${analysisActivityKey}\" ` +\n `from task activity \"${taskActivityKey}\" on the index`);\n\n // Return deleted analysis activity's key\n return analysisActivityKey;\n}\n","// Define the \"getActivitiesByFilter\" service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateActivitiesByFilterQueryData } from \"queries/activity\";\n\n\n// getActivitiesByFilter: async\n// returns a promise which either resolves with an array of activity data\n// objects fetched from the index or the server, or rejects witn an error\nexport async function getActivitiesByFilter(filterData = {}) {\n if (typeOf(filterData) !== \"object\") {\n throw new Error(`Parameter \"filterData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Fetch activities from server\n const queryData = generateActivitiesByFilterQueryData({ filterData });\n let activitiesData = [];\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ activitiesByFilter: activitiesData = [] } = responseData || {});\n } catch(error) {\n console.warn(`Error fetching activities by filter from server`, error);\n return [];\n }\n const activitiesCount = activitiesData.length;\n console.debug(`Fetched ${activitiesCount} activities by filter from server`);\n if (activitiesCount === 0) {\n return [];\n }\n\n // Save activities to the index\n try {\n await Promise.all([\n this.indexService.saveDocuments({\n collectionName: \"activityItems\",\n documentsData: activitiesData\n })\n ]);\n console.debug(`Saved ${activitiesCount} activities to the index`);\n } catch(error) {\n console.warn(`Error saving ${activitiesCount} activities ` + \n `to the index`, error);\n }\n\n // Return activities data\n return activitiesData;\n}\n","// Define the \"getActivitiesByTask\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateActivitiesByTaskQueryData } from \"queries/activity\";\n\n\n// getActivitiesByTask: async\n// returns a promise which either resolves with an array of activities\n// data objects contained in the specified task (by key), or rejects with\n// an error\nexport async function getActivitiesByTask({\n taskKey = \"\"\n}) {\n if (typeOf(taskKey) !== \"string\" || !taskKey) {\n throw new Error(`Parameter \"taskKey\" should be a task key`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Get analysis activities from the index\n const cacheKey = computeHash(`activities-by-task-${taskKey}`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: indexedAnalysisActivitiesKeys = [] } = cacheData || {};\n const indexedAnalysisActivitiesCount = indexedAnalysisActivitiesKeys.length;\n if (indexedAnalysisActivitiesCount > 0) {\n const indexedAnalysisActivitiesData = await this.getActivityItemsByKeys({\n activitiesKeys: indexedAnalysisActivitiesKeys\n });\n if (indexedAnalysisActivitiesData.every(aaData => aaData !== null)) {\n console.debug(`Fetched ${indexedAnalysisActivitiesCount} ` +\n `analysis activities by task \"${taskKey}\" from the index`);\n return indexedAnalysisActivitiesData;\n }\n }\n\n // Get analysis activities from the server\n const queryData = generateActivitiesByTaskQueryData({ taskKey });\n let analysisActivitiesData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n const { taskByKey: taskData = null } = responseData || {};\n ({ activities: analysisActivitiesData = [] } = taskData || {});\n } catch(error) {\n console.warn(`Error fetching analysis activities by task \"${taskKey}\" ` +\n `from the server`, error);\n return [];\n }\n const analysisActivitiesCount = analysisActivitiesData.length;\n console.debug(`Fetched ${analysisActivitiesCount} analysis activities ` +\n `by task \"${taskKey}\" from the server`);\n if (analysisActivitiesCount === 0) {\n return [];\n }\n\n // Save analysis activities to the index\n const analysisActivitiesKeys = analysisActivitiesData\n .map(({ key: analysisActivityKey = \"\" }) => analysisActivityKey);\n try {\n await Promise.all([\n this.indexService.saveDocuments({\n collectionName: \"activityItems\",\n documentsData: analysisActivitiesData\n }),\n this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: analysisActivitiesKeys\n }\n })\n ]);\n console.debug(`Saved ${analysisActivitiesCount} analysis activities ` +\n `by task \"${taskKey}\" to the index`);\n } catch(error) {\n console.warn(`Error saving ${analysisActivitiesCount} analysis activities ` +\n `by task \"${taskKey}\" to the index`, error);\n }\n\n // Return analysis activities' data\n return analysisActivitiesData;\n}\n","// Define the \"getActivityByKey\" activity service method\n\n// Import utility modules\nimport { computeActivityType } from \"utilities/activity\";\n\n\n// getActivityByKey: async\n// returns a promise which either resolves with the activity data\n// corresponding to the specified key, or rejects with a error\nexport async function getActivityByKey({ \n activityKey = \"\",\n indexCollectionName = \"\",\n fetchFromIndex = true,\n saveToIndex = true\n}) {\n const activityData = await this.entityService.getEntityByKey({\n entityName: \"activity\",\n entityKey: activityKey || \"\",\n indexCollectionName,\n fetchFromIndex,\n saveToIndex\n });\n const activityType = computeActivityType(activityData);\n\n if ([\"analysis\"].includes(activityType)) {\n // Adjust cards' lemmas/inInflectors/definitions fields by ensuring\n // that they have the same lengths of the corresponding inChildElements\n // field, if any\n const { analysis: analysisData = null } = activityData || {};\n const { cards: cardsData = [] } = analysisData || {};\n const finalCardsData = cardsData.map(loadedCardData => {\n const { \n inElement: inElementData = null,\n lemmas: lemmasData = [],\n inInflectors: inInflectorsData = [],\n definitions: definitionsData = []\n } = loadedCardData || {};\n const { childElements: inChildElementsData = [] } = inElementData || {};\n const inChildElementsCount = inChildElementsData.length;\n const lemmasCount = lemmasData.length;\n const inInflectorsCount = inInflectorsData.length;\n const definitionsCount = definitionsData.length;\n return inChildElementsCount === 0 ? loadedCardData : {\n ...loadedCardData,\n lemmas: lemmasCount === inChildElementsCount ? \n lemmasData : inChildElementsData.map(() => null),\n inInflectors: inInflectorsCount === inChildElementsCount ? \n inInflectorsData : inChildElementsData.map(() => null),\n definitions: definitionsCount === inChildElementsCount ? \n definitionsData : inChildElementsData.map(() => null)\n };\n });\n return {\n ...activityData,\n analysis: {\n ... analysisData,\n cards: finalCardsData\n }\n };\n } else {\n return activityData;\n }\n\n}\n","// Define the \"getActivityItemsByFilter\" service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateActivityItemsByFilterQueryData } from \"queries/activity\";\n\n\n// getActivityItemsByFilter: async\n// returns a promise which either resolves with an array of activity item\n// objects fetched from the index or the server, or rejects witn an error\nexport async function getActivityItemsByFilter(filterData = {}) {\n if (typeOf(filterData) !== \"object\") {\n throw new Error(`Parameter \"filterData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Fetch activity items from server\n const queryData = generateActivityItemsByFilterQueryData({ filterData });\n let activitiesData = [];\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ activitiesByFilter: activitiesData = [] } = responseData || {});\n } catch(error) {\n console.warn(`Error fetching activity items by filter from server`, error);\n return [];\n }\n const activitiesCount = activitiesData.length;\n console.debug(`Fetched ${activitiesCount} activity items by filter ` +\n `from server`);\n if (activitiesCount === 0) {\n return [];\n }\n\n // Save activity items to the index\n try {\n await Promise.all([\n this.indexService.saveDocuments({\n collectionName: \"activityItems\",\n documentsData: activitiesData\n })\n ]);\n console.debug(`Saved ${activitiesCount} activity items to the index`);\n } catch(error) {\n console.warn(`Error saving ${activitiesCount} activity items ` + \n `to the index`, error);\n }\n\n // Return activity items data\n return activitiesData;\n}\n","// Define the \"getCurrentActivities\" activity service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { sortActivities } from \"utilities/activity\";\n\n// Import parameter modules\nimport { activities as defaultActivitiesParams } from \"parameters/default\";\n\n// Define internal parameters\nconst {\n currentFetchCount: currentActivitiesFetchCount\n} = defaultActivitiesParams;\n\n\n// getCurrentActivities: async\n// returns a promise which resolves with an (possibly empty) array of\n// current activities' data, either cached locally or fetched from the\n// index/server\nexport async function getCurrentActivities() {\n if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n } else if (!this.getActivityItemsByKeys) {\n throw new Error(`Missing required \"getActivityItemsByKeys\" method`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Fetch current activities from the index\n const cacheKey = computeHash(`current-activities`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: indexedCurrentActivitiesKeys = [] } = cacheData || {};\n if (indexedCurrentActivitiesKeys.length > 0) {\n const indexedCurrentActivitiesData = await this.getActivityItemsByKeys({\n activitiesKeys: indexedCurrentActivitiesKeys\n });\n const allActivitiesDataAvailable = indexedCurrentActivitiesData\n .every(currentActivityData => currentActivityData !== null);\n if (allActivitiesDataAvailable) {\n const currentActivitiesCount = indexedCurrentActivitiesData.length;\n console.debug(`Fetched ${currentActivitiesCount} current ` +\n `activities from index`);\n return sortActivities({\n activitiesData: indexedCurrentActivitiesData,\n ascendingIndex: this.sortActivitiesByAscendingIndex\n });\n }\n }\n\n // Fetch current activities from the server\n let currentActivitiesData;\n try {\n currentActivitiesData = await this.getActivityItemsByFilter({\n direction: this.sortActivitiesByAscendingIndex ? \"dsc\": \"asc\",\n count: currentActivitiesFetchCount\n });\n } catch(error) {\n console.warn(`Error fetching ${currentActivitiesFetchCount} ` +\n `current activities from the server`, error);\n return [];\n }\n const currentActivitiesCount = currentActivitiesData.length;\n if (currentActivitiesCount > 0) {\n console.debug(`Fetched ${currentActivitiesCount} current ` +\n `activities from the server`);\n } else {\n console.warn(`Could not fetch any current activity from the server`);\n }\n\n // Sort activities by index\n currentActivitiesData = sortActivities({\n activitiesData: currentActivitiesData,\n ascendingIndex: this.sortActivitiesByAscendingIndex\n });\n\n // Save current activities to the index\n const currentActivitiesKeys = currentActivitiesData\n .map(({ key: activityKey = \"\" }) => activityKey);\n try {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: currentActivitiesKeys\n }\n });\n } catch(error) {\n console.warn(`Error saving current activities to the index`, error);\n }\n\n // Return current activities' data\n return currentActivitiesData;\n}\n","// Define the \"getLastOpenedActivityId\" activity service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\n\n\n// getLastOpenedActivityId: async \n// returns a promise which resolves with an object containing the meta-data\n// for the specified task activity\n// note: this piece of information is stored in the index and not on\n// the server\nexport async function getLastOpenedActivityId() {\n if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n }\n\n // Fetch task activity meta-data from the index\n const cacheKey = computeHash(`last-opened-activity`);\n try {\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"activities\",\n documentKey: cacheKey\n });\n const { value: lastOpenedActivityId = \"\" } = cacheData || {};\n return lastOpenedActivityId;\n } catch(error) {\n console.error(error);\n console.warn(`Error getting last opened activity id from the index`, error);\n return \"\";\n }\n}\n","// Define the \"getPreviousActivities\" activity service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { sortActivities } from \"utilities/activity\";\n\n// Import parameter modules\nimport { activities as defaultActivitiesParams } from \"parameters/default\";\n\n// Define internal parameters\nconst {\n previousFetchCount: previousActivitiesFetchCount\n} = defaultActivitiesParams;\n\n\n// getPreviousActivities: async\n// returns a promise which resolves with an (possibly empty) array of\n// previous activities' data, either cached locally or fetched from the\n// index or the server\nexport async function getPreviousActivities() {\n if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Fetch current activities from the index\n const currentActivitiesData = await this.getCurrentActivities();\n const currentActivitiesCount = currentActivitiesData.length;\n\n // Fetch previous activities from the server\n let previousActivitiesData;\n try {\n previousActivitiesData = await this.getActivityItemsByFilter({\n direction: this.sortActivitiesByAscendingIndex ? \"dsc\" : \"asc\",\n skip: currentActivitiesCount,\n count: previousActivitiesFetchCount\n });\n } catch(error) {\n console.warn(`Error fetching ${previousActivitiesFetchCount} ` +\n `previous activities from the server: returning an empty array`, error);\n return [];\n }\n const previousActivitiesCount = previousActivitiesData.length;\n if (previousActivitiesCount > 0) {\n console.debug(`Fetched ${previousActivitiesCount} previous ` +\n `activities from the server`);\n } else {\n console.debug(`Could not fetch any previous activity from the server`);\n }\n\n // Save updated current activities to the index\n const activitiesData = sortActivities({\n activitiesData: [ ...previousActivitiesData, ...currentActivitiesData ],\n ascendingIndex: this.sortActivitiesByAscendingIndex\n });\n const cacheKey = computeHash(`current-activities`);\n const activitiesKeys = activitiesData\n .map(({ key: activityKey = \"\" }) => activityKey);\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: activitiesKeys\n }\n })\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error saving updated current activities to the index`);\n }\n\n // Return previous activities' data\n return {\n previousActivitiesData,\n activitiesData\n };\n}\n","// Define the \"getRecentActivities\" activity service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n\n// getRecentActivities: async\n// returns a promise which resolves with an (possibly empty) array of\n// recent activities' data, either cached locally or fetched from the\n// server\nexport async function getRecentActivities({\n maxActivitiesCount = 3\n} = {}) {\n if (typeOf(maxActivitiesCount) !== \"number\" || maxActivitiesCount <= 0) {\n throw new Error(`Parameter \"maxActivitiesCount\" should be ` +\n `a positive integer`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Fetch recent activities from the server\n let currentActivitiesData;\n try {\n currentActivitiesData = await this.getCurrentActivities();\n } catch(error) {\n currentActivitiesData = [];\n }\n\n // Sort activities by descending modification time-stamp\n const recentActivitiesData = currentActivitiesData\n .sort((\n { modifiedOn: activity1ModifiedOn = 0 },\n { modifiedOn: activity2ModifiedOn = 0 }\n ) => {\n return activity2ModifiedOn - activity1ModifiedOn\n })\n .slice(0, maxActivitiesCount);\n const recentActivitiesCount = recentActivitiesData.length;\n console.debug(`Fetched ${recentActivitiesCount} recent activities ` +\n `from the server`);\n\n // Return recent activities' data\n return recentActivitiesData;\n}\n","// Define the \"getTaskActivityMetaDataByKey\" activity service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { \n taskActivityMetaData as defaultTaskActivityMetaData \n} from \"parameters/activity\"\n\n\n// getTaskActivityMetaDataByKey: async \n// returns a promise which resolves with an object containing the meta-data\n// for the specified task activity\n// note: this piece of information is stored in the index and not on\n// the server\nexport async function getTaskActivityMetaDataByKey({\n taskActivityKey = \"\"\n}) {\n if (typeOf(taskActivityKey) !== \"string\" || !taskActivityKey) {\n throw new Error(`Parameter \"taskActivityKey\" should be a task key`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n }\n\n // Fetch task activity meta-data from the index\n const cacheKey = computeHash(`meta-data-by-task-activity-${taskActivityKey}`);\n try {\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"activities\",\n documentKey: cacheKey\n });\n const { \n value: taskActivityMetaData = defaultTaskActivityMetaData \n } = cacheData || {};\n return taskActivityMetaData;\n } catch(error) {\n console.error(error);\n console.warn(`Error getting meta-data for task activity ` +\n `\"${taskActivityKey}\" from the index`, error);\n return defaultTaskActivityMetaData;\n }\n}\n","// Define the \"reuseActivity\" service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateReuseActivityMutationData } from \"queries/activity\";\n\n\n// reuseActivity: async\n// returns a promise which either resolves with the activity data object\n// whose index has been updated on the server/index, or rejects with an\n// error\nexport async function reuseActivity({\n activityKey = \"\",\n nextActivityKey = \"\" // optional\n}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be an activity key`);\n } else if (typeOf(nextActivityKey) !== \"string\") {\n throw new Error(`Parameter \"nextActivityKey\" should be a string`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Reuse activity on server\n const nextActivityIndexString = nextActivityKey ?\n `before activity \"${nextActivityKey}\"` : `at the end of activities`;\n const queryData = generateReuseActivityMutationData({\n activityKey,\n nextActivityKey\n });\n let updatedActivityData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ reuseActivity: updatedActivityData } = responseData);\n } catch(error) {\n throw new Error(`Error re-using activity \"${activityKey}\" ` +\n `${nextActivityIndexString} on the server`);\n }\n console.debug(`Re-used activity \"${activityKey}\" ` +\n `${nextActivityIndexString} on the server`);\n\n // Reuse activity on the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: updatedActivityData\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error re-using activity \"${activityKey}\" ` +\n `${nextActivityIndexString} on the index`);\n }\n console.debug(`Re-used activity \"${activityKey}\" ` +\n `${nextActivityIndexString} on the index`);\n\n // Return updated activity data\n return updatedActivityData;\n}\n","// Define the \"setLastOpenedActivityId\" activity service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n\n// setLastOpenedActivityId: async\n// returns a promise which either resolves after the meta-data for the \n// specified task activity has been saved on the activities collection \n// of the index, or rejects with an error\nexport async function setLastOpenedActivityId({\n lastOpenedActivityId = \"\"\n}) {\n if (typeOf(lastOpenedActivityId) !== \"string\" || !lastOpenedActivityId) {\n throw new Error(`Parameter \"lastOpenedActivityId\" should be ` +\n `a non-empty string`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n }\n\n // Save last opened activity to the index\n const cacheKey = computeHash(`last-opened-activity`);\n try {\n this.indexService.saveDocument({\n collectionName: \"activities\",\n documentData: {\n key: cacheKey,\n value: lastOpenedActivityId\n }\n });\n } catch(error) {\n console.warn(`Error saving last opened activity id to the index`);\n }\n console.debug(`Saved last opened activity id to the index`);\n}\n","// Define the \"setTaskActivityMetaDataByKey\" activity service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n\n// setTaskActivityMetaDataByKey: async\n// returns a promise which either resolves after the meta-data for the \n// specified task activity has been saved on the activities collection \n// of the index, or rejects with an error\nexport async function setTaskActivityMetaDataByKey({\n taskActivityKey = \"\",\n taskActivityMetaData = null\n}) {\n if (typeOf(taskActivityKey) !== \"string\" || !taskActivityKey) {\n throw new Error(`Parameter \"taskActivityKey\" should be a task activity key`);\n } else if (typeOf(taskActivityMetaData) !== \"object\") {\n throw new Error(`Parameter \"taskActivityMetaData\" should be an object`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n }\n\n // Save task activity meta-data to the index\n const cacheKey = computeHash(`meta-data-by-task-activity-${taskActivityKey}`);\n try {\n this.indexService.saveDocument({\n collectionName: \"activities\",\n documentData: {\n key: cacheKey,\n value: taskActivityMetaData\n }\n });\n } catch(error) {\n console.warn(`Error saving task activity meta-data to the index`);\n }\n console.debug(`Saved task activity meta-data to the index`);\n}\n","// Define the \"unuseActivity\" service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateUnuseActivityMutationData } from \"queries/activity\";\n\n\n// unuseActivity: async\n// returns a promise which either resolves with the activity data after it\n// has been un-used from the array of activities, or rejects with an error\nexport async function unuseActivity({\n activityKey = \"\"\n}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be an activity key`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Use activity on the server\n const queryData = generateUnuseActivityMutationData({\n activityKey\n });\n let unusedActivityKey;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ unuseActivity: unusedActivityKey } = responseData);\n } catch(error) {\n throw new Error(`Error un-using activity \"${activityKey}\" on the server`);\n }\n console.debug(`Un-used activity \"${unusedActivityKey}\" on the server`);\n\n // Unuse activity on the index\n try {\n await this.indexService.deleteDocumentByKey({\n collectionName: \"activityItems\",\n documentKey: unusedActivityKey\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error un-using activity \"${unusedActivityKey}\" ` +\n `on the index`);\n }\n console.debug(`Un-used activity \"${unusedActivityKey}\" on the index`);\n\n // Return un-used activity key\n return unusedActivityKey;\n}\n","// Define the \"updateAnalysisActivity\" service method\n\n// Import utility modules\nimport { computeAnalysisActivityItem } from \"utilities/activity\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateUpdateAnalysisActivityMutationData } from \"queries/activity\";\n\n\n// updateAnalysisActivity: async\n// returns a promise which either resolves with the analysis activity data\n// object updated on the server/index, or rejects with an error\nexport async function updateAnalysisActivity({\n analysisActivityKey = \"\",\n updateAnalysisActivityData = null\n}) {\n if (typeOf(analysisActivityKey) !== \"string\" || !analysisActivityKey) {\n throw new Error(`Parameter \"analysisActivityKey\" should be an activity key`);\n } else if (typeOf(updateAnalysisActivityData) !== \"object\") {\n throw new Error(`Parameter \"updateAnalysisActivityData\" should be ` +\n `an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Update analysis on server\n const queryData = generateUpdateAnalysisActivityMutationData({\n analysisActivityKey,\n updateAnalysisActivityData\n });\n let updatedAnalysisActivityData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ updateAnalysisActivity: updatedAnalysisActivityData } = responseData);\n } catch(error) {\n throw new Error(`Error updating analysis activity ` +\n `\"${analysisActivityKey}\" on the server`);\n }\n console.debug(`Updated analysis activity \"${analysisActivityKey}\" ` +\n `on the server`);\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: computeAnalysisActivityItem({\n analysisActivityData: updatedAnalysisActivityData\n })\n }),\n this.indexService.saveDocument({\n collectionName: \"activities\",\n documentData: updatedAnalysisActivityData\n })\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error updating analysis activity ` +\n `\"${analysisActivityKey}\" on the index`);\n }\n console.debug(`Updated analysis activity \"${analysisActivityKey}\" ` +\n `on the index`);\n\n // Return updated analysis activity's data\n return updatedAnalysisActivityData;\n}\n","// Define the \"updateTaskActivity\" service method\n\n// Import query modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateUpdateTaskActivityMutationData\n} from \"queries/activity\";\n\n\n// updateTaskActivity: async\n// returns a promise which either resolves with the task activity data\n// object updated on the server/index, or rejects with an error\nexport async function updateTaskActivity({\n taskActivityKey = \"\",\n updateTaskActivityData = null\n}) {\n if (typeOf(taskActivityKey) !== \"string\" || !taskActivityKey) {\n throw new Error(`Parameter \"taskActivityKey\" should be an activity key`);\n } else if (typeOf(updateTaskActivityData) !== \"object\") {\n throw new Error(`Parameter \"updateTaskActivityData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Update task activity on server\n const queryData = generateUpdateTaskActivityMutationData({\n taskActivityKey,\n updateTaskActivityData\n });\n let updatedTaskActivityData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ updateTaskActivity: updatedTaskActivityData } = responseData);\n } catch(error) {\n throw new Error(`Error updating task activity \"${taskActivityKey}\"`);\n }\n console.debug(`Updated task activity \"${taskActivityKey}\" on server`);\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: updatedTaskActivityData\n }),\n this.indexService.saveDocument({\n collectionName: \"activities\",\n documentData: updatedTaskActivityData\n })\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error updating task activity on index`);\n }\n console.debug(`Updated task activity \"${taskActivityKey}\" on index`);\n\n // Return updated task activity's data\n return updatedTaskActivityData;\n}\n","// Define the \"updateTaskAnalysisActivity\" service method\n\n// Import query modules\nimport { computeAnalysisActivityItem } from \"utilities/activity\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateUpdateTaskAnalysisActivityMutationData\n} from \"queries/activity\";\n\n\n// updateTaskAnalysisActivity: async\n// returns a promise which either resolves with the task analysis activity\n// data updated on the server/index, or rejects with an error\nexport async function updateTaskAnalysisActivity({\n taskActivityKey = \"\",\n analysisActivityKey = \"\",\n updateAnalysisActivityData = null\n}) {\n if (typeOf(taskActivityKey) !== \"string\" || !taskActivityKey) {\n throw new Error(`Parameter \"taskActivityKey\" should be an activity key`);\n } else if (typeOf(analysisActivityKey) !== \"string\" || !analysisActivityKey) {\n throw new Error(`Parameter \"analysisActivityKey\" should be an activity key`);\n } else if (typeOf(updateAnalysisActivityData) !== \"object\") {\n throw new Error(`Parameter \"updateAnalysisActivityData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Update task analysis activity on server\n const queryData = generateUpdateTaskAnalysisActivityMutationData({\n taskActivityKey,\n analysisActivityKey,\n updateAnalysisActivityData\n });\n let updatedTaskAnalysisActivityData;\n try {\n ({\n updateTaskAnalysisActivity: updatedTaskAnalysisActivityData\n } = await this.graphQLService.query({ queryData }));\n } catch(error) {\n throw new Error(`Error updating ` +\n `analysis activity \"${analysisActivityKey}\" ` +\n `in task activity \"${taskActivityKey}\" on server`);\n }\n console.debug(`Updated analysis activity \"${analysisActivityKey}\" ` +\n `in task activity \"${taskActivityKey}\" on server`);\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: computeAnalysisActivityItem({\n analysisActivityData: updatedTaskAnalysisActivityData\n })\n }),\n this.indexService.saveDocument({\n collectionName: \"activities\",\n documentData: updatedTaskAnalysisActivityData\n })\n ]);\n } catch(error) {\n throw new Error(`Error updating ` +\n `analysis activity \"${analysisActivityKey}\" ` +\n `in task activity \"${taskActivityKey}\" on index`);\n }\n console.debug(`Updated analysis activity \"${analysisActivityKey}\" ` +\n `in task activity \"${taskActivityKey}\" on index`);\n\n // Return updated task analysis activity's data\n return updatedTaskAnalysisActivityData;\n}\n","// Define the \"useActivity\" service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateUseActivityMutationData } from \"queries/activity\";\n\n\n// useActivity: async\n// returns a promise which either resolves with the activity data after it\n// has been used before the specified nextActivity (by key), or rejects\n// with an error\nexport async function useActivity({\n activityKey = \"\",\n nextActivityKey = \"\" // optional\n}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be an activity key`);\n } else if (typeOf(nextActivityKey) !== \"string\") {\n throw new Error(`Parameter \"nextActivityKey\" should be a string`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Use activity on server\n const nextActivityIndexString = nextActivityKey ?\n `before activity \"${nextActivityKey}\"` : `at the end of activities`;\n const queryData = generateUseActivityMutationData({\n activityKey,\n nextActivityKey\n });\n let updatedActivityData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ useActivity: updatedActivityData } = responseData);\n } catch(error) {\n throw new Error(`Error using activity \"${activityKey}\" ` +\n `${nextActivityIndexString} on the server`);\n }\n console.debug(`Used activity \"${activityKey}\" ` +\n `${nextActivityIndexString} on the server`);\n\n // Use activity on the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"activityItems\",\n documentData: updatedActivityData\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error using activity \"${activityKey}\" ` +\n `${nextActivityIndexString} on the index`);\n }\n console.debug(`Used activity \"${activityKey}\" ` +\n `${nextActivityIndexString} on the index`);\n\n // Return updated activity data\n return updatedActivityData;\n}\n","// Define the \"api\" service\n\n// Import class modules\nimport CircularArray from \"classes/circular-array\";\n\n// Import parameter modules\nimport { responseTimeAverageWindowLength } from \"parameters/api\";\n\n// Import method modules\nimport { isAlive } from \"services/api/is-alive\";\nimport { fetch } from \"services/api/fetch\";\n\n\n// Export the \"APIService\" class\nexport default class APIService {\n\n // Attributes\n _responseTimesMs = new CircularArray(responseTimeAverageWindowLength, 0);\n\n constructor() {\n if (window.fetch) {\n console.debug(`User agent supports the fetch API`);\n } else {\n throw new Error(`User agent does not support the fetch API: ` +\n `please update your browser`);\n }\n }\n\n\n // Getter and setter methods\n get responseTimesMs() {\n return this._responseTimesMs.elements\n .filter(rttMs => rttMs > 0);\n }\n\n get averageResponseTimeMs() {\n if (this.responseTimesMs.length > 0) {\n const responseTimesSumMs = this.responseTimesMs\n .reduce((accRTTsSumMs, rttMs) => accRTTsSumMs + rttMs, 0);\n return Math.round(responseTimesSumMs/this.responseTimesMs.length);\n }\n return 0;\n }\n\n\n // Core methods\n async isAlive() {\n return await isAlive.call(this);\n }\n\n async fetch(fetchParams) {\n return await fetch.call(this, fetchParams);\n }\n\n}\n","// Define the \"fetch\" api service method\n// notes: directly wraps the window.fetch method\n\n// Import utility modules\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { toString } from \"utilities/object\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { runMode } from \"parameters/environment\";\nimport {\n baseEndpoint as apiBaseEndpoint,\n fetchOptions as defaultOptions\n} from \"parameters/api\";\n\n// Define internal parameters\nconst leadingSlashesRegExp = /^\\/+/;\n\n\n// fetch: async\n// returns a promise which either resolves with the data returned by the\n// specified path (relative to the api base endpoint) or rejects with an\n// error\nexport async function fetch({\n method = defaultOptions.method,\n path = \"\", \n body = null,\n headers = {},\n mode = defaultOptions.mode,\n credentials = defaultOptions.credentials,\n cache = defaultOptions.cache,\n redirect = defaultOptions.redirect\n}) {\n if (typeOf(method) !== \"string\" || !method) {\n throw new Error(`Parameter \"method\" should be a non-empty string`);\n } else if (typeOf(path) !== \"string\" || !path) {\n throw new Error(`Parameter \"path\" should be a non-empty string`);\n } else if (![\"object\", \"null\"].includes(typeOf(body))) {\n throw new Error(`Parameter \"body\" should be an object or null`);\n } else if (typeOf(headers) !== \"object\") {\n throw new Error(`Parameter \"headers\" should be an object`);\n } else if (typeOf(mode) !== \"string\" || !mode) {\n throw new Error(`Parameter \"mode\" should be a non-empty string`);\n } else if (typeOf(credentials) !== \"string\" || !credentials) {\n throw new Error(`Parameter \"credentials\" should be a non-empty string`);\n } else if (typeOf(cache) !== \"string\" || !cache) {\n throw new Error(`Parameter \"cache\" should be a non-empty string`);\n } else if (typeOf(redirect) !== \"string\" || !redirect) {\n throw new Error(`Parameter \"redirect\" should be a non-empty string`);\n } else if (!window.navigator.onLine) {\n throw new Error(`No network connection`);\n }\n\n // Verify browser's online status\n const isOnline = window.navigator.onLine;\n if (!isOnline) {\n throw new Error(`Cannot fetch from the \"api\" service: ` +\n `browser is currently off-line`);\n }\n\n const finalPath = path.replace(leadingSlashesRegExp, \"\");\n const finalUrl = `${apiBaseEndpoint}/${finalPath}`;\n const finalOptions = {\n method,\n ...(body ? { body: toString(body, { multiline: false }) } : {}),\n headers: { ...defaultOptions.headers, ...headers },\n mode,\n credentials,\n cache,\n redirect\n };\n\n let response, responseTimeMs;\n try {\n const txTimeStampMs = getCurrentTimeStampMs();\n response = await window.fetch(finalUrl, finalOptions);\n const rxTimeStampMs = getCurrentTimeStampMs();\n responseTimeMs = rxTimeStampMs - txTimeStampMs;\n } catch(error) { // network errors (only) handled here\n throw new Error(`Network error while fetching from the \"api\" service`);\n }\n this._responseTimesMs.element = responseTimeMs;\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n console.debug(`api current/average response time ` +\n `${responseTimeMs}ms/${this.averageResponseTimeMs}ms ` +\n `(${this.responseTimesMs.length} samples)`);\n }\n if (!response.ok) { // non 2xx responses handled here\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n console.warn(\"error response\", response);\n }\n throw new Error(`Error while fetching from the \"api\" service`);\n }\n\n // Return parsed response data\n return response.json(); // 2xx responses handled here\n}\n","// Define the \"isAlive\" api service method\n\n// Import parameter modules\nimport { alivePath as apiAlivePath } from \"parameters/api\";\nimport { runMode } from \"parameters/environment\";\n\n\n// isAlive: async\n// returns a promise which resolves with the current liveness status of\n// the api service by sending a request to its /alive end-point\nexport async function isAlive() {\n let responseData;\n try {\n responseData = await this.fetch({\n path: apiAlivePath\n });\n } catch(error) {\n return false;\n }\n const {\n alive: isAlive = false,\n on: serviceAliveOnMs\n } = responseData || {};\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n const serviceAliveDate = new Date(serviceAliveOnMs);\n const serviceAliveDateString = serviceAliveDate.toDateString();\n if (isAlive) {\n console.debug(`api service tested alive on ${serviceAliveDateString}`);\n } else {\n console.warn(`api service tested dead on ${serviceAliveDateString}`);\n }\n }\n return isAlive;\n}\n","// Define the \"application\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport IndexService from \"services/index\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { getRunMode } from \"./application/get-run-mode\";\nimport { setRunMode } from \"./application/set-run-mode\";\n\n\n// Export the \"ApplicationService\" class\n@inject(\n IndexService,\n UserService\n)\nexport default class ApplicationService {\n\n constructor(\n indexService,\n userService\n ) {\n this.indexService = indexService;\n this.userService = userService;\n }\n\n async getRunMode() {\n return await getRunMode.call(this);\n }\n\n async setRunMode(runMode) {\n return await setRunMode.call(this, runMode);\n }\n\n}\n","// Define the \"getRunMode\" application service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\n\n// Import parameter modules\nimport { runMode as defaultRunMode } from \"parameters/environment\";\n\n// Define internal parameters\nconst cacheKey = computeHash(`run-mode`);\n\n\n// getRunMode: async\n// seturns a promise which either resolves with the run-mode saved into\n// the keyvalue collection of the index, or rejects with an error\nexport async function getRunMode() {\n if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Fetch current runMode from the index\n try {\n const cacheData = await this.indexService.getDocumentByKey({\n databaseName: \"global\",\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: runMode = defaultRunMode } = cacheData || {};\n return runMode;\n } catch(error) {\n console.error(error);\n console.warn(`Error getting run-mode from the index: ` +\n `returning default value \"${defaultRunMode}\"`, error);\n return defaultRunMode;\n }\n}\n","// Define the \"setRunMode\" application service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\n\n// Import parameter modules\nimport { runModes } from \"parameters/application\";\n\n\n// setRunMode: async\n// returns a promise which either resolves after the specified run-mode\n// has been set on the keyvalues collection of the index, or rejects with\n// an error\nexport async function setRunMode(runMode) {\n if (!runModes.includes(runMode)) {\n throw new Error(`Parameter \"runMode\" should be one of [${runModes}]`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n }\n\n const currentRunMode = await this.getRunMode();\n\n if (runMode === currentRunMode) {\n return;\n }\n\n // Save updated run-mode to the index\n const cacheKey = computeHash(`run-mode`);\n try {\n this.indexService.saveDocument({\n databaseName: \"global\",\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: runMode\n }\n });\n } catch(error) {\n console.warn(`Error saving updated run-mode \"${runMode}\" to the index`);\n }\n console.debug(`Saved updated run-mode \"${runMode}\" to the index`);\n}\n","// Define the \"card\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport ElementService from \"services/element\";\nimport EntityService from \"services/entity\";\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\nimport SentenceService from \"services/sentence\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { createCard } from \"./card/create-card\";\n\n\n// Export the \"CardService\" class\n@inject(\n ElementService,\n EntityService,\n GraphQLService,\n IndexService,\n SentenceService,\n UserService\n)\nexport default class CardService {\n\n constructor(\n elementService,\n entityService,\n graphQLService,\n indexService,\n sentenceService,\n userService\n ) {\n this.elementService = elementService;\n this.entityService = entityService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.sentenceService = sentenceService;\n this.userService = userService;\n }\n\n async getCardByKey(cardKey) {\n return await this.entityService.getEntityByKey({\n entityName: \"card\",\n entityKey: cardKey || \"\"\n });\n }\n\n async getCardsByKeys(cardsKeys) {\n return await this.entityService.getEntitiesByKeys({\n entityName: \"card\",\n entitiesKeys: cardsKeys || []\n });\n }\n\n async createCard(cardData) {\n return await createCard.call(this, cardData);\n }\n\n async saveCard(cardData) {\n return await this.createCard(cardData);\n }\n\n}\n","// Define the \"createCard\" service method\n\n// Import utility modules\nimport { computeCreateCardData } from \"utilities/card\";\n\n// Import query modules\nimport { generateCreateCardMutationData } from \"queries/card\";\n\n\n// createCard: async\n// returns a promise which either resolves with the card data object\n// created on the server/index, or rejects with an error\nexport async function createCard(cardData) {\n if (!cardData) {\n throw new Error(`Missing required parameter \"cardData\"`);\n }\n\n // Create card on server\n const createCardData = computeCreateCardData(cardData);\n const queryData = generateCreateCardMutationData({ createCardData });\n let createdCardData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ createCard: createdCardData } = responseData);\n } catch(error) {\n throw new Error(`Error creating card on server`);\n }\n const { key: createdCardKey } = createdCardData;\n console.debug(`Created card \"${createdCardKey}\" on server`);\n\n // Update the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"cards\",\n documentData: createdCardData\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating card \"${createdCardKey}\" on index`);\n }\n console.debug(`Created card \"${createdCardKey}\" on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Return created card's data\n return createdCardData;\n}\n","// Define the \"definition\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport EntityService from \"services/entity\";\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { getDefinitionsByLemma } from \"./definition/get-definitions-by-lemma\";\nimport { createDefinition } from \"./definition/create-definition\";\nimport { reportDefinition } from \"./definition/report-definition\";\nimport { reportLemmaDefinition } from \"./definition/report-lemma-definition\";\nimport { unreportDefinition } from \"./definition/unreport-definition\";\nimport { unreportLemmaDefinition } from \"./definition/unreport-lemma-definition\";\n\n\n// Export the \"DefinitionService\" class\n@inject(\n EntityService,\n GraphQLService, \n IndexService, \n UserService\n)\nexport default class DefinitionService {\n\n constructor(\n entityService,\n graphQLService, \n indexService, \n userService\n ) {\n this.entityService = entityService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.userService = userService;\n }\n\n async getDefinitionByKey(definitionKey) {\n return await this.entityService.getEntityByKey({\n entityName: \"definition\",\n entityKey: definitionKey || \"\"\n });\n }\n\n async getDefinitionsByKeys(definitionsKeys) {\n return await this.entityService.getEntitiesByKeys({\n entityName: \"definition\",\n entitiesKeys: definitionsKeys || []\n });\n }\n\n async getDefinitionsByLemma({ lemmaKey, localeCode }) {\n return await getDefinitionsByLemma.call(this, { lemmaKey, localeCode });\n }\n\n async createDefinition(createDefinitionData) {\n return await createDefinition.call(this, createDefinitionData);\n }\n\n async reportDefinition({\n definitionKey = \"\",\n lemmaKey = \"\",\n reportData = null\n }) {\n return await reportDefinition.call(this, {\n definitionKey,\n lemmaKey,\n reportData\n });\n }\n async reportLemmaDefinition({\n definitionKey = \"\",\n lemmaKey = \"\",\n reportData = null\n }) {\n return await reportLemmaDefinition.call(this, {\n definitionKey,\n lemmaKey,\n reportData\n });\n }\n\n async unreportDefinition({\n definitionKey = \"\",\n lemmaKey = \"\"\n }) {\n return await unreportDefinition.call(this, {\n definitionKey,\n lemmaKey\n });\n }\n async unreportLemmaDefinition({\n definitionKey = \"\",\n lemmaKey = \"\"\n }) {\n return await unreportLemmaDefinition.call(this, {\n definitionKey,\n lemmaKey\n });\n }\n\n}\n","// Define the \"createDefinition\" service method\n\n// Import utility modules\nimport { computeCreateDefinitionData } from \"utilities/definition\";\nimport { cyrb53 as computeHash } from \"utilities/hash\";\n\n// Import query modules\nimport { generateCreateDefinitionMutationData } from \"queries/definition\";\n\n\n// createDefinition: async\n// returns a promise which either resolves with the definition data object\n// created on the server/index, or rejects with an error\nexport async function createDefinition(createDefinitionData) {\n if (!this.userService.isSessionOpen) {\n throw new Error(`Could not create definition on server (no open session)`);\n }\n\n createDefinitionData = computeCreateDefinitionData(createDefinitionData);\n const queryData =\n generateCreateDefinitionMutationData({ createDefinitionData });\n let createdDefinitionData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ createDefinition: createdDefinitionData } = responseData);\n } catch(error) {\n throw new Error(`Error creating definition on server`);\n }\n const { key: createdDefinitionKey } = createdDefinitionData;\n console.debug(`Created definition \"${createdDefinitionKey}\" on server`);\n\n // Update the index\n const { lemmaKey } = createDefinitionData;\n const cacheKey = computeHash(`definitions-by-lemma-${lemmaKey}`);\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"definitions\",\n documentData: createdDefinitionData\n }),\n this.indexService.deleteDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n }),\n ]);\n } catch(error) {\n throw new Error(`Error creating definition \"${createdDefinitionKey}\" ` +\n `on index`);\n }\n console.debug(`Created definition \"${createdDefinitionKey}\" on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Return created definition's data\n return createdDefinitionData;\n}\n","// Define the \"getDefinitionsByLemma\" service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateDefinitionsByLemmaQueryData } from \"queries/definition\";\n\n\n// getDefinitionsByLemma: async\n// Returns a promise which resolves with an (possibly empty) array of\n// definitions admitted by the specified lemma (by key)\nexport async function getDefinitionsByLemma({\n lemmaKey = \"\",\n localeCode = \"\"\n}) {\n if (typeOf(lemmaKey) !== \"string\" || !lemmaKey) {\n throw new Error(`Parameter \"lemmaKey\" should be a non-empty string`);\n } else if (typeOf(localeCode) !== \"string\" || !localeCode) {\n throw new Error(`Parameter \"localeCode\" should be a non-empty string`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Fetch definitions from server\n const queryData = generateDefinitionsByLemmaQueryData({\n lemmaKey,\n localeCode: this.userService.outLocaleCode\n });\n let definitionsData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ definitionsByLemma: definitionsData = [] } = responseData);\n } catch(error) {\n console.warn(`Error fetching definitions by lemma \"${lemmaKey}\" ` +\n `from server`, error);\n return [];\n }\n const definitionsCount = definitionsData.length;\n console.debug(`Fetched ${definitionsCount} definition/s ` + \n `by lemma \"${lemmaKey}\" from server`);\n if (definitionsCount === 0) {\n return [];\n }\n\n // Save definitions to the index\n try {\n await Promise.all([\n this.indexService.saveDocuments({\n collectionName: \"definitions\",\n documentsData: definitionsData\n })\n ]);\n console.debug(`Saved ${definitionsCount} definition/s to the index`);\n } catch(error) {\n console.warn(`Error while saving ${definitionsCount} definition/s ` +\n `to the index`, error);\n }\n\n // Return definitions' data\n return definitionsData;\n}\n","// Define the \"reportDefinition\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateReportDefinitionMutationData } from \"queries/definition\";\n\n\n// reportDefinition: async\n// returns a promise which either resolves with the updated definition data \n// object reported on the server/index, or rejects with an error\nexport async function reportDefinition({\n definitionKey = \"\",\n lemmaKey = \"\",\n reportData = {}\n}) {\n if (typeOf(definitionKey) !== \"string\" || !definitionKey) {\n throw new Error(`Parameter \"definitionKey\" should be a non-empty string`);\n } else if (typeOf(lemmaKey) !== \"string\" || !lemmaKey) {\n throw new Error(`Parameter \"lemmaKey\" should be a non-empty string`);\n } else if (typeOf(reportData) !== \"object\") {\n throw new Error(`Parameter \"reportData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService.isSessionOpen) {\n throw new Error(`Could not report definition on server (no open session)`);\n }\n\n const queryData = generateReportDefinitionMutationData({ \n definitionKey,\n reportData\n });\n let reportTypeString, updatedDefinitionData;\n try {\n const { \n reportDefinition: reportDefinitionData = null \n } = await this.graphQLService.query({ queryData });\n ({ definitionData: updatedDefinitionData = null } = reportDefinitionData);\n const { userReport: userReportData = null } = updatedDefinitionData || {};\n const { type: reportType = \"\" } = userReportData || {};\n reportTypeString = reportType ? ` (${reportType})` : ``;\n\n } catch(error) {\n console.warn(\"error\", error);\n throw new Error(`Error reporting definition \"${definitionKey}\" ` +\n `as problematic${reportTypeString} on server`);\n }\n console.debug(`Reported definition \"${definitionKey}\" ` +\n `as problematic${reportTypeString} on server`);\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.deleteDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: computeHash(`definitions-by-lemma-${lemmaKey}`)\n }),\n this.indexService.saveDocument({\n collectionName: \"definitions\",\n documentData: updatedDefinitionData\n })\n ]);\n } catch(error) {\n console.warn(\"error\", error);\n throw new Error(`Error reporting definition \"${definitionKey}\" ` +\n `as problematic${reportTypeString} on index`);\n }\n console.debug(`Reported definition \"${definitionKey}\" ` +\n `as problematic${reportTypeString} on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Return updated definition's data\n return updatedDefinitionData;\n}\n","// Define the \"reportLemmaDefinition\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateReportLemmaDefinitionMutationData } from \"queries/definition\";\n\n\n// reportLemmaDefinition: async\n// returns a promise which either resolves with the updated definition object \n// after the specified definition (by key) has been reported against the \n// specified lemma (by key) on the server/index, or rejects with an error\nexport async function reportLemmaDefinition({\n definitionKey = \"\",\n lemmaKey = \"\",\n reportData = null\n}) {\n if (typeOf(definitionKey) !== \"string\" || !definitionKey) {\n throw new Error(`Parameter \"definitionKey\" should be a non-empty string`);\n } else if (typeOf(lemmaKey) !== \"string\" || !lemmaKey) {\n throw new Error(`Parameter \"lemmaKey\" should be a non-empty string`);\n } else if (typeOf(reportData) !== \"object\") {\n throw new Error(`Parameter \"reportData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService.isSessionOpen) {\n throw new Error(`Could not report definition on server (no open session)`);\n }\n\n const queryData = generateReportLemmaDefinitionMutationData({ \n definitionKey,\n lemmaKey,\n reportData\n });\n let reportTypeString, updatedDefinitionData;\n try {\n const { \n reportLemmaDefinition: reportLemmaDefinitionData = null \n } = await this.graphQLService.query({ queryData });\n ({ definitionData: updatedDefinitionData } = reportLemmaDefinitionData);\n const { userReport: userReportData = null } = updatedDefinitionData || {};\n const { type: reportType = \"\" } = userReportData || {};\n reportTypeString = reportType ? ` (${reportType})` : ``;\n } catch(error) {\n console.warn(\"error\", error);\n throw new Error(`Error reporting definition \"${definitionKey}\" ` +\n `as problematic${reportTypeString} for lemma \"${lemmaKey}\" on server`);\n }\n console.debug(`Reported definition \"${definitionKey}\" ` +\n `as problematic${reportTypeString} for lemma \"${lemmaKey}\" on server`);\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.deleteDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: computeHash(`definitions-by-lemma-${lemmaKey}`)\n }),\n this.indexService.saveDocument({\n collectionName: \"definitions\",\n documentData: updatedDefinitionData\n })\n ]);\n } catch(error) {\n console.warn(\"error\", error);\n throw new Error(`Error reporting definition \"${definitionKey}\" ` +\n `as problematic${reportTypeString} for lemma \"${lemmaKey}\" on index`);\n }\n console.debug(`Reported definition \"${definitionKey}\" ` +\n `as problematic${reportTypeString} for lemma \"${lemmaKey}\" on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Return updated definition's data\n return updatedDefinitionData;\n}\n","// Define the \"unreportDefinition\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateUnreportDefinitionMutationData } from \"queries/definition\";\n\n\n// unreportDefinition: async\n// returns a promise which either resolves with the updated definition data \n// object un-reported on the server/index, or rejects with an error\nexport async function unreportDefinition({\n definitionKey = \"\",\n lemmaKey = \"\"\n}) {\n if (typeOf(definitionKey) !== \"string\" || !definitionKey) {\n throw new Error(`Parameter \"definitionKey\" should be a non-empty string`);\n } else if (typeOf(lemmaKey) !== \"string\" || !lemmaKey) {\n throw new Error(`Parameter \"lemmaKey\" should be a non-empty string`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService.isSessionOpen) {\n throw new Error(`Could not un-report definition on server (no open session)`);\n }\n\n const queryData = generateUnreportDefinitionMutationData({ \n definitionKey\n });\n let updatedDefinitionData;\n try {\n const { \n unreportDefinition: unreportDefinitionResponse = null \n } = await this.graphQLService.query({ queryData });\n ({ definitionData: updatedDefinitionData } = unreportDefinitionResponse);\n } catch(error) {\n console.error(error);\n throw new Error(`Error un-reporting definition \"${definitionKey}\" ` +\n `as problematic on server`);\n }\n console.debug(`Un-reported definition \"${definitionKey}\" ` +\n `as problematic on server`);\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.deleteDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: computeHash(`definitions-by-lemma-${lemmaKey}`)\n }),\n this.indexService.saveDocument({\n collectionName: \"definitions\",\n documentData: updatedDefinitionData\n })\n ]);\n } catch(error) {\n console.warn(\"error\", error);\n throw new Error(`Error un-reporting definition \"${definitionKey}\" ` +\n `as problematic on index`);\n }\n console.debug(`Un-reported definition \"${definitionKey}\" ` +\n `as problematic on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Return updated definition's data\n return updatedDefinitionData;\n}\n","// Define the \"unreportLemmaDefinition\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateUnreportLemmaDefinitionMutationData } from \"queries/definition\";\n\n\n// unreportLemmaDefinition: async\n// returns a promise which either resolves with the updated definition data \n// object un-reported on the server/index, or rejects with an error\nexport async function unreportLemmaDefinition({\n definitionKey = \"\",\n lemmaKey = \"\"\n}) {\n if (typeOf(definitionKey) !== \"string\" || !definitionKey) {\n throw new Error(`Parameter \"definitionKey\" should be a non-empty string`);\n } else if (typeOf(lemmaKey) !== \"string\" || !lemmaKey) {\n throw new Error(`Parameter \"lemmaKey\" should be a non-empty string`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService.isSessionOpen) {\n throw new Error(`Could not un-report lemma definition on server ` +\n `(no open session)`);\n }\n\n const queryData = generateUnreportLemmaDefinitionMutationData({ \n definitionKey,\n lemmaKey\n });\n let updatedDefinitionData;\n try {\n const { \n unreportLemmaDefinition: unreportLemmaDefinitionData = null \n } = await this.graphQLService.query({ queryData });\n ({ definitionData: updatedDefinitionData } = unreportLemmaDefinitionData);\n } catch(error) {\n console.error(error);\n throw new Error(`Error un-reporting definition \"${definitionKey}\" ` +\n `as problematic for lemma \"${lemmaKey}\" on server`);\n }\n console.debug(`Un-reported definition \"${definitionKey}\" ` +\n `as problematic for lemma \"${lemmaKey}\" on server`);\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.deleteDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: computeHash(`definitions-by-lemma-${lemmaKey}`)\n }),\n this.indexService.saveDocument({\n collectionName: \"definitions\",\n documentData: updatedDefinitionData\n })\n ]);\n } catch(error) {\n console.warn(\"error\", error);\n throw new Error(`Error un-reporting definition \"${definitionKey}\" ` +\n `as problematic for lemma \"${lemmaKey}\" on index`);\n }\n console.debug(`Un-reported definition \"${definitionKey}\" ` +\n `as problematic for lemma \"${lemmaKey}\" on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Return updated definition's data\n return updatedDefinitionData;\n}\n","// Define the \"element\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport EntityService from \"services/entity\";\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { createElement } from \"./element/create-element\";\nimport {\n getChildElementsByElement\n} from \"./element/get-child-elements-by-element\";\n\n\n// Export the \"ElementService\" class\n@inject(\n EntityService,\n GraphQLService, \n IndexService, \n UserService\n)\nexport default class ElementService {\n\n constructor(\n entityService,\n graphQLService, \n indexService, \n userService\n ) {\n this.entityService = entityService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.userService = userService;\n }\n\n\n // Core methods\n async getElementByKey(elementKey) {\n return await this.entityService.getEntityByKey({\n entityName: \"element\",\n entityKey: elementKey || \"\"\n });\n }\n\n async getElementsByKeys(elementsKeys) {\n return await this.entityService.getEntitiesByKeys({\n entityName: \"element\",\n entitiesKeys: elementsKeys || []\n });\n }\n\n async getChildElementsByElement(elementData) {\n return await getChildElementsByElement.call(this, elementData);\n }\n\n async createElement(elementData) {\n return await createElement.call(this, elementData);\n }\n\n async saveElement(elementData) {\n return await this.createElement(elementData);\n }\n\n}\n","// Define the \"createElement\" service method\n\n// Import utility modules\nimport { computeCreateElementData } from \"utilities/element\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateCreateElementMutationData } from \"queries/element\";\n\n\n// createElement: async\n// returns a promise which either resolves with the element data object\n// created on the server/index, or rejects with an error\nexport async function createElement(elementData) {\n if (typeOf(elementData) !== \"object\") {\n throw new Error(`Parameter \"elementData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Create element on server\n const createElementData = computeCreateElementData(elementData);\n const queryData = generateCreateElementMutationData({ createElementData });\n let createdElementData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ createElement: createdElementData } = responseData);\n } catch(error) {\n throw new Error(`Error creating element on server`);\n }\n const { key: createdElementKey } = createdElementData;\n console.debug(`Created element \"${createdElementKey}\" on server`);\n\n // Save element to the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"elements\",\n documentData: createdElementData\n });\n } catch(error) {\n throw new Error(`Error saving element \"${createdElementKey}\" to the index`);\n }\n console.debug(`Saved element \"${createdElementKey}\" to the index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Return created element's data\n return createdElementData;\n}\n","// Define the \"getChildElementsByElement\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateChildElementsByElementQueryData } from \"queries/element\";\n\n\n// getChildElementsByElement: async\n// returns a promise which resolves with the (possibly empty) array of\n// elements that are children of the specified element data object\nexport async function getChildElementsByElement({\n key: elementKey,\n type: elementType\n}) {\n if (typeOf(elementKey) !== \"string\") {\n throw new Error(`Parameter \"elementKey\" should be a string`);\n } else if (typeOf(elementType) !== \"string\") {\n throw new Error(`Parameter \"elementType\" should be a string`);\n } else if (!this.getElementsByKeys) {\n throw new Error(`Missing required \"getElementsByKeys\" method`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n if (![ \"grp\", \"cmp\" ].includes(elementType)) {\n console.debug(`No child elements for ${elementType} element \"${elementKey}\"`);\n return [];\n }\n\n // Fetch child elements from index\n const cacheKey = computeHash(`child-elements-by-element-${elementKey}`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: indexedChildElementsKeys = [] } = cacheData || {};\n const indexedChildElementsCount = indexedChildElementsKeys.length;\n if (indexedChildElementsCount > 0) {\n const indexedChildElementsData = \n await this.getElementsByKeys(indexedChildElementsKeys);\n if (indexedChildElementsData.every(elementData => elementData !== null)) {\n console.debug(`Fetched ${indexedChildElementsCount} child elements ` + \n `by element \"${elementKey}\" from index`);\n return indexedChildElementsData;\n }\n }\n\n // Fetch child elements from server\n const queryData = generateChildElementsByElementQueryData({ elementKey });\n let childElementsData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n const { elementByKey: elementData = null } = responseData || {};\n ({ childElements: childElementsData = [] } = elementData || {});\n } catch(error) {\n console.warn(`Error fetching child elements by element \"${elementKey}\" ` +\n `from server`, error);\n return [];\n }\n const childElementsCount = childElementsData.length;\n console.debug(`Fetched ${childElementsCount} child elements ` +\n `by element \"${elementKey}\" from server`);\n if (childElementsCount === 0) {\n return [];\n }\n\n // Save child elements to the index\n const childElementsKeys = childElementsData\n .map(({ key: elementKey = \"\" }) => elementKey)\n try {\n await Promise.all([\n this.indexService.saveDocuments({\n collectionName: \"elements\",\n documentsData: childElementsData\n }),\n this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: childElementsKeys\n }\n })\n ]);\n console.debug(`Saved ${childElementsCount} child elements ` +\n `by element \"${elementKey}\" to the index`);\n } catch(error) {\n console.warn(`Error saving ${childElementsCount} child elements ` +\n `by element \"${elementKey}\" to the index`, error);\n }\n\n // Return child elements' data\n return childElementsData;\n}\n","// Define the \"entity\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\n\n// Import method modules\nimport { getEntityByKey } from \"./entity/get-entity-by-key\";\nimport { getEntitiesByKeys } from \"./entity/get-entities-by-keys\";\n\n\n// Export the \"EntityService\" class\n@inject(\n GraphQLService,\n IndexService\n)\nexport default class EntityService {\n\n constructor(\n graphQLService, \n indexService\n ) {\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n }\n\n async getEntityByKey({ \n entityName = \"\", \n entityKey = \"\",\n indexDatabaseName = this.indexService.defaltDatabaseName, // optional\n indexCollectionName = \"\", // optional\n fetchFromIndex = true,\n saveToIndex = true\n }) {\n return await getEntityByKey.call(this, { \n entityName, \n entityKey,\n indexDatabaseName,\n indexCollectionName,\n fetchFromIndex,\n saveToIndex\n });\n }\n\n async getEntitiesByKeys({ \n entityName = \"\", \n entitiesKeys = [],\n indexDatabaseName = this.indexService.defaltDatabaseName, // optional\n indexCollectionName = \"\", // optional\n fetchFromIndex = true,\n saveToIndex = true\n }) {\n return await getEntitiesByKeys.call(this, {\n entityName,\n entitiesKeys,\n indexDatabaseName,\n indexCollectionName,\n fetchFromIndex,\n saveToIndex\n });\n }\n\n}\n","// Define the \"getEntitiesByKeys\" service method\n\n// Import utility modules\nimport { pluralize } from \"utilities/english\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateEntitiesByKeysQueryData } from \"queries/entity\";\n\n\n// getEntitiesByKeys: async\n// returns a promise which resolves either with an array of entity data\n// objects corresponding to the specified array of keys, or with an empty\n// array if an error occurs\nexport async function getEntitiesByKeys({\n entityName,\n entitiesKeys = [],\n indexDatabaseName = this.indexService.defaultDatabaseName, // optional\n indexCollectionName = \"\", // optional\n fetchFromIndex = true,\n saveToIndex = true\n}) {\n if (typeOf(entityName) !== \"string\" || !entityName) {\n throw new Error(`Parameter \"entityName\" should be a non-empty string`)\n } if (typeOf(entitiesKeys) !== \"array\") {\n throw new Error(`Parameter \"entitiesKeys\" should be an array of strings`);\n } if (typeOf(indexDatabaseName) !== \"string\") {\n throw new Error(`Parameter \"indexDatabaseName\" should be a string`);\n } if (typeOf(indexCollectionName) !== \"string\") {\n throw new Error(`Parameter \"indexCollectionName\" should be a string`);\n } if (typeOf(fetchFromIndex) !== \"boolean\") {\n throw new Error(`Parameter \"fetchFromIndex\" should be a boolean`);\n } if (typeOf(saveToIndex) !== \"boolean\") {\n throw new Error(`Parameter \"saveToIndex\" should be a boolean`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n }\n\n if (entitiesKeys.length === 0) {\n return [];\n }\n\n // Compute derived parameters\n const pluralizedEntityName = pluralize(entityName);\n const entitiesDisplayName = entityName === \"activityItem\" ? \n \"activity items\" : pluralizedEntityName;\n const entitiesQueryName = `${entityName === \"activityItem\" ? \n \"activities\" : pluralizedEntityName}ByKeys`;\n indexCollectionName = indexCollectionName || pluralizedEntityName;\n\n // Fetch entities from index\n if (fetchFromIndex) {\n const indexedEntitiesData = await this.indexService.getDocumentsByKeys({\n databaseName: indexDatabaseName,\n collectionName: indexCollectionName,\n documentsKeys: entitiesKeys\n });\n const allIndexedEntitiesAvailable = indexedEntitiesData\n .every(indexedEntityData => indexedEntityData !== null);\n if (allIndexedEntitiesAvailable) {\n console.debug(`Fetched ${entitiesDisplayName} by keys [${entitiesKeys}] ` +\n `from index`);\n return indexedEntitiesData;\n }\n }\n\n // Fetch missing entities from server\n const queryData = generateEntitiesByKeysQueryData({\n entityName,\n entitiesKeys\n });\n let entitiesData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n const safeResponseData = responseData || {};\n entitiesData = safeResponseData[entitiesQueryName] || [];\n } catch(error) {\n console.warn(`Error fetching ${entitiesDisplayName} by keys ` +\n `[${entitiesKeys}] from server`, error);\n return [];\n }\n const entitiesCount = entitiesData.length;\n console.debug(`Fetched ${entitiesDisplayName} by keys [${entitiesKeys}] ` +\n `from server`);\n if (entitiesCount === 0) {\n return [];\n }\n\n // Save entities to the index\n if (saveToIndex) {\n try {\n await this.indexService.saveDocuments({\n databaseName: indexDatabaseName,\n collectionName: indexCollectionName,\n documentsData: entitiesData\n });\n console.debug(`Saved ${entitiesCount} ${entitiesDisplayName} ` +\n `to the index`);\n } catch(error) {\n console.warn(`Error saving ${entitiesCount} ${entitiesDisplayName} ` +\n `to the index`, error);\n }\n }\n\n // Return entities data\n return entitiesData;\n}\n","// Define the \"getEntityByKey\" service method\n\n// Import utility modules\nimport { pluralize } from \"utilities/english\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateEntityByKeyQueryData } from \"queries/entity\";\n\n\n// getEntityByKey: async\n// Returns a promise which resolves either with the entity data object\n// corresponding to the specified key, or with null if an error occurred\n// while fetching data from the server\nexport async function getEntityByKey({\n entityName = \"\",\n entityKey = \"\",\n indexDatabaseName = this.indexService.defaultDatabaseName, // optional\n indexCollectionName = \"\", // optional\n fetchFromIndex = true,\n saveToIndex = true\n}) {\n if (typeOf(entityName) !== \"string\" || !entityName) {\n throw new Error(`Parameter \"entityName\" should be a non-empty string`);\n } else if (typeOf(entityKey) !== \"string\" || !entityKey) {\n throw new Error(`Parameter \"entityKey\" should be a non-empty string`);\n } if (typeOf(indexDatabaseName) !== \"string\") {\n throw new Error(`Parameter \"indexDatabaseName\" should be a string`);\n } if (typeOf(indexCollectionName) !== \"string\") {\n throw new Error(`Parameter \"indexCollectionName\" should be a string`);\n } if (typeOf(fetchFromIndex) !== \"boolean\") {\n throw new Error(`Parameter \"fetchFromIndex\" should be a boolean`);\n } if (typeOf(saveToIndex) !== \"boolean\") {\n throw new Error(`Parameter \"saveToIndex\" should be a boolean`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n }\n\n // Compute derived parameters\n const entityDisplayName = \n entityName === \"activityItem\" ? \"activity item\" : entityName;\n const entityQueryName = \n `${entityName === \"activityItem\" ? \"activity\" : entityName}ByKey`;\n indexCollectionName = indexCollectionName || pluralize(entityName);\n\n // Fetch entity data from the index\n if (fetchFromIndex) {\n const indexEntityData = await this.indexService.getDocumentByKey({\n databaseName: indexDatabaseName,\n collectionName: indexCollectionName,\n documentKey: entityKey\n });\n if (indexEntityData) {\n // Return index's entity data\n console.debug(`Fetched ${entityDisplayName} by key \"${entityKey}\" ` +\n `from index`);\n return indexEntityData;\n }\n }\n\n // Fetch entity data from the server\n const queryData = generateEntityByKeyQueryData({ entityName, entityKey });\n let serverEntityData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n const safeResponseData = responseData || {};\n serverEntityData = safeResponseData[entityQueryName] || null;\n } catch(error) {\n console.warn(`Error fetching ${entityDisplayName} by key \"${entityKey}\" ` +\n `from server`, error);\n return null;\n }\n if (!serverEntityData) {\n console.warn(`Fetched empty ${entityDisplayName} by key \"${entityKey}\" ` +\n `from server`);\n return null;\n }\n console.debug(`Fetched ${entityDisplayName} by key \"${entityKey}\" ` +\n `from server`);\n\n // Save entity data to the index\n if (saveToIndex) {\n try {\n await this.indexService.saveDocument({\n databaseName: indexDatabaseName,\n collectionName: indexCollectionName,\n documentData: serverEntityData\n });\n } catch(error) {\n console.warn(`Error saving data for ${entityDisplayName} \"${entityKey}\" ` +\n `to the index`, error);\n }\n }\n\n // Return entity data\n return serverEntityData;\n}\n","// Define the \"event\" service\n\n// Import library modules\nimport { EventAggregator } from \"aurelia-event-aggregator\";\nimport { inject } from \"aurelia-framework\";\n\n// Import parameter modules\nimport { runMode } from \"parameters/environment\";\n\n// Import method modules\nimport { publish } from \"services/event/publish\";\nimport { subscribe } from \"services/event/subscribe\";\n\n\n// Export the \"EventService\" class\n@inject(EventAggregator)\nexport default class EventService {\n\n constructor(eventAggregator) {\n this.eventAggregator = eventAggregator;\n this.subscribedEventNamesSet = new Set();\n }\n\n publish({ \n eventName = \"\", \n eventData = null,\n showDebugs = [\"dev\"].includes(runMode),\n showWarnings = [\"dev\"].includes(runMode)\n }) {\n publish.call(this, { \n eventName, \n eventData, \n showDebugs, \n showWarnings \n });\n }\n\n subscribe({ \n eventName, \n eventHandler, \n eventFilter = () => true,\n showDebugs = [\"dev\"].includes(runMode),\n }) {\n return subscribe.call(this, { \n eventName, \n eventHandler, \n eventFilter,\n showDebugs\n });\n }\n\n}\n","// Define the \"publish\" event service class method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { runMode } from \"parameters/environment\";\n\n// Define internal parameters\nconst propertyNameEndsWithKeyRegExp = /Key$/;\nconst propertyNameEndsWithIndexRegExp = /Index$/;\n\n\n// publish: sync\n// publish the specified event name to the event aggregator, passing the\n// specified event data as an argument\nexport function publish({\n eventName = \"\", \n eventData = null,\n showDebugs = [\"dev\"].includes(runMode),\n showWarnings = [\"dev\"].includes(runMode)\n}) {\n if (typeOf(eventName) !== \"string\" || !eventName) {\n throw new Error(`Parameter \"eventName\" should be a non-empty string`);\n } else if (![\"object\", \"null\"].includes(typeOf(eventData))) {\n throw new Error(`Parameter \"eventData\" should be an object or null`);\n } else if (typeOf(showDebugs) !== \"boolean\") {\n throw new Error(`Parameter \"showDebugs\" should be a boolean`);\n } else if (typeOf(showWarnings) !== \"boolean\") {\n throw new Error(`Parameter \"showWarnings\" should be a boolean`);\n }\n\n if (!this.subscribedEventNamesSet.has(eventName)) {\n if (showWarnings) {\n console.warn(`Refused to publish unsubscribed event \"${eventName}\"`,\n eventData);\n }\n return;\n }\n const eventDataValid = Object.entries(eventData)\n .every(([ propertyName, propertyValue ]) => {\n if (propertyNameEndsWithIndexRegExp.test(propertyName)) {\n return typeOf(propertyValue) === \"number\" && \n propertyValue >= 0;\n } else if (propertyNameEndsWithKeyRegExp.test(propertyName)) {\n return typeOf(propertyValue) === \"string\";\n }\n return true;\n });\n if (eventDataValid) {\n if (showDebugs) {\n console.debug(`Published \"${eventName}\" event`, eventData);\n }\n this.eventAggregator.publish(eventName, eventData);\n } else {\n if (showWarnings) {\n console.warn(`Refused to publish invalid \"${eventName}\" event`, eventData);\n }\n }\n}\n","// Define the \"subscribe\" event service class method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { runMode } from \"parameters/environment\";\n\n\n// subscribe: sync\n// subscribe the event aggregator to the specified event name by executing\n// the specified event handler function with the published event data as\n// an argument, provided that the event filter function, if specified,\n// returns a truthy value when passed the event data as an argument\nexport function subscribe({\n eventName, \n eventHandler, \n eventFilter = () => true,\n showDebugs = [\"dev\"].includes(runMode),\n}) {\n if (typeOf(eventName) !== \"string\" || !eventName) {\n throw new Error(`Parameter \"eventName\" should be a non-empty string`);\n } else if (![\"function\", \"asyncfunction\"].includes(typeOf(eventHandler))) {\n throw new Error(`Could not subscribe to \"${eventName}\" event: ` +\n `event handler should be a function`);\n } else if (![\"function\", \"asyncfunction\"].includes(typeOf(eventFilter))) {\n throw new Error(`Could not subscribe to \"${eventName}\" event: ` +\n `event filter should be a function`);\n } else if (typeOf(showDebugs) !== \"boolean\") {\n throw new Error(`Parameter \"showDebugs\" should be a boolean`);\n }\n\n this.subscribedEventNamesSet.add(eventName);\n return this.eventAggregator.subscribe(eventName, async(eventData) => {\n if (eventFilter(eventData)) {\n if (showDebugs) {\n console.debug(`Handling \"${eventName}\" event`, eventData);\n }\n await eventHandler(eventData);\n }\n });\n}\n","// Define the \"generator\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport GraphQLService from \"./graphql\";\nimport IndexService from \"./index\";\nimport UserService from \"./user\";\n\n// Import method modules\nimport {\n getGeneratorsByElement\n} from \"./generator/get-generators-by-element\";\n\n\n// Export the \"GeneratorService\" class\n@inject(\n GraphQLService, \n IndexService, \n UserService\n)\nexport default class GeneratorService {\n\n constructor(\n graphQLService, \n indexService, \n userService\n ) {\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.userService = userService;\n }\n\n\n // Core methods\n async getGeneratorsByElement({\n inElementData = null,\n fetchFromIndex = true,\n saveToIndex = true\n }) {\n return await getGeneratorsByElement.call(this, {\n inElementData,\n fetchFromIndex,\n saveToIndex\n });\n }\n\n}\n","// Define the \"getGeneratorsByElement\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { sortInflectors } from \"utilities/inflector\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateGeneratorsByElementQueryData } from \"queries/generator\";\n\n\n// getGeneratorsByElement: async\n// returns a promise which resolves with the (possibly empty) array of\n// generators (i.e. inflector/lemma combibations) generating the specified\n// element data object\nexport async function getGeneratorsByElement({\n inElementData: {\n key: inElementKey,\n type: inElementType\n },\n fetchFromIndex = true,\n saveToIndex = true\n}) {\n if (typeOf(inElementKey) !== \"string\") {\n throw new Error(`Parameter \"inElementKey\" should be a string`);\n } else if (typeOf(inElementType) !== \"string\") {\n throw new Error(`Parameter \"inElementType\" should be a string`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // No generator for non-word/group/prefix/suffix elements\n if (![ \"wrd\", \"grp\", \"pfx\", \"sfx\" ].includes(inElementType)) {\n console.debug(`No generators for ${inElementType} element \"${inElementKey}\"`);\n return [];\n }\n\n // Fetch generators from index\n // note: generators do not have keys, so they are indexed directly\n const cacheKey = computeHash(`generators-by-element-${inElementKey}`);\n if (fetchFromIndex) {\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: indexedGeneratorsData = [] } = cacheData || {};\n const indexedGeneratorsCount = indexedGeneratorsData.length;\n if (indexedGeneratorsCount > 0 &&\n indexedGeneratorsData.every(gData => gData !== null)) {\n console.debug(`Fetched ${indexedGeneratorsCount} generators ` +\n `by element \"${inElementKey}\" from index`);\n return indexedGeneratorsData;\n }\n }\n\n // Fetch generators from server\n const queryData = generateGeneratorsByElementQueryData({ inElementKey });\n let generatorsData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ elementByKey: { generators: generatorsData = [] } } = responseData);\n } catch(error) {\n console.warn(`Error fetching generators by ${inElementType} ` +\n `element \"${inElementKey}\" from server`, error);\n return [];\n }\n const generatorsCount = generatorsData.length;\n console.debug(`Fetched ${generatorsCount} generators by ${inElementType} ` +\n `element \"${inElementKey}\" from server`);\n if (generatorsCount === 0) {\n return [];\n }\n\n // Sort inflectors array within generators\n generatorsData = generatorsData.map(generatorData => {\n const { inflectors: inflectorsData = [] } = generatorData || {};\n const sortedInflectorsData = sortInflectors(inflectorsData);\n return {\n ...generatorData,\n inflectors: sortedInflectorsData\n };\n });\n\n // Save generators to the index\n if (saveToIndex) {\n try {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: generatorsData\n }\n });\n console.debug(`Saved ${generatorsCount} generators ` +\n `by ${inElementType} element \"${inElementKey}\" to the index`);\n } catch(error) {\n console.warn(`Error saving ${generatorsCount} generators ` +\n `by ${inElementType} element \"${inElementKey}\" to the index`, error);\n }\n }\n\n // Return generators' data\n return generatorsData;\n}\n","// Define the \"graphQL\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import utility modules\nimport { getSessionStorageData } from \"utilities/storage\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import service modules\nimport APIService from \"services/api\";\n\n// Import method modules\nimport { query } from \"services/graphql/query\";\n\n\n// Export the \"GraphQLService\" class\n@inject(APIService)\nexport default class GraphQLService {\n\n // Attributes\n _tokenString = \"\";\n\n constructor(apiService) {\n this.apiService = apiService;\n\n const { token: tokenData = {} } = getSessionStorageData() || {};\n const { string: tokenString = \"\" } = tokenData || {};\n this.tokenString = tokenString;\n }\n\n\n // Getter and setter methods\n get tokenString() {\n return this._tokenString;\n }\n set tokenString(newTokenString) {\n if (typeOf(newTokenString) !== \"string\") {\n throw new Error(`Invalid token string \"${newTokenString}\"`);\n }\n this._tokenString = newTokenString;\n }\n\n\n // Core methods\n async query(queryExtData) {\n return await query.call(this, queryExtData);\n }\n\n}\n","// Define the \"query\" graphQL service method\n\n// Import library modules\nimport ms from \"ms\";\n\n// Import utility modules\nimport { exponentialBackoff, withTimeout } from \"utilities/promise\";\n\n// Import parameter modules\nimport { runMode } from \"parameters/environment\";\nimport { graphQLPath } from \"parameters/api\";\nimport {\n requestBackoffTimeoutsMs as defaultRequestBackoffTimeoutsMs,\n requestTimeoutMs as defaultRequestTimeoutMs\n} from \"parameters/graphql\";\n\n\n// query: async\n// returns a promise which either resolves with a successful response's\n// data or rejects with the first error from a failure response\nexport async function query({\n queryData,\n tokenString = this.tokenString,\n requestTimeoutMs = defaultRequestTimeoutMs,\n requestBackoffTimeoutsMs = defaultRequestBackoffTimeoutsMs,\n authenticated = true, // set the \"Bearer\" authentication header\n cachedResponse = true // set the \"Cached-Response\" header on the request\n}) {\n // Conditionally display query string and variables\n if ([\"dev\"].includes(runMode)) {\n console.debug(\"graphQL query string:\", queryData.query);\n if (queryData.variables) {\n console.debug(\"graphQL query variables:\", queryData.variables);\n }\n }\n\n // Conditionally set request headers\n const headers = {};\n if (authenticated) {\n if (tokenString) {\n headers[\"Authentication\"] = `Bearer ${tokenString}`;\n } else {\n throw new Error(`Cannot perform authenticated request: no open session`);\n }\n }\n if (!cachedResponse) {\n headers[\"Cached-Response\"] = `off`;\n }\n\n // Define the API request\n const request = async() => {\n let responseBody;\n try {\n responseBody = await this.apiService.fetch({\n path: graphQLPath,\n method: \"post\",\n body: queryData,\n headers\n });\n } catch(error) {\n throw new Error(`Error while fetching from the API service`);\n }\n if (responseBody.errors) {\n throw responseBody.errors[0];\n }\n return responseBody.data;\n };\n const requestWithTimeout = withTimeout(request, {\n timeoutMs: requestTimeoutMs,\n error: new Error(`Time-out error while fetching from the API service`)\n });\n const requestWithBackoff = exponentialBackoff(requestWithTimeout, {\n backoffTimeoutsMs: requestBackoffTimeoutsMs,\n backoffCallback: ({ backoffTimeoutMs, error }) => {\n const { message: errorMessage = \"\" } = error || {};\n if (!errorMessage) {\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n console.error(`Unexpected error \"${errorMessage}\" ` +\n `while fetching from the API service`);\n }\n return false; // stop exponential back-off\n }\n switch (errorMessage) {\n case \"Time-out error while fetching from the API service\":\n case \"Network error while fetching from the API service\":\n case \"Error while fetching from the API service\":\n case \"No network connection\":\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n console.warn(`Exponentially backing-off by ` +\n `${ms(backoffTimeoutMs)} upon error \"${errorMessage}\" ` +\n `while querying the graphQL service`);\n }\n return true; // continue exponential back-off\n case \"User authentication failed\":\n case \"User is not authenticated\":\n case \"User is not authorized\":\n case \"Operation is forbidden\":\n case \"Input is invalid\":\n case \"Resource is in conflict\":\n case \"Resource is not found\":\n return false; // stop exponential back-off\n default:\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n console.warn(`Exponentially backing-off by ` +\n `${ms(backoffTimeoutMs)} upon unexpected error ` +\n `\"${errorMessage}\" while querying the graphQL service`);\n }\n return true; // continue exponential back-off\n }\n },\n backoffFailureError: new Error(`Exponential back-off failed`)\n });\n\n // Send final request\n let response;\n try {\n response = await requestWithBackoff;\n } catch(error) {\n console.warn(`Error while querying the graphQL service`);\n throw error;\n }\n\n // Return the response\n return response;\n}\n","// Define the \"index\" service\n\n// Import library modules\nimport Dexie from \"dexie\";\n\n// Import utility modules\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { \n getSessionStorageData,\n getLocalStorageData\n} from \"utilities/storage\";\n\n// Import parameter modules\nimport { \n globalDatabaseData, \n userDatabaseData \n} from \"parameters/index\";\nimport { \n initialSessionStorageData,\n initialLocalStorageData\n} from \"parameters/state\";\n\n// Import method modules\nimport { pruneExpiredDocuments } from \"./index/prune-expired-documents\";\nimport { getAllCollectionDocuments } from \"./index/get-all-collection-documents\";\nimport { getDocumentByKey } from \"./index/get-document-by-key\";\nimport { getDocumentsByKeys } from \"./index/get-documents-by-keys\";\nimport { setPruningInterval } from \"./index/set-pruning-interval\";\nimport { deleteDocumentByKey } from \"./index/delete-document-by-key\";\nimport { saveDocuments } from \"./index/save-documents\";\nimport { saveDocument } from \"./index/save-document\";\nimport { \n getAllDatabaseDocumentsCount \n} from \"./index/get-all-database-documents-count\";\nimport { \n deleteAllCollectionDocuments \n} from \"./index/delete-all-collection-documents\";\nimport { \n deleteAllDatabaseDocuments \n} from \"./index/delete-all-database-documents\";\n\n// Define internal parameters\nconst {\n name: globalDatabaseName,\n version: globalDatabaseVersion,\n schema: globalDatabaseSchema\n} = globalDatabaseData;\nconst {\n baseName: userDatabaseBaseName,\n version: userDatabaseVersion,\n schema: userDatabaseSchema\n} = userDatabaseData;\n\n\n// Export the \"IndexService\" class\nexport default class IndexService {\n\n // Attributes\n _pruningIntervalsIds = {};\n _allDatabases = {};\n\n constructor() {\n this.createGlobalDatabase();\n if (this.isSessionOpen) {\n this.createUserDatabase();\n }\n }\n\n\n // Core methods\n getDatabase(databaseName) {\n return this._allDatabases[databaseName];\n }\n get defaultDatabase() {\n return this.getDatabase(this.defaultDatabaseName);\n }\n get defaultDatabaseName() {\n return this.userDatabaseName || globalDatabaseName;\n }\n get databasesNames() {\n return Object.values(this._allDatabases)\n .map(database => database.name);\n }\n\n createGlobalDatabase() {\n const existingGlobalDatabase = this.getDatabase(globalDatabaseName);\n if (existingGlobalDatabase) {\n console.debug(`Existing indexedDB database \"${globalDatabaseName}\"`);\n return existingGlobalDatabase;\n }\n const globalDatabase = new Dexie(globalDatabaseName);\n globalDatabase\n .version(globalDatabaseVersion)\n .stores(globalDatabaseSchema);\n console.debug(`Created indexedDB database \"${globalDatabaseName}\"`);\n this._allDatabases[globalDatabaseName] = globalDatabase;\n this.setPruningInterval(globalDatabaseName);\n return globalDatabase;\n }\n\n get isSessionOpen() {\n const currentTimeStampMs = getCurrentTimeStampMs();\n const sessionStorageData =\n getSessionStorageData() || initialSessionStorageData;\n const tokenEtcData = sessionStorageData.token;\n const {\n exp: sessionExpiresOnMs = currentTimeStampMs\n } = tokenEtcData.data || {};\n return sessionExpiresOnMs > currentTimeStampMs;\n }\n\n get userDatabaseName() {\n const localStorageData = getLocalStorageData() || initialLocalStorageData;\n const { user: userData } = localStorageData || {};\n const { key: userKey = \"\" } = userData || {};\n const userDatabaseName = `${userDatabaseBaseName}-${userKey}`;\n return userKey ? userDatabaseName : \"\";\n }\n\n createUserDatabase() {\n const { userDatabaseName } = this;\n if (this.isSessionOpen) {\n const existingUserDatabase = this.getDatabase(userDatabaseName);\n if (existingUserDatabase) {\n console.debug(`Existing indexedDB database \"${userDatabaseName}\"`);\n return existingUserDatabase;\n }\n const userDatabase = new Dexie(userDatabaseName)\n userDatabase\n .version(userDatabaseVersion)\n .stores(userDatabaseSchema);\n console.debug(`Created indexedDB database \"${userDatabaseName}\"`);\n this._allDatabases[userDatabaseName] = userDatabase;\n this.setPruningInterval(userDatabaseName);\n return userDatabase;\n }\n }\n \n async getDocumentByKey({ \n databaseName = this.defaultDatabaseName, \n collectionName = \"\",\n documentKey = \"\",\n expiryExtension\n }) {\n return await getDocumentByKey.call(this, { \n databaseName, \n collectionName, \n documentKey,\n expiryExtension\n });\n }\n\n async getDocumentsByKeys({ \n databaseName = this.defaultDatabaseName,\n collectionName = \"\",\n documentsKeys = [],\n expiryExtension\n }) {\n return await getDocumentsByKeys.call(this, {\n databaseName,\n collectionName, \n documentsKeys,\n expiryExtension\n });\n }\n\n async getAllCollectionDocuments({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\"\n }) {\n return await getAllCollectionDocuments.call(this, {\n databaseName,\n collectionName\n });\n }\n\n async getAllDatabaseDocumentsCount({\n databaseName = this.defaultDatabaseName\n }) {\n return await getAllDatabaseDocumentsCount.call(this, { databaseName });\n }\n\n async saveDocument({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\",\n documentData = null,\n expiry\n }) {\n return await saveDocument.call(this, { \n databaseName,\n collectionName, \n documentData, \n expiry\n });\n }\n\n async saveDocuments({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\",\n documentsData = [],\n expiry\n }) {\n return await saveDocuments.call(this, {\n databaseName,\n collectionName, \n documentsData, \n expiry\n });\n }\n\n async deleteDocumentByKey({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\",\n documentKey = \"\"\n }) {\n return await deleteDocumentByKey.call(this, {\n databaseName,\n collectionName, \n documentKey\n });\n }\n\n async deleteDocumentsByKeys({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\", \n documentsKeys = []\n }) {\n return await Promise.all(documentsKeys\n .map(documentKey => this.deleteDocumentByKey({\n databaseName,\n collectionName, \n documentKey\n })));\n }\n\n async deleteAllCollectionDocuments({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\"\n }) {\n return await deleteAllCollectionDocuments.call(this, {\n databaseName,\n collectionName\n });\n }\n\n async deleteAllDatabaseDocuments({\n databaseName = this.defaultDatabaseName\n }) {\n return await deleteAllDatabaseDocuments.call(this, { databaseName });\n }\n\n setPruningInterval(databaseName) {\n setPruningInterval.call(this, databaseName);\n }\n\n async pruneExpiredDocuments({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\",\n expiryTimeoutMs\n }) {\n await pruneExpiredDocuments.call(this, {\n databaseName,\n collectionName, \n expiryTimeoutMs\n });\n }\n\n}\n","// Define the \"deleteAllCollectionDocuments\" index service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n\n// deleteAllCollectionDocuments: async \n// returns a promise which either resolves with the number of documents\n// deleted from the specified collection of the index, or rejects with an\n// error\nexport async function deleteAllCollectionDocuments({\n databaseName,\n collectionName, \n}) {\n if (typeOf(databaseName) !== \"string\" || !databaseName) {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n } else if (typeOf(collectionName) || !collectionName) {\n throw new Error(`Parameter \"collectionName\" should be a non-empty string`);\n }\n\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find database \"${databaseName}\"`);\n }\n const collection = database[collectionName];\n if (!collection) {\n throw new Error(`Could not find collection \"${collectionName}\" ` +\n `in database \"${databaseName}\"`);\n }\n\n try {\n return await collection.toCollection().delete();\n } catch(error) {\n console.error(\"error\", error);\n throw new Error(`Error deleting all documents ` +\n `from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n }\n}\n","// Define the \"deleteAllDatabaseDocuments\" index service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n\n// deleteAllDatabaseDocuments: async \n// returns a promise which either resolves with the number of documents\n// deleted from all collections of the specified database of the index, or\n// rejects with an error\nexport async function deleteAllDatabaseDocuments({\n databaseName\n}) {\n if (typeOf(databaseName) !== \"string\" || !databaseName) {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n }\n\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find database \"${databaseName}\"`);\n }\n\n const tables = database.tables;\n try {\n const deletedDocumentsCounts = await Promise.all(tables\n .map(table => table.toCollection().delete()));\n return deletedDocumentsCounts\n .reduce((accDeletedDocumentsCount, deletedDocumentsCount) => {\n return accDeletedDocumentsCount + deletedDocumentsCount;\n }, 0);\n } catch(error) {\n console.error(\"error\", error);\n throw new Error(`Error deleting all documents ` +\n `from index database \"${databaseName}\"`);\n }\n}\n","// Define the \"deleteDocumentByKey\" index service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n\n// deleteDocumentByKey: async\n// returns a promise which either resolves with the key of the document\n// which has been deleted from the indexedDB, or tejects with an error\nexport async function deleteDocumentByKey({\n databaseName,\n collectionName, \n documentKey\n}) {\n if (typeOf(databaseName) !== \"string\") {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n } else if (typeOf(collectionName) !== \"string\") {\n throw new Error(`Parameter \"collectionName\" should be a non-empty string`);\n } else if (typeOf(documentKey) !== \"string\") {\n throw new Error(`Parameter \"documentKey\" should be a non-empty string`);\n }\n\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find database \"${databaseName}\"`);\n }\n const collection = database[collectionName];\n if (!collection) {\n throw new Error(`Could not find collection \"${collectionName}\" ` + \n `in database \"${databaseName}\"`);\n }\n\n try {\n await collection.delete(documentKey);\n } catch(error) {\n console.warn(\"error\", error);\n throw new Error(`Error while deleting document \"${documentKey}\" ` +\n `from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n }\n return documentKey;\n}\n","// Define the \"getAllCollectionDocuments\" index service method\n\n// Import utility modules\nimport { removeDocumentsMetaData } from \"utilities/index\";\nimport { typeOf } from \"utilities/etc\";\n\n\n// getAllCollectionDocuments: async \n// returns a promise which either resolves with the array of documents\n// stored in the specified collection of the index, or rejects with an\n// error\nexport async function getAllCollectionDocuments({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\"\n}) {\n if (typeOf(databaseName) !== \"string\" || !databaseName) {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n } else if (typeOf(collectionName) !== \"string\" || !collectionName) {\n throw new Error(`Parameter \"collectionName\" should be a non-empty string`);\n }\n\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find database \"${databaseName}\"`);\n }\n const collection = database[collectionName];\n if (!collection) {\n throw new Error(`Could not find collection \"${collectionName}\" ` +\n `in database \"${databaseName}\"`);\n }\n\n let documentsData;\n try {\n documentsData = await collection.toArray();\n } catch(error) {\n console.error(\"error\", error);\n throw new Error(`Error getting all documents ` +\n `from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n }\n\n // Return documents without meta-data\n return removeDocumentsMetaData(documentsData);\n}\n","// Define the \"getAllDatabaseDocumentsCount\" index service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n\n// getAllDatabaseDocumentsCount: async \n// returns a promise which either resolves with the number of documents\n// present in all collections of the specified index database, or rejects\n// with an error\nexport async function getAllDatabaseDocumentsCount({\n databaseName\n}) {\n if (typeOf(databaseName) !== \"string\" || !databaseName) {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n }\n\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find database \"${databaseName}\"`);\n }\n\n const tables = database.tables;\n try {\n const documentsCounts = await Promise.all(tables\n .map(table => table.toCollection().count()));\n return documentsCounts\n .reduce((accDocumentsCount, documentsCount) => {\n return accDocumentsCount + documentsCount;\n }, 0);\n } catch(error) {\n console.error(\"error\", error);\n throw new Error(`Error counting all documents ` +\n `from database \"${databaseName}\"`);\n }\n}\n","// Define the \"getDocumentByKey\" index service method\n\n// Import library modules\nimport ms from \"ms\";\n\n// Import utility modules\nimport { removeDocumentMetaData } from \"utilities/index\";\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport {\n extendData,\n minResidualValidityMs\n} from \"parameters/index\";\n\n\n// getDocumentByKey: async\nexport async function getDocumentByKey({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\", \n documentKey = \"\",\n expiryExtension // optional: timestampMs | ms-compatible time string\n}) {\n if (typeOf(databaseName) !== \"string\" || !databaseName) {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n } else if (typeOf(collectionName) !== \"string\" || !collectionName) {\n throw new Error(`Parameter \"collectionName\" should be a non-empty string`);\n } else if (typeOf(documentKey) !== \"string\" || !documentKey) {\n throw new Error(`Parameter \"documentKey\" should be a non-empty string`);\n }\n\n // Get document from database collection\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find index database \"${databaseName}\"`);\n }\n const collection = database[collectionName];\n if (!collection) {\n throw new Error(`Could not find collection \"${collectionName}\" ` +\n `in index database \"${databaseName}\"`);\n }\n let documentData;\n try {\n documentData = await collection.get(documentKey);\n } catch(error) {\n console.error(\"error\", error);\n throw new Error(`Error getting document \"${documentKey}\" ` +\n `from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n }\n if (!documentData) {\n return documentData;\n }\n\n // Compute expiry extension in ms\n let expiryExtensionMs;\n switch (typeOf(expiryExtension)) {\n case \"number\": // assuming expiry expressed in ms\n expiryExtensionMs = expiryExtension;\n break;\n case \"string\": // assuming expiry is a time-string in ms format\n expiryExtensionMs = ms(expiryExtension);\n break;\n case \"undefined\":\n expiryExtensionMs = undefined;\n break;\n default:\n throw new Error(`Invalid expiry extension parameter \"${expiryExtension}\"`);\n }\n\n // Conditionally extend the validity of documents expiring soon\n // note: no need to await for the operation to complete (faster)\n const { _expOn: documentExpiresOnMs } = documentData || {};\n if (documentExpiresOnMs !== undefined) {\n const currentTimeStampMs = getCurrentTimeStampMs();\n const residualValidityMs = documentExpiresOnMs - currentTimeStampMs;\n if (residualValidityMs < minResidualValidityMs) { // update expiry\n const finalExpiryExtensionMs = expiryExtensionMs !== undefined ?\n expiryExtensionMs :\n extendData[collectionName] || extendData[\"default\"];\n const finalExpiryMs = \n documentExpiresOnMs - currentTimeStampMs + finalExpiryExtensionMs;\n this.saveDocument({ // no await here\n databaseName,\n collectionName,\n documentData,\n expiry: finalExpiryMs\n });\n console.debug(`Automatically extended expiry of document ` +\n `\"${documentKey}\" from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n }\n }\n\n // Return document without meta-data\n return removeDocumentMetaData(documentData);\n}\n","// Define the \"getDocumentsByKeys\" index service method\n\n// Import library modules\nimport ms from \"ms\";\n\n// Import utility modules\nimport { removeDocumentsMetaData } from \"utilities/index\";\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport {\n extendData,\n minResidualValidityMs\n} from \"parameters/index\";\n\n\n// getDocumentsByKeys: async\n// returns a promise which either resolves with an (possibly empty) array\n// of documents matching the specified array of keys, or rejects with an\n// error\nexport async function getDocumentsByKeys({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\", \n documentsKeys = [],\n expiryExtension // optional: timestampMs | ms-compatible time string\n}) {\n if (typeOf(databaseName) !== \"string\") {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n } else if (typeOf(collectionName) !== \"string\") {\n throw new Error(`Parameter \"collectionName\" should be a non-empty string`);\n } else if (typeOf(documentsKeys) !== \"array\" ||\n documentsKeys.some(documentData => typeOf(documentData) !== \"string\")) {\n throw new Error(`Parameter \"documentsKeys\" should be an array of strings`);\n }\n\n // Get documents from index\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find index database \"${databaseName}\"`);\n }\n const collection = database[collectionName];\n if (!collection) {\n throw new Error(`Could not find collection \"${collectionName}\" ` +\n `in index database \"${databaseName}\"`);\n }\n let documentsData;\n try {\n documentsData = await collection.bulkGet(documentsKeys);\n } catch(error) {\n console.error(\"error\", error);\n throw new Error(`Error while getting documents by keys [${documentsKeys}] ` +\n `from index collection \"${collectionName}\"` +\n `on database \"${databaseName}\"`);\n }\n\n // Compute expiry extension in ms\n let expiryExtensionMs;\n switch (typeOf(expiryExtension)) {\n case \"number\": // assuming expiry expressed in ms\n expiryExtensionMs = expiryExtension;\n break;\n case \"string\": // assuming expiry is a time-string in ms format\n expiryExtensionMs = ms(expiryExtension);\n break;\n case \"undefined\":\n expiryExtensionMs = undefined;\n break;\n default:\n throw new Error(`Invalid expiry extension parameter \"${expiryExtension}\"`);\n }\n\n // Conditionally extend the validity of documents expiring soon\n // note: no need to await for the operation to complete (faster)\n const currentTimeStampMs = getCurrentTimeStampMs();\n const extendedDocumentsData = documentsData\n .filter(documentData => {\n if (!documentData) {\n return false;\n }\n const { _expOn: documentExpiresOnMs } = documentData || {};\n if (!documentExpiresOnMs) {\n return false;\n }\n const residualValidityMs = documentExpiresOnMs - currentTimeStampMs;\n return residualValidityMs < minResidualValidityMs;\n });\n if (extendedDocumentsData.length > 0) {\n extendedDocumentsData.forEach(extendedDocumentData => { // no await here\n const { _expOn: documentExpiresOnMs } = extendedDocumentData || {};\n const finalExpiryExtensionMs = expiryExtensionMs !== undefined ?\n expiryExtensionMs :\n extendData[collectionName] || extendData[\"default\"];\n const finalExpiryMs = \n documentExpiresOnMs - currentTimeStampMs + finalExpiryExtensionMs;\n this.saveDocument({ // no await here\n databaseName,\n collectionName, \n documentData: extendedDocumentData,\n expiry: finalExpiryMs\n });\n });\n const extendedDocumentsKeys = extendedDocumentsData\n .map(({ key: documentKey }) => documentKey);\n console.debug(`Automatically extended expiry of documents ` +\n `[${extendedDocumentsKeys}] from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n }\n\n // Return documents without meta-data\n return removeDocumentsMetaData(documentsData);\n}\n","// Define the \"pruneExpiredDocuments\" index service method\n\n// Import utility modules\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { pluralize } from \"utilities/english\";\n\n\n// pruneExpiredDocuments: async\n// returns a promise which resolves after pruning expired documents from\n// their respeciive collections\nexport async function pruneExpiredDocuments({\n databaseName,\n collectionName\n}) {\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find database \"${databaseName}\"`);\n }\n const collection = database[collectionName];\n if (!collection) {\n throw new Error(`Could not find collection \"${collectionName}\" ` + \n `in database \"${databaseName}\"`);\n }\n\n // Query for expired documents\n const currentTimeStampMs = getCurrentTimeStampMs();\n const expiredDocumentsData = await collection\n .where(\"_expOn\")\n .below(currentTimeStampMs)\n .toArray();\n const expiredDocumentsCount = expiredDocumentsData.length;\n if (expiredDocumentsCount === 0) {\n return;\n }\n\n // Prune expired documents\n const documentString = pluralize(\"document\", expiredDocumentsCount);\n try {\n await collection\n .where(\"_expOn\")\n .below(currentTimeStampMs)\n .delete();\n } catch(error) {\n console.error(\"error\", error);\n console.warn(`Error pruning ${expiredDocumentsCount} expired ` +\n `${documentString} from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`, error);\n return;\n }\n console.debug(`Pruned ${expiredDocumentsCount} expired ` +\n `${documentString} from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n}\n","// Define the \"saveDocument\" index service method\n\n// Import library modules\nimport ms from \"ms\";\n\n// Import utility modules\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { addDocumentMetaData } from \"utilities/index\";\nimport { typeOf } from \"utilities/etc\";\n\n\n// saveDocument: async\n// returns a promise while either resolves with the key of the document\n// saved to the index, or rejects with an error\nexport async function saveDocument({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\",\n documentData = null,\n expiry // optional: timestampMs | ms-compatible time string\n}) {\n if (typeOf(databaseName) !== \"string\") {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n } else if (typeOf(collectionName) !== \"string\") {\n throw new Error(`Parameter \"collectionName\" should be a non-empty string`);\n } else if (typeOf(documentData) !== \"object\") {\n throw new Error(`Parameter \"documentData\" should be an object`);\n }\n\n let expiryMs;\n switch (typeOf(expiry)) {\n case \"number\": // assuming expiry expressed in ms\n expiryMs = expiry;\n break;\n case \"string\": // assuming expiry is a time-string in ms format\n expiryMs = ms(expiry);\n break;\n case \"undefined\":\n expiryMs = undefined;\n break;\n default:\n throw new Error(`Invalid expiry parameter \"${expiry}\"`);\n }\n\n // Add document's meta-data\n const currentTimeStampMs = getCurrentTimeStampMs();\n const documentMetaData = expiryMs ?\n { _expOn: currentTimeStampMs + expiryMs } : {};\n const finalDocumentData = addDocumentMetaData({\n collectionName,\n documentData,\n documentMetaData\n });\n\n // Save document to the index\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find index database \"${databaseName}\"`);\n }\n const collection = database[collectionName];\n if (!collection) {\n throw new Error(`Could not find collection \"${collectionName}\" ` + \n `in index database \"${databaseName}\"`);\n }\n\n let docKey;\n try {\n docKey = await collection.put(finalDocumentData);\n } catch(error) {\n console.error(\"error\", error);\n throw new Error(`Error saving document ` +\n `to index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n }\n return docKey;\n}\n","// Define the \"saveDocuments\" index service method\n\n// Import library modules\nimport ms from \"ms\";\n\n// Import utility modules\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { addDocumentsMetaData } from \"utilities/index\";\nimport { typeOf } from \"utilities/etc\";\n\n\n// saveDocuments: async\n// returns a promise while either resolves with an array of keys of the\n// documents saved to the index, or rejects with an error\nexport async function saveDocuments({\n databaseName = this.defaultDatabaseName,\n collectionName = \"\",\n documentsData = [],\n expiry // optional: timestampMs | ms-compatible time string\n}) {\n if (typeOf(databaseName) !== \"string\") {\n throw new Error(`Parameter \"databaseName\" should be a non-empty string`);\n } else if (typeOf(collectionName) !== \"string\") {\n throw new Error(`Parameter \"collectionName\" should be a non-empty string`);\n } else if (typeOf(documentsData) !== \"array\" ||\n documentsData.some(documentData => typeOf(documentData) !== \"object\")) {\n throw new Error(`Parameter \"documentsData\" should be an array of objects`);\n }\n\n let expiryMs;\n switch (typeOf(expiry)) {\n case \"number\": // assuming expiry expressed in ms\n expiryMs = expiry;\n break;\n case \"string\": // assuming expiry is a time-string in ms format\n expiryMs = ms(expiry);\n break;\n case \"undefined\":\n expiryMs = undefined;\n break;\n default:\n throw new Error(`Invalid expiry parameter \"${expiry}\"`);\n }\n\n // Add documents' meta-data\n const currentTimeStampMs = getCurrentTimeStampMs();\n const documentMetaData = expiryMs ?\n { _expOn: currentTimeStampMs + expiryMs } : {};\n const finalDocumentsData = addDocumentsMetaData({\n collectionName,\n documentsData,\n documentMetaData\n });\n\n // Save documents to the index\n const database = this.getDatabase(databaseName);\n if (!database) {\n throw new Error(`Could not find index database \"${databaseName}\"`);\n }\n const collection = database[collectionName];\n if (!collection) {\n throw new Error(`Could not find collection \"${collectionName}\" ` + \n `in index database \"${databaseName}\"`);\n }\n\n let documentsKeys = [];\n try {\n documentsKeys = await database\n .transaction(\"rw\", collection, async() => {\n const documentsKeys = await Promise.all(finalDocumentsData\n .map(documentData => collection.put(documentData)));\n return documentsKeys;\n });\n } catch(error) {\n console.error(\"error\", error);\n throw new Error(`Error saving documents ` +\n `to index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\"`);\n }\n return documentsKeys;\n}\n","// Define the \"setPruningInterval\" index service method\n\n// Import parameter modules\nimport {\n globalDatabaseData,\n userDatabaseData\n} from \"parameters/index\";\n\n// Define internal parameters\nconst isGlobalDatabaseNameRegExp = /^global$/;\nconst isUserDatabaseNameRegExp = /^user-/;\n\n\n// setPruningInterval: sync\n// sets an interval for pruning expired documents from the index\nexport function setPruningInterval(databaseName) {\n const databaseData = \n isGlobalDatabaseNameRegExp.test(databaseName) ? globalDatabaseData : \n isUserDatabaseNameRegExp.test(databaseName) ? userDatabaseData :\n null;\n if (!databaseData) {\n throw new Error(`Unsupported indexedDB database name \"${databaseName}\"`);\n }\n \n const { pruningIntervalMs } = databaseData;\n this._pruningIntervalsIds[databaseName] = setInterval(() => {\n const collectionsNames = Object.keys(databaseData.schema);\n Promise.all(collectionsNames.map(async(collectionName) => {\n try {\n await this.pruneExpiredDocuments({\n databaseName,\n collectionName\n });\n } catch(error) {\n console.warn(`Error while pruning expired documents ` +\n `from index collection \"${collectionName}\" ` +\n `on database \"${databaseName}\": \"${error.message}\"`);\n }\n }));\n }, pruningIntervalMs);\n}\n","// Define the \"inflection\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport EntityService from \"services/entity\"\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { getInflectionsByFilter } from \"./inflection/get-inflections-by-filter\";\n\n\n// Export the \"InflectionService\" class\n@inject(\n EntityService,\n GraphQLService, \n IndexService, \n UserService\n)\nexport default class InflectionService {\n\n constructor(\n entityService,\n graphQLService, \n indexService, \n userService\n ) {\n this.entityService = entityService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.userService = userService;\n }\n\n\n // Core methods\n async getInflectionByKey(inflectionKey) {\n return await this.entityService.getEntityByKey({\n entityName: \"inflection\",\n entityKey: inflectionKey || \"\"\n });\n }\n\n async getInflectionsByKeys(inflectionsKeys) {\n return await this.entityService.getInflectionsByKeys({\n entityName: \"inflection\",\n entitiesKeys: inflectionsKeys || []\n });\n }\n\n async getInflectionsByFilter(filterData) {\n return await getInflectionsByFilter.call(this, filterData);\n }\n\n}\n","// Define the \"getInflectionsByFilter\" service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateInflectionsByFilterQueryData } from \"queries/inflection\";\n\n\n// getInflectionsByFilter: async\n// returns a promise which either resolves with an array of inflections\n// matching the specified filter data object, or rejects with an error\nexport async function getInflectionsByFilter(filterData = {}) {\n if (typeOf(filterData) !== \"object\") {\n throw new Error(`Parameter \"filterData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Fetch inflections from server\n const queryData = generateInflectionsByFilterQueryData({ filterData });\n let inflectionsData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ inflectionsByFilter: inflectionsData = [] } = responseData || {});\n } catch(error) {\n console.warn(`Error fetching inflections by filter from server`, error);\n return [];\n }\n const inflectionsCount = inflectionsData.length;\n console.debug(`Fetched ${inflectionsCount} inflections by filter ` +\n `from server`);\n if (inflectionsCount === 0) {\n return [];\n }\n\n // Save inflections to the index\n try {\n await this.indexService.saveDocuments({\n collectionName: \"inflections\",\n documentsData: inflectionsData\n });\n console.debug(`Saved ${inflectionsCount} inflections to the index`);\n } catch(error) {\n console.warn(`Error saving ${inflectionsCount} inflections ` +\n `to the index`, error);\n }\n\n // Return inflections' data\n return inflectionsData;\n}\n","// Define the \"lemma\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport EntityService from \"services/entity\";\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { createLemma } from \"./lemma/create-lemma\";\nimport { deleteLemma } from \"./lemma/delete-lemma\";\nimport { getLemmaInflectionStatus } from \"./lemma/get-lemma-inflection-status\";\n\n\n// Export the \"LemmaService\" class\n@inject(\n EntityService,\n GraphQLService, \n IndexService, \n UserService\n)\nexport default class LemmaService {\n\n constructor(\n entityService,\n graphQLService, \n indexService, \n userService\n ) {\n this.entityService = entityService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.userService = userService;\n }\n\n\n // Core methods\n async getLemmaByKey(lemmaKey) {\n return await this.entityService.getEntityByKey({\n entityName: \"lemma\",\n entityKey: lemmaKey || \"\"\n });\n }\n\n async getLemmaInflectionStatus(lemmaKey) {\n return await getLemmaInflectionStatus.call(this, lemmaKey);\n }\n\n async createLemma(createLemmaData) {\n return await createLemma.call(this, createLemmaData);\n }\n\n async deleteLemma(lemmaKey) {\n return await deleteLemma.call(this, lemmaKey);\n }\n\n}\n","// Define the \"createLemma\" service method\n\n// Import utility modules\nimport { computeCreateLemmaData } from \"utilities/lemma\";\n\n// Import query modules\nimport { generateCreateLemmaMutationData } from \"queries/lemma\";\n\n\n// createLemma: async\n// returns a promise which either resolves with the lemma data object\n// created on the server/index, or rejects with an error\nexport async function createLemma(createLemmaData) {\n if (!this.userService.isSessionOpen) {\n throw new Error(`Could not create lemma on server (no open session)`);\n }\n\n // Create lemma on server\n createLemmaData = computeCreateLemmaData(createLemmaData);\n const queryData = generateCreateLemmaMutationData({ createLemmaData });\n let createdLemmaData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ createLemma: createdLemmaData } = responseData);\n } catch(error) {\n throw new Error(`Error creating lemma on server`);\n }\n const { key: createdLemmaKey } = createdLemmaData;\n console.debug(`Created lemma \"${createdLemmaKey}\" on server`);\n\n // Update the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"lemmas\",\n documentData: createdLemmaData\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error creating lemma \"${createdLemmaKey}\" on index`);\n }\n console.debug(`Created lemma \"${createdLemmaKey}\" on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Return created lemma's data\n return createdLemmaData;\n}\n","// Define the \"deleteLemma\" service method\n\n// Import query modules\nimport { generateDeleteLemmaMutationData } from \"queries/lemma\";\n\n\n// deleteLemma: async\n// returns a promise which either resolves with the key of the lemma that\n// has been deleted from the server/index, or rejects with an error\nexport async function deleteLemma(lemmaKey) {\n if (!lemmaKey) {\n throw new Error(`Missing required parameter \"lemmaKey\"`);\n }\n\n // Delete lemma on server\n const queryData = generateDeleteLemmaMutationData({ lemmaKey });\n let deletedLemmaKey;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ deleteLemma: deletedLemmaKey } = responseData);\n } catch(error) {\n throw new Error(`Error deleting lemma \"${lemmaKey}\" from server`);\n }\n console.debug(`Deleted lemma \"${lemmaKey}\" from server`);\n\n // Update the index\n try {\n await Promise.all([\n this.indexService.deleteDocumentByKey({\n collectionName: \"lemmas\",\n documentKey: deletedLemmaKey\n }),\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error deleting lemma \"${lemmaKey}\" from index`);\n }\n console.debug(`Deleted lemma \"${lemmaKey}\" from index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Return deleted lemma's key\n return lemmaKey;\n}\n","// Define the \"getLemmaInflectionStatus\" service method\n\n// Import query modules\nimport { generateLemmaInflectionStatusQueryData } from \"queries/lemma\";\n\n\n// getLemmaInflectionStatus: async\n// returns a promise which resolves with the (possibly null) inflection\n// status data for the specified lemma (by key)\nexport async function getLemmaInflectionStatus(lemmaKey) {\n if (!lemmaKey) {\n throw new Error(`Missing required parameter \"lemmaKey\"`);\n }\n\n // Fetch lemma's inflection status from server\n const queryData = generateLemmaInflectionStatusQueryData({ lemmaKey });\n let lemmaInflectionStatusData;\n try {\n const responseData = await this.graphQLService.query({ \n cachedResponse: false,\n queryData \n });\n ({ lemmaByKey: lemmaInflectionStatusData } = responseData);\n } catch(error) {\n console.warn(`Error fetching inflection status ` +\n `for lemma \"${lemmaKey}\"`, error);\n return null;\n }\n console.debug(`Fetched inflection status for lemma \"${lemmaKey}\"`);\n\n // Return lemma's data\n return lemmaInflectionStatusData;\n}\n"],"names":[],"sourceRoot":""}