@nxtedition/lib 28.0.12 → 28.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/app.js +278 -276
  2. package/package.json +2 -2
package/app.js CHANGED
@@ -614,10 +614,14 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
614
614
  compiler = makeTemplateCompiler({ ds, logger, ...appConfig.compiler })
615
615
  }
616
616
 
617
- const monitorProviders = {}
617
+ const monitorProviders = {
618
+ stats$: new rxjs.BehaviorSubject({}),
619
+ status$: new rxjs.BehaviorSubject({ messages: [] }),
620
+ }
618
621
 
619
- let stats$
620
622
  if (appConfig.stats) {
623
+ let stats$
624
+
621
625
  if (typeof appConfig.stats.subscribe === 'function') {
622
626
  stats$ = appConfig.stats
623
627
  } else if (typeof appConfig.stats === 'function') {
@@ -635,8 +639,6 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
635
639
 
636
640
  let statsMap
637
641
 
638
- monitorProviders.stats$ = new rxjs.BehaviorSubject({})
639
-
640
642
  const startTime = Date.now()
641
643
 
642
644
  appDestroyers.unshift(
@@ -657,24 +659,27 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
657
659
  arrayBuffers: mem.arrayBuffers,
658
660
  containerLimit: getContainerMemoryLimit(),
659
661
  containerUsage: getContainerMemoryUsage(),
660
- totalHeapTotal: 0,
661
- totalHeapUsed: 0,
662
- totalExternal: 0,
663
- totalArrayBuffers: 0,
664
662
  }
665
663
 
666
664
  const http = {
667
665
  userAgent,
668
666
  pending: globalThis._nxt_lib_http_pending?.size,
669
- totalPending: 0,
670
667
  }
671
668
 
672
669
  const undici = {
673
670
  sockets: globalThis.__undici_sockets?.size ?? 0,
674
- totalSockets: 0,
675
671
  }
676
672
 
677
673
  if (statsMap) {
674
+ memory.totalHeapTotal = 0
675
+ memory.totalHeapUsed = 0
676
+ memory.totalExternal = 0
677
+ memory.totalArrayBuffers = 0
678
+
679
+ http.totalPending = 0
680
+
681
+ undici.totalSockets = 0
682
+
678
683
  for (const stats of statsMap.values()) {
679
684
  memory.totalHeapTotal += stats.memory?.heapTotal ?? 0
680
685
  memory.totalHeapUsed += stats.memory?.heapUsed ?? 0
@@ -747,17 +752,15 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
747
752
 
748
753
  if (process.env.NODE_ENV === 'production') {
749
754
  appDestroyers.unshift(
750
- monitorProviders.stats$
751
- .pipe(rxjs.auditTime(10e3))
752
- .subscribe((stats) => {
753
- logger.debug(stats, 'STATS')
754
- })
755
+ monitorProviders.stats$.pipe(rxjs.auditTime(10e3)).subscribe((stats) => {
756
+ logger.debug(stats, 'STATS')
757
+ }),
755
758
  )
756
759
  }
757
760
  }
758
761
 
759
- let status$
760
762
  if (appConfig.status) {
763
+ let status$
761
764
  if (appConfig.status.subscribe) {
762
765
  status$ = appConfig.status
763
766
  } else if (typeof appConfig.status === 'function') {
@@ -776,285 +779,284 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
776
779
  status$ = rxjs.of({})
777
780
  }
778
781
 
779
- status$ = rxjs
780
- .combineLatest(
781
- [
782
- status$.pipe(
783
- rxjs.filter(Boolean),
784
- rxjs.map((xs) => (Array.isArray(xs) ? { messages: xs } : xs)),
785
- rxjs.catchError((err) => {
786
- logger.error({ err }, 'monitor.status')
787
- return rxjs.of([
788
- {
789
- id: 'app:user_monitor_status',
790
- level: 50,
791
- code: err.code,
792
- msg: err.message,
793
- },
794
- ])
795
- }),
796
- rxjs.startWith([]),
797
- rxjs.distinctUntilChanged(fp.isEqual),
798
- rxjs.repeatWhen((complete$) => complete$.pipe(rxjs.delay(10e3))),
799
- ),
800
- stats$.pipe(
801
- rxjs.map(({ memory, heap, utilization, undici, http }) => {
802
- const messages = []
803
-
804
- if (memory?.containerLimit) {
805
- const usagePercent = (memory.containerUsage / memory.containerLimit) * 100
806
- messages.push({
807
- id: 'app:container_memory_usage',
808
- level: usagePercent > 90 ? 50 : usagePercent > 70 ? 40 : 30,
809
- msg: `Memory Usage: ${usagePercent.toFixed(2)}%`,
810
- })
811
- }
812
-
813
- if (heap) {
814
- const usagePercent = (heap.used_heap_size / heap.heap_size_limit) * 100
815
- messages.push({
816
- id: 'app:heap_memory_usage',
817
- level: usagePercent > 90 ? 50 : usagePercent > 70 ? 40 : 30,
818
- msg: `Heap Usage: ${usagePercent.toFixed(2)}%`,
819
- })
820
- }
821
-
822
- if (utilization) {
823
- const elp = utilization.utilization * 100
824
- messages.push({
825
- id: 'app:event_loop_utilization',
826
- level: elp > 95 ? 50 : elp > 80 ? 40 : 30,
827
- msg: `Event Loop Utilization: ${elp.toFixed(2)}%`,
828
- })
829
- }
830
-
831
- if (undici) {
832
- messages.push({
833
- id: 'app:undici_upstream_sockets',
834
- level: undici.totalSockets > 8192 ? 50 : undici.totalSockets > 4096 ? 40 : 30,
835
- msg: `Undici: ${undici.totalSockets} upstream connected`,
836
- })
837
- }
838
-
839
- if (http) {
840
- messages.push({
841
- id: 'app:http_pending_requests',
842
- level: http.totalPending > 8192 ? 50 : http.totalPending > 4096 ? 40 : 30,
843
- msg: `HTTP: ${http.totalPending} pending requests`,
844
- })
845
- }
846
-
847
- return messages
848
- }),
849
- ),
850
- toobusy?.appLag$.pipe(
851
- rxjs.map((lag) =>
852
- lag == null
853
- ? []
854
- : [
855
- {
856
- id: 'app:toobusy_lag',
857
- level: lag > 1e3 ? 50 : lag > toobusy.maxLag ? 40 : 30,
858
- code: 'NXT_LAG',
859
- msg: `Lag: ${lag.toFixed(2)} ms`,
860
- },
861
- ],
782
+ appDestroyers.unshift(
783
+ rxjs
784
+ .combineLatest(
785
+ [
786
+ status$.pipe(
787
+ rxjs.filter(Boolean),
788
+ rxjs.map((xs) => (Array.isArray(xs) ? { messages: xs } : xs)),
789
+ rxjs.catchError((err) => {
790
+ logger.error({ err }, 'monitor.status')
791
+ return rxjs.of([
792
+ {
793
+ id: 'app:user_monitor_status',
794
+ level: 50,
795
+ code: err.code,
796
+ msg: err.message,
797
+ },
798
+ ])
799
+ }),
800
+ rxjs.startWith([]),
801
+ rxjs.distinctUntilChanged(fp.isEqual),
802
+ rxjs.repeatWhen((complete$) => complete$.pipe(rxjs.delay(10e3))),
862
803
  ),
863
- ) ?? rxjs.of([]),
864
- underPressure?.pressure$?.pipe(
865
- rxjs.map((pressure) =>
866
- pressure == null
867
- ? []
868
- : [
869
- {
870
- id: 'app:under_pressure',
871
- level: 40,
872
- code: 'NXT_PRESSURE',
873
- msg: `Under Pressure`,
874
- },
875
- ],
804
+ monitorProviders.stats$?.pipe(
805
+ rxjs.map(({ memory, heap, utilization, undici, http }) => {
806
+ const messages = []
807
+
808
+ if (memory?.containerLimit) {
809
+ const usagePercent = (memory.containerUsage / memory.containerLimit) * 100
810
+ messages.push({
811
+ id: 'app:container_memory_usage',
812
+ level: usagePercent > 90 ? 50 : usagePercent > 70 ? 40 : 30,
813
+ msg: `Memory Usage: ${usagePercent.toFixed(2)}%`,
814
+ })
815
+ }
816
+
817
+ if (heap) {
818
+ const usagePercent = (heap.used_heap_size / heap.heap_size_limit) * 100
819
+ messages.push({
820
+ id: 'app:heap_memory_usage',
821
+ level: usagePercent > 90 ? 50 : usagePercent > 70 ? 40 : 30,
822
+ msg: `Heap Usage: ${usagePercent.toFixed(2)}%`,
823
+ })
824
+ }
825
+
826
+ if (utilization) {
827
+ const elp = utilization.utilization * 100
828
+ messages.push({
829
+ id: 'app:event_loop_utilization',
830
+ level: elp > 95 ? 50 : elp > 80 ? 40 : 30,
831
+ msg: `Event Loop Utilization: ${elp.toFixed(2)}%`,
832
+ })
833
+ }
834
+
835
+ if (undici) {
836
+ messages.push({
837
+ id: 'app:undici_upstream_sockets',
838
+ level: undici.totalSockets > 8192 ? 50 : undici.totalSockets > 4096 ? 40 : 30,
839
+ msg: `Undici: ${undici.totalSockets} upstream connected`,
840
+ })
841
+ }
842
+
843
+ if (http) {
844
+ messages.push({
845
+ id: 'app:http_pending_requests',
846
+ level: http.totalPending > 8192 ? 50 : http.totalPending > 4096 ? 40 : 30,
847
+ msg: `HTTP: ${http.totalPending} pending requests`,
848
+ })
849
+ }
850
+
851
+ return messages
852
+ }),
876
853
  ),
877
- ) ?? rxjs.of([]),
878
- couch
879
- ? rxjs.timer(0, 10e3).pipe(
880
- rxjs.exhaustMap(async () => {
881
- try {
882
- await couch.up()
883
- return [
854
+ toobusy?.appLag$.pipe(
855
+ rxjs.map((lag) =>
856
+ lag == null
857
+ ? []
858
+ : [
884
859
  {
885
- id: 'app:couch',
886
- level: 30,
887
- msg: 'Couch: connected',
860
+ id: 'app:toobusy_lag',
861
+ level: lag > 1e3 ? 50 : lag > toobusy.maxLag ? 40 : 30,
862
+ code: 'NXT_LAG',
863
+ msg: `Lag: ${lag.toFixed(2)} ms`,
888
864
  },
889
- ]
890
- } catch (err) {
891
- return [
865
+ ],
866
+ ),
867
+ ) ?? rxjs.of([]),
868
+ underPressure?.pressure$?.pipe(
869
+ rxjs.map((pressure) =>
870
+ pressure == null
871
+ ? []
872
+ : [
892
873
  {
893
- id: 'app:couch',
874
+ id: 'app:under_pressure',
894
875
  level: 40,
895
- code: err.code,
896
- msg: 'Couch: ' + err.message,
876
+ code: 'NXT_PRESSURE',
877
+ msg: `Under Pressure`,
897
878
  },
898
- ]
899
- }
900
- }),
901
- rxjs.startWith([]),
902
- rxjs.distinctUntilChanged(fp.isEqual),
903
- )
904
- : rxjs.of({}),
905
- ds
906
- ? rxjs.fromEvent(ds, 'connectionStateChanged').pipe(
907
- rxjs.map((connectionState) =>
908
- connectionState === 'OPEN'
909
- ? [
879
+ ],
880
+ ),
881
+ ) ?? rxjs.of([]),
882
+ couch
883
+ ? rxjs.timer(0, 10e3).pipe(
884
+ rxjs.exhaustMap(async () => {
885
+ try {
886
+ await couch.up()
887
+ return [
910
888
  {
911
- id: 'app:ds_connection_state',
889
+ id: 'app:couch',
912
890
  level: 30,
913
- msg: 'Deepstream: connected',
914
- data: { connectionState },
891
+ msg: 'Couch: connected',
915
892
  },
916
893
  ]
917
- : [
894
+ } catch (err) {
895
+ return [
918
896
  {
919
- id: 'app:ds_connection_state',
897
+ id: 'app:couch',
920
898
  level: 40,
921
- msg: 'Deepstream: connecting',
922
- data: { connectionState },
899
+ code: err.code,
900
+ msg: 'Couch: ' + err.message,
923
901
  },
924
- ],
925
- ),
902
+ ]
903
+ }
904
+ }),
905
+ rxjs.startWith([]),
906
+ rxjs.distinctUntilChanged(fp.isEqual),
907
+ )
908
+ : rxjs.of({}),
909
+ ds
910
+ ? rxjs.fromEvent(ds, 'connectionStateChanged').pipe(
911
+ rxjs.map((connectionState) =>
912
+ connectionState === 'OPEN'
913
+ ? [
914
+ {
915
+ id: 'app:ds_connection_state',
916
+ level: 30,
917
+ msg: 'Deepstream: connected',
918
+ data: { connectionState },
919
+ },
920
+ ]
921
+ : [
922
+ {
923
+ id: 'app:ds_connection_state',
924
+ level: 40,
925
+ msg: 'Deepstream: connecting',
926
+ data: { connectionState },
927
+ },
928
+ ],
929
+ ),
930
+ )
931
+ : rxjs.of([]),
932
+ ds
933
+ ? rxjs.timer(0, 10e3).pipe(
934
+ rxjs.exhaustMap(async () => {
935
+ const messages = []
936
+
937
+ if (ds.stats.record.records > 100e3) {
938
+ messages.push({
939
+ id: 'app:ds_record_records',
940
+ level: 40,
941
+ code: 'NXT_DEEPSTREAM_RECORDS_RECORDS',
942
+ msg: 'Deepstream: ' + ds.stats.record.records + ' records',
943
+ })
944
+ }
945
+
946
+ if (ds.stats.record.pruning > 100e3) {
947
+ messages.push({
948
+ id: 'app:ds_record_pruning',
949
+ level: 40,
950
+ code: 'NXT_DEEPSTREAM_RECORDS_PRUNING',
951
+ msg: 'Deepstream: ' + ds.stats.record.pruning + ' pruning',
952
+ })
953
+ }
954
+
955
+ if (ds.stats.record.pending > 10e3) {
956
+ messages.push({
957
+ id: 'app:ds_record_pending',
958
+ level: 40,
959
+ code: 'NXT_DEEPSTREAM_RECORDS_PENDING',
960
+ msg: 'Deepstream: ' + ds.stats.record.pending + ' pending',
961
+ })
962
+ }
963
+
964
+ if (ds.stats.record.updating > 10e3) {
965
+ messages.push({
966
+ id: 'app:ds_record_updating',
967
+ level: 40,
968
+ code: 'NXT_DEEPSTREAM_RECORDS_UPDATING',
969
+ msg: 'Deepstream: ' + ds.stats.record.updating + ' updating',
970
+ })
971
+ }
972
+
973
+ if (ds.stats.record.patching > 10e3) {
974
+ messages.push({
975
+ id: 'app:ds_record_patching',
976
+ level: 40,
977
+ code: 'NXT_DEEPSTREAM_RECORDS_PATCHING',
978
+ msg: 'Deepstream: ' + ds.stats.record.patching + ' patching',
979
+ })
980
+ }
981
+
982
+ return messages
983
+ }),
984
+ )
985
+ : rxjs.of([]),
986
+ rxjs.timer(0, 10e3),
987
+ ].filter(Boolean),
988
+ )
989
+ .pipe(
990
+ rxjs.auditTime(1e3),
991
+ rxjs.map(([status, lag, couch, ds]) => {
992
+ const messages = [
993
+ lag,
994
+ couch,
995
+ ds,
996
+ [
997
+ status?.messages,
998
+ fp.map((x) => (fp.isString(x) ? { msg: x, level: 40 } : x), status?.warnings),
999
+ status,
1000
+ ].find((x) => fp.isArray(x) && !fp.isEmpty(x)) ?? [],
1001
+ ]
1002
+ .flat()
1003
+ .filter((x) => fp.isPlainObject(x) && !fp.isEmpty(x))
1004
+ .map((message) =>
1005
+ message.msg || !message.message
1006
+ ? message
1007
+ : {
1008
+ ...message,
1009
+ message: undefined,
1010
+ msg: message.message,
1011
+ },
926
1012
  )
927
- : rxjs.of([]),
928
- ds
929
- ? rxjs.timer(0, 10e3).pipe(
930
- rxjs.exhaustMap(async () => {
931
- const messages = []
932
-
933
- if (ds.stats.record.records > 100e3) {
934
- messages.push({
935
- id: 'app:ds_record_records',
936
- level: 40,
937
- code: 'NXT_DEEPSTREAM_RECORDS_RECORDS',
938
- msg: 'Deepstream: ' + ds.stats.record.records + ' records',
939
- })
940
- }
941
-
942
- if (ds.stats.record.pruning > 100e3) {
943
- messages.push({
944
- id: 'app:ds_record_pruning',
945
- level: 40,
946
- code: 'NXT_DEEPSTREAM_RECORDS_PRUNING',
947
- msg: 'Deepstream: ' + ds.stats.record.pruning + ' pruning',
948
- })
949
- }
950
-
951
- if (ds.stats.record.pending > 10e3) {
952
- messages.push({
953
- id: 'app:ds_record_pending',
954
- level: 40,
955
- code: 'NXT_DEEPSTREAM_RECORDS_PENDING',
956
- msg: 'Deepstream: ' + ds.stats.record.pending + ' pending',
957
- })
958
- }
959
-
960
- if (ds.stats.record.updating > 10e3) {
961
- messages.push({
962
- id: 'app:ds_record_updating',
963
- level: 40,
964
- code: 'NXT_DEEPSTREAM_RECORDS_UPDATING',
965
- msg: 'Deepstream: ' + ds.stats.record.updating + ' updating',
966
- })
967
- }
968
-
969
- if (ds.stats.record.patching > 10e3) {
970
- messages.push({
971
- id: 'app:ds_record_patching',
972
- level: 40,
973
- code: 'NXT_DEEPSTREAM_RECORDS_PATCHING',
974
- msg: 'Deepstream: ' + ds.stats.record.patching + ' patching',
975
- })
976
- }
977
-
978
- return messages
979
- }),
1013
+ .map((message) =>
1014
+ message.id
1015
+ ? message
1016
+ : {
1017
+ ...message,
1018
+ id: hashString(
1019
+ [message.msg, message].find(fp.isString) ?? JSON.stringify(message),
1020
+ ),
1021
+ },
980
1022
  )
981
- : rxjs.of([]),
982
- rxjs.timer(0, 10e3),
983
- ].filter(Boolean),
984
- )
985
- .pipe(
986
- rxjs.auditTime(1e3),
987
- rxjs.map(([status, lag, couch, ds]) => {
988
- const messages = [
989
- lag,
990
- couch,
991
- ds,
992
- [
993
- status?.messages,
994
- fp.map((x) => (fp.isString(x) ? { msg: x, level: 40 } : x), status?.warnings),
995
- status,
996
- ].find((x) => fp.isArray(x) && !fp.isEmpty(x)) ?? [],
997
- ]
998
- .flat()
999
- .filter((x) => fp.isPlainObject(x) && !fp.isEmpty(x))
1000
- .map((message) =>
1001
- message.msg || !message.message
1002
- ? message
1003
- : {
1004
- ...message,
1005
- message: undefined,
1006
- msg: message.message,
1007
- },
1008
- )
1009
- .map((message) =>
1010
- message.id
1011
- ? message
1012
- : {
1013
- ...message,
1014
- id: hashString(
1015
- [message.msg, message].find(fp.isString) ?? JSON.stringify(message),
1016
- ),
1017
- },
1018
- )
1019
1023
 
1020
- return { ...status, messages, timestamp: Date.now() }
1021
- }),
1022
- rxjs.catchError((err) => {
1023
- logger.error({ err }, 'monitor.status')
1024
- return rxjs.of({
1025
- messages: [{ id: 'app:monitor_status', level: 50, code: err.code, msg: err.message }],
1026
- })
1027
- }),
1028
- rxjs.repeatWhen((complete$) => complete$.pipe(rxjs.delay(10e3))),
1029
- rxjs.startWith({}),
1030
- rxjs.distinctUntilChanged(fp.isEqual),
1031
- rxjs.publishReplay(1),
1032
- rxjs.refCount(),
1033
- )
1024
+ return { ...status, messages, timestamp: Date.now() }
1025
+ }),
1026
+ rxjs.catchError((err) => {
1027
+ logger.error({ err }, 'monitor.status')
1028
+ return rxjs.of({
1029
+ messages: [{ id: 'app:monitor_status', level: 50, code: err.code, msg: err.message }],
1030
+ })
1031
+ }),
1032
+ rxjs.repeatWhen((complete$) => complete$.pipe(rxjs.delay(10e3))),
1033
+ rxjs.startWith({}),
1034
+ rxjs.distinctUntilChanged(fp.isEqual),
1035
+ )
1036
+ .subscribe(monitorProviders.status$),
1037
+ )
1034
1038
 
1035
- const loggerSubscription = status$
1036
- .pipe(rxjs.auditTime(1e3), rxjs.pluck('messages'), rxjs.startWith([]), rxjs.pairwise())
1037
- .subscribe(([prev, next]) => {
1038
- for (const { level, msg: status, ...message } of fp.differenceBy('id', next, prev)) {
1039
- if (level >= 50) {
1040
- logger.error({ ...message, status }, `status added`)
1041
- } else if (level >= 40) {
1042
- logger.warn({ ...message, status }, `status added`)
1043
- } else {
1044
- logger.info({ ...message, status }, `status added`)
1039
+ appDestroyers.unshift(
1040
+ monitorProviders.status$
1041
+ .pipe(rxjs.auditTime(1e3), rxjs.pluck('messages'), rxjs.startWith([]), rxjs.pairwise())
1042
+ .subscribe(([prev, next]) => {
1043
+ for (const { level, msg: status, ...message } of fp.differenceBy('id', next, prev)) {
1044
+ if (level >= 50) {
1045
+ logger.error({ ...message, status }, `status added`)
1046
+ } else if (level >= 40) {
1047
+ logger.warn({ ...message, status }, `status added`)
1048
+ } else {
1049
+ logger.info({ ...message, status }, `status added`)
1050
+ }
1045
1051
  }
1046
- }
1047
1052
 
1048
- for (const { level, msg: status, ...message } of fp.differenceBy('id', prev, next)) {
1049
- if (level >= 40) {
1050
- logger.info({ ...message, status }, `status removed`)
1053
+ for (const { level, msg: status, ...message } of fp.differenceBy('id', prev, next)) {
1054
+ if (level >= 40) {
1055
+ logger.info({ ...message, status }, `status removed`)
1056
+ }
1051
1057
  }
1052
- }
1053
- })
1054
-
1055
- monitorProviders.status$ = status$
1056
-
1057
- appDestroyers.unshift(loggerSubscription)
1058
+ }),
1059
+ )
1058
1060
  }
1059
1061
 
1060
1062
  if (ds && Object.keys(monitorProviders).length && appConfig.monitor !== false) {
@@ -1288,13 +1290,13 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
1288
1290
  try {
1289
1291
  if (req.method === 'GET' && req.url === '/stats') {
1290
1292
  res.setHeader('content-type', 'application/json')
1291
- res.end(JSON.stringify(stats$.value))
1293
+ res.end(JSON.stringify(monitorProviders.stats$.value))
1292
1294
  return
1293
1295
  }
1294
1296
 
1295
1297
  if (req.method === 'GET' && req.url === '/status') {
1296
1298
  res.setHeader('content-type', 'application/json')
1297
- res.end(JSON.stringify(status$.value))
1299
+ res.end(JSON.stringify(monitorProviders.status$.value))
1298
1300
  return
1299
1301
  }
1300
1302
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "28.0.12",
3
+ "version": "28.0.13",
4
4
  "license": "UNLICENSED",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -92,5 +92,5 @@
92
92
  "pino": ">=7.0.0",
93
93
  "rxjs": "^7.0.0"
94
94
  },
95
- "gitHead": "e0d096f01e85551f3caac31e96f8d144f4c17f5a"
95
+ "gitHead": "ab3426c105b6b59a7a092a3a5ae1e563ea1253a4"
96
96
  }