mongodash 2.0.0 → 2.1.1

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 (165) hide show
  1. package/README.md +107 -23
  2. package/dist/dashboard/index.html +40 -0
  3. package/dist/lib/playground/server.js +124 -0
  4. package/dist/lib/playground/server.js.map +1 -0
  5. package/dist/lib/src/ConcurrentRunner.js +141 -0
  6. package/dist/lib/src/ConcurrentRunner.js.map +1 -0
  7. package/dist/lib/{OnError.js → src/OnError.js} +2 -9
  8. package/dist/lib/src/OnError.js.map +1 -0
  9. package/dist/lib/{OnInfo.js → src/OnInfo.js} +5 -9
  10. package/dist/lib/src/OnInfo.js.map +1 -0
  11. package/dist/lib/{createContinuousLock.js → src/createContinuousLock.js} +4 -9
  12. package/dist/lib/src/createContinuousLock.js.map +1 -0
  13. package/dist/lib/{cronTasks.js → src/cronTasks.js} +128 -79
  14. package/dist/lib/src/cronTasks.js.map +1 -0
  15. package/dist/lib/{getCollection.js → src/getCollection.js} +1 -8
  16. package/dist/lib/src/getCollection.js.map +1 -0
  17. package/dist/lib/{getMongoClient.js → src/getMongoClient.js} +1 -8
  18. package/dist/lib/src/getMongoClient.js.map +1 -0
  19. package/dist/lib/src/globalsCollection.js +3 -0
  20. package/dist/lib/src/globalsCollection.js.map +1 -0
  21. package/dist/lib/src/index.js +94 -0
  22. package/dist/lib/src/index.js.map +1 -0
  23. package/dist/lib/src/initPromise.js +9 -0
  24. package/dist/lib/src/initPromise.js.map +1 -0
  25. package/dist/lib/src/mongoCompatibility.js +3 -0
  26. package/dist/lib/src/mongoCompatibility.js.map +1 -0
  27. package/dist/lib/src/parseInterval.js +53 -0
  28. package/dist/lib/src/parseInterval.js.map +1 -0
  29. package/dist/lib/src/prefixFilterKeys.js +62 -0
  30. package/dist/lib/src/prefixFilterKeys.js.map +1 -0
  31. package/dist/lib/src/processInBatches.js +39 -0
  32. package/dist/lib/src/processInBatches.js.map +1 -0
  33. package/dist/lib/src/reactiveTasks/LeaderElector.js +148 -0
  34. package/dist/lib/src/reactiveTasks/LeaderElector.js.map +1 -0
  35. package/dist/lib/src/reactiveTasks/MetricsCollector.js +403 -0
  36. package/dist/lib/src/reactiveTasks/MetricsCollector.js.map +1 -0
  37. package/dist/lib/src/reactiveTasks/ReactiveTaskManager.js +281 -0
  38. package/dist/lib/src/reactiveTasks/ReactiveTaskManager.js.map +1 -0
  39. package/dist/lib/src/reactiveTasks/ReactiveTaskOps.js +178 -0
  40. package/dist/lib/src/reactiveTasks/ReactiveTaskOps.js.map +1 -0
  41. package/dist/lib/src/reactiveTasks/ReactiveTaskPlanner.js +436 -0
  42. package/dist/lib/src/reactiveTasks/ReactiveTaskPlanner.js.map +1 -0
  43. package/dist/lib/src/reactiveTasks/ReactiveTaskReconciler.js +211 -0
  44. package/dist/lib/src/reactiveTasks/ReactiveTaskReconciler.js.map +1 -0
  45. package/dist/lib/src/reactiveTasks/ReactiveTaskRegistry.js +177 -0
  46. package/dist/lib/src/reactiveTasks/ReactiveTaskRegistry.js.map +1 -0
  47. package/dist/lib/src/reactiveTasks/ReactiveTaskRepository.js +348 -0
  48. package/dist/lib/src/reactiveTasks/ReactiveTaskRepository.js.map +1 -0
  49. package/dist/lib/src/reactiveTasks/ReactiveTaskRetryStrategy.js +146 -0
  50. package/dist/lib/src/reactiveTasks/ReactiveTaskRetryStrategy.js.map +1 -0
  51. package/dist/lib/src/reactiveTasks/ReactiveTaskTypes.js +27 -0
  52. package/dist/lib/src/reactiveTasks/ReactiveTaskTypes.js.map +1 -0
  53. package/dist/lib/src/reactiveTasks/ReactiveTaskWorker.js +179 -0
  54. package/dist/lib/src/reactiveTasks/ReactiveTaskWorker.js.map +1 -0
  55. package/dist/lib/src/reactiveTasks/compileWatchProjection.js +58 -0
  56. package/dist/lib/src/reactiveTasks/compileWatchProjection.js.map +1 -0
  57. package/dist/lib/src/reactiveTasks/index.js +291 -0
  58. package/dist/lib/src/reactiveTasks/index.js.map +1 -0
  59. package/dist/lib/src/reactiveTasks/queryToExpression.js +153 -0
  60. package/dist/lib/src/reactiveTasks/queryToExpression.js.map +1 -0
  61. package/dist/lib/src/reactiveTasks/validateTaskFilter.js +81 -0
  62. package/dist/lib/src/reactiveTasks/validateTaskFilter.js.map +1 -0
  63. package/dist/lib/src/task-management/OperationalTaskController.js +155 -0
  64. package/dist/lib/src/task-management/OperationalTaskController.js.map +1 -0
  65. package/dist/lib/src/task-management/index.js +20 -0
  66. package/dist/lib/src/task-management/index.js.map +1 -0
  67. package/dist/lib/src/task-management/serveDashboard.js +142 -0
  68. package/dist/lib/src/task-management/serveDashboard.js.map +1 -0
  69. package/dist/lib/src/task-management/types.js +3 -0
  70. package/dist/lib/src/task-management/types.js.map +1 -0
  71. package/dist/lib/{withLock.js → src/withLock.js} +2 -10
  72. package/dist/lib/src/withLock.js.map +1 -0
  73. package/dist/lib/{withTransaction.js → src/withTransaction.js} +3 -10
  74. package/dist/lib/src/withTransaction.js.map +1 -0
  75. package/dist/lib/tools/check-db-connection.js +21 -0
  76. package/dist/lib/tools/check-db-connection.js.map +1 -0
  77. package/dist/lib/tools/clean-testing-databases.js +5 -0
  78. package/dist/lib/tools/clean-testing-databases.js.map +1 -0
  79. package/dist/lib/tools/prepare-republish.js +20 -0
  80. package/dist/lib/tools/prepare-republish.js.map +1 -0
  81. package/dist/lib/tools/test-matrix-local.js +205 -0
  82. package/dist/lib/tools/test-matrix-local.js.map +1 -0
  83. package/dist/lib/tools/testingDatabase.js +48 -0
  84. package/dist/lib/tools/testingDatabase.js.map +1 -0
  85. package/dist/types/playground/server.d.ts +1 -0
  86. package/dist/types/src/ConcurrentRunner.d.ts +30 -0
  87. package/dist/types/{OnInfo.d.ts → src/OnInfo.d.ts} +1 -1
  88. package/dist/types/{cronTasks.d.ts → src/cronTasks.d.ts} +44 -1
  89. package/dist/types/src/globalsCollection.d.ts +4 -0
  90. package/dist/types/src/index.d.ts +28 -0
  91. package/dist/types/src/mongoCompatibility.d.ts +29 -0
  92. package/dist/types/src/parseInterval.d.ts +12 -0
  93. package/dist/types/src/prefixFilterKeys.d.ts +11 -0
  94. package/dist/types/src/processInBatches.d.ts +10 -0
  95. package/dist/types/src/reactiveTasks/LeaderElector.d.ts +42 -0
  96. package/dist/types/src/reactiveTasks/MetricsCollector.d.ts +73 -0
  97. package/dist/types/src/reactiveTasks/ReactiveTaskManager.d.ts +18 -0
  98. package/dist/types/src/reactiveTasks/ReactiveTaskOps.d.ts +17 -0
  99. package/dist/types/src/reactiveTasks/ReactiveTaskPlanner.d.ts +62 -0
  100. package/dist/types/src/reactiveTasks/ReactiveTaskReconciler.d.ts +29 -0
  101. package/dist/types/src/reactiveTasks/ReactiveTaskRegistry.d.ts +34 -0
  102. package/dist/types/src/reactiveTasks/ReactiveTaskRepository.d.ts +59 -0
  103. package/dist/types/src/reactiveTasks/ReactiveTaskRetryStrategy.d.ts +21 -0
  104. package/dist/types/src/reactiveTasks/ReactiveTaskTypes.d.ts +389 -0
  105. package/dist/types/src/reactiveTasks/ReactiveTaskWorker.d.ts +36 -0
  106. package/dist/types/src/reactiveTasks/compileWatchProjection.d.ts +12 -0
  107. package/dist/types/src/reactiveTasks/index.d.ts +82 -0
  108. package/dist/types/src/reactiveTasks/queryToExpression.d.ts +13 -0
  109. package/dist/types/src/reactiveTasks/validateTaskFilter.d.ts +10 -0
  110. package/dist/types/src/task-management/OperationalTaskController.d.ts +59 -0
  111. package/dist/types/src/task-management/index.d.ts +3 -0
  112. package/dist/types/src/task-management/serveDashboard.d.ts +12 -0
  113. package/dist/types/src/task-management/types.d.ts +95 -0
  114. package/dist/types/tools/check-db-connection.d.ts +2 -0
  115. package/dist/types/tools/clean-testing-databases.d.ts +1 -0
  116. package/dist/types/tools/prepare-republish.d.ts +2 -0
  117. package/dist/types/tools/test-matrix-local.d.ts +1 -0
  118. package/dist/types/tools/testingDatabase.d.ts +2 -0
  119. package/docs/.vitepress/cache/deps/_metadata.json +31 -0
  120. package/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js +12824 -0
  121. package/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js.map +7 -0
  122. package/docs/.vitepress/cache/deps/package.json +3 -0
  123. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4505 -0
  124. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  125. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +9731 -0
  126. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  127. package/docs/.vitepress/cache/deps/vue.js +347 -0
  128. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  129. package/docs/.vitepress/config.mts +50 -0
  130. package/docs/.vitepress/theme/index.ts +4 -0
  131. package/docs/.vitepress/theme/style.css +21 -0
  132. package/docs/assets/dashboard.png +0 -0
  133. package/docs/cron-tasks.md +172 -0
  134. package/docs/dashboard.md +117 -0
  135. package/docs/getters.md +31 -0
  136. package/docs/getting-started.md +186 -0
  137. package/docs/index.md +32 -0
  138. package/docs/initialization.md +59 -0
  139. package/docs/process-in-batches.md +73 -0
  140. package/docs/public/logo.png +0 -0
  141. package/docs/reactive-tasks.md +913 -0
  142. package/docs/with-lock.md +45 -0
  143. package/docs/with-transaction.md +65 -0
  144. package/grafana/reactive_tasks.json +765 -0
  145. package/package.json +131 -116
  146. package/dist/lib/OnError.js.map +0 -1
  147. package/dist/lib/OnInfo.js.map +0 -1
  148. package/dist/lib/createContinuousLock.js.map +0 -1
  149. package/dist/lib/cronTasks.js.map +0 -1
  150. package/dist/lib/getCollection.js.map +0 -1
  151. package/dist/lib/getMongoClient.js.map +0 -1
  152. package/dist/lib/index.js +0 -64
  153. package/dist/lib/index.js.map +0 -1
  154. package/dist/lib/initPromise.js +0 -17
  155. package/dist/lib/initPromise.js.map +0 -1
  156. package/dist/lib/withLock.js.map +0 -1
  157. package/dist/lib/withTransaction.js.map +0 -1
  158. package/dist/types/index.d.ts +0 -17
  159. /package/dist/types/{OnError.d.ts → src/OnError.d.ts} +0 -0
  160. /package/dist/types/{createContinuousLock.d.ts → src/createContinuousLock.d.ts} +0 -0
  161. /package/dist/types/{getCollection.d.ts → src/getCollection.d.ts} +0 -0
  162. /package/dist/types/{getMongoClient.d.ts → src/getMongoClient.d.ts} +0 -0
  163. /package/dist/types/{initPromise.d.ts → src/initPromise.d.ts} +0 -0
  164. /package/dist/types/{withLock.d.ts → src/withLock.d.ts} +0 -0
  165. /package/dist/types/{withTransaction.d.ts → src/withTransaction.d.ts} +0 -0
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prefixFilterKeys = prefixFilterKeys;
4
+ /**
5
+ * Recursively prefixes all keys in a MongoDB filter object.
6
+ * This is used to apply a filter meant for a document to a field
7
+ * within a larger document, such as 'fullDocument' in a change stream event.
8
+ *
9
+ * @param filter The filter object to process.
10
+ * @param prefix The prefix to add to each key (e.g., 'fullDocument').
11
+ * @returns A new filter object with all keys prefixed.
12
+ */
13
+ function prefixFilterKeys(filter, prefix) {
14
+ const newFilter = {};
15
+ for (const key in filter) {
16
+ if (Object.prototype.hasOwnProperty.call(filter, key)) {
17
+ const value = filter[key];
18
+ if (key === '$expr') {
19
+ // Handle $expr specially: we need to prefix field paths inside it
20
+ newFilter[key] = prefixExpr(value, prefix);
21
+ }
22
+ else if (key.startsWith('$')) {
23
+ // Handle logical operators like $or, $and, $nor
24
+ if (Array.isArray(value) && (key === '$or' || key === '$and' || key === '$nor')) {
25
+ newFilter[key] = value.map((item) => typeof item === 'object' && item !== null && !Array.isArray(item) ? prefixFilterKeys(item, prefix) : item); // eslint-disable-line @typescript-eslint/no-explicit-any
26
+ }
27
+ else {
28
+ // For other operators ($in, $eq, etc.), keep them as they are.
29
+ newFilter[key] = value;
30
+ }
31
+ }
32
+ else {
33
+ // For regular field names, add the prefix.
34
+ const newKey = `${prefix}.${key}`;
35
+ newFilter[newKey] = value;
36
+ }
37
+ }
38
+ }
39
+ return newFilter;
40
+ }
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ function prefixExpr(expr, prefix) {
43
+ if (Array.isArray(expr)) {
44
+ return expr.map((item) => prefixExpr(item, prefix));
45
+ }
46
+ else if (typeof expr === 'object' && expr !== null) {
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ const newExpr = {};
49
+ for (const key in expr) {
50
+ newExpr[key] = prefixExpr(expr[key], prefix);
51
+ }
52
+ return newExpr;
53
+ }
54
+ else if (typeof expr === 'string') {
55
+ // Prefix field paths (starting with $) but not system variables (starting with $$)
56
+ if (expr.startsWith('$') && !expr.startsWith('$$')) {
57
+ return `$${prefix}.${expr.substring(1)}`;
58
+ }
59
+ }
60
+ return expr;
61
+ }
62
+ //# sourceMappingURL=prefixFilterKeys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefixFilterKeys.js","sourceRoot":"","sources":["../../../src/prefixFilterKeys.ts"],"names":[],"mappings":";;AAWA,4CA6BC;AAtCD;;;;;;;;GAQG;AACH,SAAgB,gBAAgB,CAAqB,MAAiB,EAAE,MAAc;IAClF,MAAM,SAAS,GAAc,EAAE,CAAC;IAEhC,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAsB,CAAC,CAAC;YAE7C,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;gBAClB,kEAAkE;gBAClE,SAAS,CAAC,GAAsB,CAAC,GAAG,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAClE,CAAC;iBAAM,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7B,gDAAgD;gBAChD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,CAAC,EAAE,CAAC;oBAC9E,SAAS,CAAC,GAAsB,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACnD,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAClH,CAAC,CAAC,yDAAyD;gBACvE,CAAC;qBAAM,CAAC;oBACJ,+DAA+D;oBAC/D,SAAS,CAAC,GAAsB,CAAC,GAAG,KAAK,CAAC;gBAC9C,CAAC;YACL,CAAC;iBAAM,CAAC;gBACJ,2CAA2C;gBAC3C,MAAM,MAAM,GAAG,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC;gBAClC,SAAS,CAAC,MAAyB,CAAC,GAAG,KAAK,CAAC;YACjD,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC;AAED,8DAA8D;AAC9D,SAAS,UAAU,CAAC,IAAS,EAAE,MAAc;IACzC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,CAAC;SAAM,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACnD,8DAA8D;QAC9D,MAAM,OAAO,GAAQ,EAAE,CAAC;QACxB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;SAAM,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAClC,mFAAmF;QACnF,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processInBatches = processInBatches;
4
+ async function processInBatches(collection, queryOrPipeline, transform, executeBatch, options = {}) {
5
+ const { batchSize = 1000, shouldStop } = options;
6
+ const cursor = Array.isArray(queryOrPipeline) ? collection.aggregate(queryOrPipeline) : collection.find(queryOrPipeline);
7
+ let batch = [];
8
+ let processedDocuments = 0;
9
+ let operationsPerformed = 0;
10
+ while (await cursor.hasNext()) {
11
+ if (shouldStop && shouldStop()) {
12
+ break;
13
+ }
14
+ const doc = (await cursor.next());
15
+ if (!doc)
16
+ continue;
17
+ processedDocuments++;
18
+ const result = await transform(doc);
19
+ if (result !== null && result !== undefined) {
20
+ if (Array.isArray(result)) {
21
+ batch.push(...result);
22
+ }
23
+ else {
24
+ batch.push(result);
25
+ }
26
+ }
27
+ if (batch.length >= batchSize) {
28
+ await executeBatch(batch);
29
+ operationsPerformed += batch.length;
30
+ batch = [];
31
+ }
32
+ }
33
+ if (batch.length > 0) {
34
+ await executeBatch(batch);
35
+ operationsPerformed += batch.length;
36
+ }
37
+ return { processedDocuments, operationsPerformed };
38
+ }
39
+ //# sourceMappingURL=processInBatches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processInBatches.js","sourceRoot":"","sources":["../../../src/processInBatches.ts"],"names":[],"mappings":";;AAYA,4CA+CC;AA/CM,KAAK,UAAU,gBAAgB,CAClC,UAA4B,EAC5B,eAA0C,EAC1C,SAAkG,EAClG,YAA2C,EAC3C,UAAmC,EAAE;IAErC,MAAM,EAAE,SAAS,GAAG,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAEjD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAO,eAAe,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAO,eAAe,CAAC,CAAC;IAErI,IAAI,KAAK,GAAU,EAAE,CAAC;IACtB,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAC3B,IAAI,mBAAmB,GAAG,CAAC,CAAC;IAE5B,OAAO,MAAM,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAC5B,IAAI,UAAU,IAAI,UAAU,EAAE,EAAE,CAAC;YAC7B,MAAM;QACV,CAAC;QAED,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAS,CAAC;QAC1C,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,kBAAkB,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;QAEpC,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACJ,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC;QACL,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;YAC5B,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;YAC1B,mBAAmB,IAAI,KAAK,CAAC,MAAM,CAAC;YACpC,KAAK,GAAG,EAAE,CAAC;QACf,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QAC1B,mBAAmB,IAAI,KAAK,CAAC,MAAM,CAAC;IACxC,CAAC;IAED,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LeaderElector = void 0;
4
+ const mongodb_1 = require("mongodb");
5
+ const ReactiveTaskTypes_1 = require("./ReactiveTaskTypes");
6
+ const _debug = require("debug");
7
+ const OnInfo_1 = require("../OnInfo");
8
+ const OnError_1 = require("../OnError");
9
+ const debug = _debug('mongodash:reactiveTasks:leader');
10
+ /**
11
+ * Manages leader election among multiple scheduler instances.
12
+ *
13
+ * Responsibilities:
14
+ * - Attempts to acquire a distributed lock in the globals collection.
15
+ * - Maintains the lock by periodically renewing it (heartbeat).
16
+ * - Notifies callbacks when the instance becomes leader or loses leadership.
17
+ * - Ensures only one instance (the leader) runs the `ReactiveTaskPlanner` at a time.
18
+ */
19
+ class LeaderElector {
20
+ constructor(globalsCollection, instanceId, options, callbacks, onInfo = OnInfo_1.defaultOnInfo, onError = OnError_1.defaultOnError) {
21
+ this.globalsCollection = globalsCollection;
22
+ this.instanceId = instanceId;
23
+ this.options = options;
24
+ this.callbacks = callbacks;
25
+ this.onInfo = onInfo;
26
+ this.onError = onError;
27
+ this.isRunning = false;
28
+ this._isLeader = false;
29
+ this.leaderTimer = null;
30
+ this.metaDocId = ReactiveTaskTypes_1.REACTIVE_TASK_META_DOC_ID;
31
+ this.metaDocId = options.metaDocId || this.metaDocId;
32
+ }
33
+ get isLeader() {
34
+ return this._isLeader;
35
+ }
36
+ async start() {
37
+ if (this.isRunning)
38
+ return;
39
+ this.isRunning = true;
40
+ await this.runLeaderElectionLoop();
41
+ }
42
+ async stop() {
43
+ if (!this.isRunning)
44
+ return;
45
+ this.isRunning = false;
46
+ if (this.leaderTimer) {
47
+ clearTimeout(this.leaderTimer);
48
+ this.leaderTimer = null;
49
+ }
50
+ if (this._isLeader) {
51
+ await this.releaseLock();
52
+ this._isLeader = false;
53
+ }
54
+ }
55
+ forceLoseLeader() {
56
+ this._isLeader = false;
57
+ }
58
+ async runLeaderElectionLoop() {
59
+ const loop = async () => {
60
+ try {
61
+ await this.tryAcquireLock();
62
+ if (this._isLeader) {
63
+ await this.callbacks.onHeartbeat();
64
+ }
65
+ }
66
+ catch (error) {
67
+ this.onError(error);
68
+ }
69
+ finally {
70
+ if (this.isRunning) {
71
+ this.leaderTimer = setTimeout(loop, this.options.lockHeartbeatMs);
72
+ }
73
+ }
74
+ };
75
+ await loop();
76
+ }
77
+ async tryAcquireLock() {
78
+ var _a, _b;
79
+ const now = new Date();
80
+ const expiresAt = new Date(now.getTime() + this.options.lockTtlMs);
81
+ try {
82
+ debug(`[Scheduler ${this.instanceId}] Trying to acquire lock on ${this.metaDocId} in ${this.globalsCollection.collectionName}`);
83
+ const updatePipeline = [
84
+ {
85
+ $set: {
86
+ lock: {
87
+ $cond: {
88
+ if: {
89
+ $or: [
90
+ { $lt: ['$lock.expiresAt', now] },
91
+ { $eq: ['$lock.expiresAt', null] },
92
+ { $eq: ['$lock', null] },
93
+ { $eq: ['$lock.instanceId', this.instanceId] },
94
+ ],
95
+ },
96
+ then: { expiresAt, instanceId: this.instanceId },
97
+ else: '$lock',
98
+ },
99
+ },
100
+ },
101
+ },
102
+ ];
103
+ const result = (await this.globalsCollection.findOneAndUpdate({ _id: this.metaDocId }, updatePipeline, {
104
+ upsert: true,
105
+ returnDocument: 'after',
106
+ includeResultMetadata: true,
107
+ }));
108
+ if (((_b = (_a = result.value) === null || _a === void 0 ? void 0 : _a.lock) === null || _b === void 0 ? void 0 : _b.instanceId) === this.instanceId) {
109
+ if (!this._isLeader) {
110
+ this._isLeader = true;
111
+ debug(`[Scheduler ${this.instanceId}] Leader lock acquired.`);
112
+ await this.callbacks.onBecomeLeader();
113
+ }
114
+ }
115
+ else {
116
+ if (this._isLeader) {
117
+ this._isLeader = false;
118
+ this.onInfo({ message: `Leader lock lost.`, code: ReactiveTaskTypes_1.CODE_REACTIVE_TASK_LEADER_LOCK_LOST });
119
+ await this.callbacks.onLoseLeader();
120
+ }
121
+ }
122
+ }
123
+ catch (error) {
124
+ const closedErrorAfterStop = !this.isRunning && error instanceof mongodb_1.MongoClientClosedError;
125
+ if (!closedErrorAfterStop) {
126
+ this.onError(error);
127
+ }
128
+ if (this._isLeader) {
129
+ this._isLeader = false;
130
+ await this.callbacks.onLoseLeader();
131
+ }
132
+ }
133
+ }
134
+ async releaseLock() {
135
+ try {
136
+ await this.globalsCollection.updateOne({
137
+ _id: this.metaDocId,
138
+ 'lock.instanceId': this.instanceId,
139
+ }, { $unset: { lock: '' } });
140
+ debug(`[Scheduler ${this.instanceId}] Leader lock released.`);
141
+ }
142
+ catch (error) {
143
+ this.onError(error);
144
+ }
145
+ }
146
+ }
147
+ exports.LeaderElector = LeaderElector;
148
+ //# sourceMappingURL=LeaderElector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeaderElector.js","sourceRoot":"","sources":["../../../../src/reactiveTasks/LeaderElector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,403 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MetricsCollector = void 0;
4
+ const debug_1 = require("debug");
5
+ const OnError_1 = require("../OnError");
6
+ const OnInfo_1 = require("../OnInfo");
7
+ const ReactiveTaskTypes_1 = require("./ReactiveTaskTypes");
8
+ const _debugLogger = (0, debug_1.default)('mongodash:reactiveTasks:metrics');
9
+ // ============================================================================
10
+ // Constants
11
+ // ============================================================================
12
+ const METRIC_NAMES = {
13
+ DURATION: 'reactive_tasks_duration_seconds',
14
+ RETRIES: 'reactive_tasks_retries_total',
15
+ QUEUE_DEPTH: 'reactive_tasks_queue_depth',
16
+ GLOBAL_LAG: 'reactive_tasks_global_lag_seconds',
17
+ CHANGE_STREAM_LAG: 'reactive_tasks_change_stream_lag_seconds',
18
+ LAST_RECONCILIATION: 'reactive_tasks_last_reconciliation_timestamp_seconds',
19
+ };
20
+ const REGISTRY_DOC_ID = 'reactive_tasks_metrics_registry';
21
+ const STALE_THRESHOLD_MULTIPLIER = 20;
22
+ const DEFAULT_PUSH_INTERVAL = 60000;
23
+ const DEFAULT_OPTIONS = {
24
+ enabled: true,
25
+ scrapeMode: 'cluster',
26
+ readPreference: 'secondaryPreferred',
27
+ pushIntervalMs: DEFAULT_PUSH_INTERVAL,
28
+ };
29
+ // ============================================================================
30
+ // MetricsCollector
31
+ // ============================================================================
32
+ /**
33
+ * Collects and aggregates metrics for Reactive Tasks.
34
+ *
35
+ * Supports two scrape modes:
36
+ * - **cluster**: Returns aggregated metrics from ALL instances (via DB registry).
37
+ * Use when Prometheus scrapes a single endpoint (e.g., behind a load balancer).
38
+ * - **local**: Returns metrics from THIS instance only.
39
+ * Use when Prometheus scrapes each pod individually (K8s Pod Monitors).
40
+ *
41
+ * Global stats (queue depth, lag) are computed on-the-fly by the **Leader only**.
42
+ */
43
+ class MetricsCollector {
44
+ // ========================================================================
45
+ // Constructor
46
+ // ========================================================================
47
+ constructor(instanceId, registry, globalsCollection, leaderElector, options, onInfo = OnInfo_1.defaultOnInfo, onError = OnError_1.defaultOnError) {
48
+ this.queueMetricsPromise = null;
49
+ this.instanceId = instanceId;
50
+ this.registry = registry;
51
+ this.globalsCollection = globalsCollection;
52
+ this.leaderElector = leaderElector;
53
+ this.options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
54
+ this.enabled = this.options.enabled;
55
+ this.onInfo = onInfo;
56
+ this.onError = onError;
57
+ if (this.enabled) {
58
+ this.initPrometheus();
59
+ }
60
+ }
61
+ // ========================================================================
62
+ // Initialization
63
+ // ========================================================================
64
+ initPrometheus() {
65
+ try {
66
+ this.promClientModule = require('prom-client');
67
+ }
68
+ catch (_a) {
69
+ this.enabled = false;
70
+ this.onError(new Error('ReactiveTasks Monitoring is enabled but "prom-client" is not installed. Monitoring disabled.'));
71
+ return;
72
+ }
73
+ this.localPromRegistry = new this.promClientModule.Registry();
74
+ this.globalStatsRegistry = new this.promClientModule.Registry();
75
+ this.initLocalMetrics();
76
+ this.initGlobalStatsMetrics();
77
+ }
78
+ initLocalMetrics() {
79
+ this.metricDuration = this.getOrCreateMetric(METRIC_NAMES.DURATION, this.promClientModule.Histogram, { help: 'Distribution of task execution durations.', labelNames: ['task_name', 'status'] }, this.localPromRegistry);
80
+ this.metricRetries = this.getOrCreateMetric(METRIC_NAMES.RETRIES, this.promClientModule.Counter, { help: 'Total number of retries attempted.', labelNames: ['task_name'] }, this.localPromRegistry);
81
+ }
82
+ initGlobalStatsMetrics() {
83
+ const registry = this.globalStatsRegistry;
84
+ const promClient = this.promClientModule;
85
+ // Queue Depth gauge (triggers collectQueueMetrics)
86
+ new promClient.Gauge({
87
+ name: METRIC_NAMES.QUEUE_DEPTH,
88
+ help: 'Count of tasks in each state.',
89
+ labelNames: ['task_name', 'status'],
90
+ registers: [registry],
91
+ collect: () => this.collectIfLeader(() => this.triggerQueueMetricsCollection()),
92
+ });
93
+ // Global Lag gauge (shares collection with Queue Depth via deduplication)
94
+ new promClient.Gauge({
95
+ name: METRIC_NAMES.GLOBAL_LAG,
96
+ help: 'Age of oldest pending task.',
97
+ labelNames: ['task_name'],
98
+ registers: [registry],
99
+ collect: () => this.collectIfLeader(() => this.triggerQueueMetricsCollection()),
100
+ });
101
+ // Reconciliation timestamp gauge
102
+ const reconciliationGauge = new promClient.Gauge({
103
+ name: METRIC_NAMES.LAST_RECONCILIATION,
104
+ help: 'Timestamp of last successful reconciliation.',
105
+ registers: [registry],
106
+ collect: () => this.collectIfLeader(() => this.collectReconciliationMetrics((v) => reconciliationGauge.set(v))),
107
+ });
108
+ // Change Stream Lag gauge
109
+ const changeStreamLagGauge = new promClient.Gauge({
110
+ name: METRIC_NAMES.CHANGE_STREAM_LAG,
111
+ help: 'Now - ResumeToken.ClusterTime.',
112
+ registers: [registry],
113
+ collect: () => this.collectIfLeader(() => this.collectChangeStreamLag((v) => changeStreamLagGauge.set(v))),
114
+ });
115
+ }
116
+ // ========================================================================
117
+ // Lifecycle
118
+ // ========================================================================
119
+ start() {
120
+ if (!this.enabled || this.options.scrapeMode === 'local')
121
+ return;
122
+ this.pushInterval = setInterval(() => this.pushLocalMetrics(), this.options.pushIntervalMs);
123
+ // Push immediately to register presence
124
+ this.pushLocalMetrics().catch((err) => this.onError(err));
125
+ }
126
+ stop() {
127
+ var _a, _b;
128
+ if (this.pushInterval) {
129
+ clearInterval(this.pushInterval);
130
+ this.pushInterval = undefined;
131
+ }
132
+ // Extra safety: Clear registries to release references to Metric objects
133
+ (_a = this.localPromRegistry) === null || _a === void 0 ? void 0 : _a.clear();
134
+ (_b = this.globalStatsRegistry) === null || _b === void 0 ? void 0 : _b.clear();
135
+ }
136
+ // ========================================================================
137
+ // Public API: Recording Metrics
138
+ // ========================================================================
139
+ recordTaskExecution(task, status, durationMs) {
140
+ if (!this.enabled || !this.metricDuration)
141
+ return;
142
+ this.metricDuration.observe({ task_name: task, status }, durationMs / 1000);
143
+ }
144
+ recordRetry(task) {
145
+ if (!this.enabled || !this.metricRetries)
146
+ return;
147
+ this.metricRetries.inc({ task_name: task });
148
+ }
149
+ // ========================================================================
150
+ // Public API: Scraping Metrics
151
+ // ========================================================================
152
+ async getPrometheusMetrics() {
153
+ if (!this.enabled || !this.promClientModule)
154
+ return null;
155
+ return this.options.scrapeMode === 'cluster' ? this.getClusterMetrics() : this.getLocalMetrics();
156
+ }
157
+ // ========================================================================
158
+ // Scrape Mode Implementations
159
+ // ========================================================================
160
+ /**
161
+ * Returns aggregated metrics from ALL instances.
162
+ * Fetches other instances' metrics from DB and merges with fresh local metrics.
163
+ * Leader also includes global stats.
164
+ */
165
+ async getClusterMetrics() {
166
+ const allMetrics = [];
167
+ // 1. Fetch other instances' metrics from DB
168
+ const otherInstanceMetrics = await this.fetchOtherInstancesMetrics();
169
+ allMetrics.push(...otherInstanceMetrics);
170
+ // 2. Add fresh local metrics
171
+ const localMetrics = await this.getLocalMetricsAsJson();
172
+ if (localMetrics)
173
+ allMetrics.push(localMetrics);
174
+ // 3. If leader, add global stats
175
+ const globalStats = await this.getGlobalStatsAsJson();
176
+ if (globalStats)
177
+ allMetrics.push(globalStats);
178
+ // 4. Aggregate all
179
+ if (allMetrics.length === 0)
180
+ return null;
181
+ try {
182
+ return await this.promClientModule.AggregatorRegistry.aggregate(allMetrics);
183
+ }
184
+ catch (e) {
185
+ this.onError(e);
186
+ return null;
187
+ }
188
+ }
189
+ /**
190
+ * Returns metrics from THIS instance only.
191
+ * Leader also includes global stats.
192
+ */
193
+ async getLocalMetrics() {
194
+ const registries = [];
195
+ if (this.localPromRegistry) {
196
+ registries.push(this.localPromRegistry);
197
+ }
198
+ // Leader adds global stats
199
+ if (this.leaderElector.isLeader && this.globalStatsRegistry) {
200
+ await this.triggerGlobalStatsCollection();
201
+ registries.push(this.globalStatsRegistry);
202
+ }
203
+ // The following line was added as per instruction, but 'stats' is not defined in this scope.
204
+ // It also has a syntax error (missing closing brace).
205
+ // Assuming it was meant to be a placeholder or part of a different context,
206
+ // it's commented out to maintain syntactical correctness of the file.
207
+ // if (stats.statuses.find((s) => s._id === 'failed')) {return null;
208
+ if (registries.length === 0)
209
+ return null;
210
+ if (registries.length === 1)
211
+ return registries[0];
212
+ return this.promClientModule.Registry.merge(registries);
213
+ }
214
+ // ========================================================================
215
+ // Metrics Data Fetching
216
+ // ========================================================================
217
+ async fetchOtherInstancesMetrics() {
218
+ try {
219
+ const registryDoc = (await this.globalsCollection.findOne({ _id: REGISTRY_DOC_ID }, { readPreference: this.options.readPreference }));
220
+ if (!(registryDoc === null || registryDoc === void 0 ? void 0 : registryDoc.instances) || !Array.isArray(registryDoc.instances)) {
221
+ return [];
222
+ }
223
+ const now = Date.now();
224
+ const staleThreshold = STALE_THRESHOLD_MULTIPLIER * this.options.pushIntervalMs;
225
+ return registryDoc.instances
226
+ .filter((inst) => {
227
+ const age = now - new Date(inst.lastSeen).getTime();
228
+ const isStale = age > staleThreshold;
229
+ const isSelf = inst.id === this.instanceId;
230
+ return !isStale && !isSelf && Array.isArray(inst.metrics);
231
+ })
232
+ .map((inst) => inst.metrics);
233
+ }
234
+ catch (e) {
235
+ this.onError(e);
236
+ return [];
237
+ }
238
+ }
239
+ async getLocalMetricsAsJson() {
240
+ if (!this.localPromRegistry)
241
+ return null;
242
+ try {
243
+ return await this.localPromRegistry.getMetricsAsJSON();
244
+ }
245
+ catch (e) {
246
+ this.onError(e);
247
+ return null;
248
+ }
249
+ }
250
+ async getGlobalStatsAsJson() {
251
+ if (!this.leaderElector.isLeader || !this.globalStatsRegistry)
252
+ return null;
253
+ try {
254
+ return await this.globalStatsRegistry.getMetricsAsJSON();
255
+ }
256
+ catch (e) {
257
+ this.onError(e);
258
+ return null;
259
+ }
260
+ }
261
+ async triggerGlobalStatsCollection() {
262
+ if (!this.globalStatsRegistry)
263
+ return;
264
+ try {
265
+ await this.globalStatsRegistry.getMetricsAsJSON();
266
+ }
267
+ catch (e) {
268
+ this.onError(e);
269
+ }
270
+ }
271
+ // ========================================================================
272
+ // Push to Global Registry (Cluster Mode)
273
+ // ========================================================================
274
+ async pushLocalMetrics() {
275
+ if (!this.localPromRegistry)
276
+ return;
277
+ try {
278
+ const metricsJson = await this.localPromRegistry.getMetricsAsJSON();
279
+ await this.publishMetricsToGlobalRegistry(metricsJson);
280
+ }
281
+ catch (e) {
282
+ this.onError(e);
283
+ }
284
+ }
285
+ async publishMetricsToGlobalRegistry(metrics) {
286
+ try {
287
+ const threshold = STALE_THRESHOLD_MULTIPLIER * this.options.pushIntervalMs;
288
+ // Leader cleans up stale instances; followers only update self
289
+ const keepCondition = this.leaderElector.isLeader
290
+ ? { $and: [{ $ne: ['$$inst.id', this.instanceId] }, { $lt: [{ $subtract: ['$$NOW', '$$inst.lastSeen'] }, threshold] }] }
291
+ : { $ne: ['$$inst.id', this.instanceId] };
292
+ await this.globalsCollection.updateOne({ _id: REGISTRY_DOC_ID }, [
293
+ {
294
+ $set: {
295
+ instances: {
296
+ $concatArrays: [
297
+ { $filter: { input: { $ifNull: ['$instances', []] }, as: 'inst', cond: keepCondition } },
298
+ [{ id: this.instanceId, lastSeen: '$$NOW', metrics }],
299
+ ],
300
+ },
301
+ },
302
+ },
303
+ ], { upsert: true });
304
+ }
305
+ catch (e) {
306
+ this.onError(e);
307
+ }
308
+ }
309
+ // ========================================================================
310
+ // Global Stats Collection (Leader Only)
311
+ // ========================================================================
312
+ async collectIfLeader(collectFn) {
313
+ if (!this.leaderElector.isLeader)
314
+ return;
315
+ try {
316
+ await collectFn();
317
+ }
318
+ catch (e) {
319
+ this.onError(e);
320
+ }
321
+ }
322
+ /**
323
+ * Triggers queue metrics collection with deduplication.
324
+ * Both QUEUE_DEPTH and GLOBAL_LAG share the same aggregation query.
325
+ */
326
+ async triggerQueueMetricsCollection() {
327
+ if (!this.globalStatsRegistry)
328
+ return;
329
+ if (!this.queueMetricsPromise) {
330
+ this.queueMetricsPromise = this.collectQueueMetrics((name, labels, val) => {
331
+ const gauge = this.globalStatsRegistry.getSingleMetric(name);
332
+ if (gauge)
333
+ gauge.set(labels, val);
334
+ }).finally(() => {
335
+ this.queueMetricsPromise = null;
336
+ });
337
+ }
338
+ await this.queueMetricsPromise;
339
+ }
340
+ async collectQueueMetrics(setGauge) {
341
+ const entries = this.registry.getAllEntries();
342
+ await Promise.all(entries.map(async ({ repository }) => {
343
+ try {
344
+ const stats = await repository.getStatistics({}, {
345
+ readPreference: this.options.readPreference,
346
+ includeStatusCounts: true,
347
+ includeGlobalLag: true,
348
+ groupByTask: true,
349
+ });
350
+ for (const d of stats.statuses) {
351
+ // When groupByTask is true, _id is { task, status }
352
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
353
+ const id = d._id;
354
+ setGauge(METRIC_NAMES.QUEUE_DEPTH, { task_name: id.task, status: id.status }, d.count);
355
+ }
356
+ const now = Date.now();
357
+ const globalLag = stats.globalLag || [];
358
+ for (const o of globalLag) {
359
+ const lagSeconds = o.minScheduledAt ? Math.max(0, (now - new Date(o.minScheduledAt).getTime()) / 1000) : 0;
360
+ setGauge(METRIC_NAMES.GLOBAL_LAG, { task_name: o._id }, lagSeconds);
361
+ }
362
+ }
363
+ catch (e) {
364
+ this.onError(e);
365
+ }
366
+ }));
367
+ }
368
+ async collectReconciliationMetrics(setValue) {
369
+ try {
370
+ const metaDoc = (await this.globalsCollection.findOne({ _id: ReactiveTaskTypes_1.REACTIVE_TASK_META_DOC_ID }, { readPreference: this.options.readPreference }));
371
+ if (metaDoc === null || metaDoc === void 0 ? void 0 : metaDoc.lastReconciledAt) {
372
+ setValue(new Date(metaDoc.lastReconciledAt).getTime() / 1000);
373
+ }
374
+ }
375
+ catch (e) {
376
+ this.onError(e);
377
+ }
378
+ }
379
+ async collectChangeStreamLag(setValue) {
380
+ var _a;
381
+ try {
382
+ const metaDoc = (await this.globalsCollection.findOne({ _id: ReactiveTaskTypes_1.REACTIVE_TASK_META_DOC_ID }, { readPreference: this.options.readPreference }));
383
+ if ((_a = metaDoc === null || metaDoc === void 0 ? void 0 : metaDoc.streamState) === null || _a === void 0 ? void 0 : _a.lastClusterTime) {
384
+ const lagSeconds = Math.max(0, (Date.now() - new Date(metaDoc.streamState.lastClusterTime).getTime()) / 1000);
385
+ setValue(lagSeconds);
386
+ }
387
+ }
388
+ catch (e) {
389
+ this.onError(e);
390
+ }
391
+ }
392
+ // ========================================================================
393
+ // Helpers
394
+ // ========================================================================
395
+ getOrCreateMetric(name, MetricClass, config, registry) {
396
+ const existing = registry.getSingleMetric(name);
397
+ if (existing)
398
+ return existing;
399
+ return new MetricClass(Object.assign(Object.assign({ name }, config), { registers: [registry] }));
400
+ }
401
+ }
402
+ exports.MetricsCollector = MetricsCollector;
403
+ //# sourceMappingURL=MetricsCollector.js.map