@uptimizr/collector-server 2.0.1 → 2.2.0

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 (139) hide show
  1. package/AGENTS.md +187 -11
  2. package/README.md +91 -7
  3. package/dist/__tests__/support/registryRequests.d.ts +2 -1
  4. package/dist/__tests__/support/registryRequests.d.ts.map +1 -1
  5. package/dist/__tests__/support/registryRequests.js +17 -1
  6. package/dist/__tests__/support/registryRequests.js.map +1 -1
  7. package/dist/__tests__/support/subscriptionFixtures.d.ts +63 -0
  8. package/dist/__tests__/support/subscriptionFixtures.d.ts.map +1 -0
  9. package/dist/__tests__/support/subscriptionFixtures.js +17 -0
  10. package/dist/__tests__/support/subscriptionFixtures.js.map +1 -0
  11. package/dist/agentReport.d.ts +256 -0
  12. package/dist/agentReport.d.ts.map +1 -0
  13. package/dist/agentReport.js +861 -0
  14. package/dist/agentReport.js.map +1 -0
  15. package/dist/app.d.ts +13 -0
  16. package/dist/app.d.ts.map +1 -1
  17. package/dist/app.js +112 -5
  18. package/dist/app.js.map +1 -1
  19. package/dist/audit.d.ts +5 -1
  20. package/dist/audit.d.ts.map +1 -1
  21. package/dist/audit.js +24 -4
  22. package/dist/audit.js.map +1 -1
  23. package/dist/auth.d.ts +41 -1
  24. package/dist/auth.d.ts.map +1 -1
  25. package/dist/auth.js +47 -0
  26. package/dist/auth.js.map +1 -1
  27. package/dist/cli.js +133 -13
  28. package/dist/cli.js.map +1 -1
  29. package/dist/cliStore.d.ts +31 -2
  30. package/dist/cliStore.d.ts.map +1 -1
  31. package/dist/cliStore.js +34 -0
  32. package/dist/cliStore.js.map +1 -1
  33. package/dist/clickhouseStore.d.ts.map +1 -1
  34. package/dist/clickhouseStore.js +44 -2
  35. package/dist/clickhouseStore.js.map +1 -1
  36. package/dist/config.d.ts +49 -0
  37. package/dist/config.d.ts.map +1 -1
  38. package/dist/config.js +21 -0
  39. package/dist/config.js.map +1 -1
  40. package/dist/connectionLimiter.d.ts +23 -0
  41. package/dist/connectionLimiter.d.ts.map +1 -0
  42. package/dist/connectionLimiter.js +24 -0
  43. package/dist/connectionLimiter.js.map +1 -0
  44. package/dist/duckdbStore.d.ts.map +1 -1
  45. package/dist/duckdbStore.js +44 -1
  46. package/dist/duckdbStore.js.map +1 -1
  47. package/dist/internalDispatch.d.ts +36 -0
  48. package/dist/internalDispatch.d.ts.map +1 -0
  49. package/dist/internalDispatch.js +50 -0
  50. package/dist/internalDispatch.js.map +1 -0
  51. package/dist/memoryStore.d.ts.map +1 -1
  52. package/dist/memoryStore.js +314 -1
  53. package/dist/memoryStore.js.map +1 -1
  54. package/dist/mssqlStore.d.ts.map +1 -1
  55. package/dist/mssqlStore.js +44 -2
  56. package/dist/mssqlStore.js.map +1 -1
  57. package/dist/postgresStore.d.ts.map +1 -1
  58. package/dist/postgresStore.js +44 -2
  59. package/dist/postgresStore.js.map +1 -1
  60. package/dist/projectMetadata.d.ts +82 -0
  61. package/dist/projectMetadata.d.ts.map +1 -0
  62. package/dist/projectMetadata.js +50 -0
  63. package/dist/projectMetadata.js.map +1 -0
  64. package/dist/routes/context.d.ts +135 -0
  65. package/dist/routes/context.d.ts.map +1 -0
  66. package/dist/routes/context.js +306 -0
  67. package/dist/routes/context.js.map +1 -0
  68. package/dist/routes/insights.d.ts +36 -0
  69. package/dist/routes/insights.d.ts.map +1 -0
  70. package/dist/routes/insights.js +810 -0
  71. package/dist/routes/insights.js.map +1 -0
  72. package/dist/routes/live.d.ts +8 -0
  73. package/dist/routes/live.d.ts.map +1 -1
  74. package/dist/routes/live.js +8 -7
  75. package/dist/routes/live.js.map +1 -1
  76. package/dist/routes/mcp.d.ts +36 -0
  77. package/dist/routes/mcp.d.ts.map +1 -0
  78. package/dist/routes/mcp.js +262 -0
  79. package/dist/routes/mcp.js.map +1 -0
  80. package/dist/routes/meta.d.ts +16 -2
  81. package/dist/routes/meta.d.ts.map +1 -1
  82. package/dist/routes/meta.js +699 -10
  83. package/dist/routes/meta.js.map +1 -1
  84. package/dist/routes/metadata.d.ts +31 -0
  85. package/dist/routes/metadata.d.ts.map +1 -0
  86. package/dist/routes/metadata.js +163 -0
  87. package/dist/routes/metadata.js.map +1 -0
  88. package/dist/routes/narrative.d.ts +36 -0
  89. package/dist/routes/narrative.d.ts.map +1 -0
  90. package/dist/routes/narrative.js +95 -0
  91. package/dist/routes/narrative.js.map +1 -0
  92. package/dist/routes/panels.d.ts +44 -0
  93. package/dist/routes/panels.d.ts.map +1 -0
  94. package/dist/routes/panels.js +108 -0
  95. package/dist/routes/panels.js.map +1 -0
  96. package/dist/routes/query-dsl.d.ts +50 -0
  97. package/dist/routes/query-dsl.d.ts.map +1 -0
  98. package/dist/routes/query-dsl.js +359 -0
  99. package/dist/routes/query-dsl.js.map +1 -0
  100. package/dist/routes/query.d.ts +36 -0
  101. package/dist/routes/query.d.ts.map +1 -1
  102. package/dist/routes/query.js +101 -13
  103. package/dist/routes/query.js.map +1 -1
  104. package/dist/routes/subscriptions.d.ts +52 -0
  105. package/dist/routes/subscriptions.d.ts.map +1 -0
  106. package/dist/routes/subscriptions.js +290 -0
  107. package/dist/routes/subscriptions.js.map +1 -0
  108. package/dist/store.d.ts +113 -2
  109. package/dist/store.d.ts.map +1 -1
  110. package/dist/subscriptions/cli.d.ts +36 -0
  111. package/dist/subscriptions/cli.d.ts.map +1 -0
  112. package/dist/subscriptions/cli.js +143 -0
  113. package/dist/subscriptions/cli.js.map +1 -0
  114. package/dist/subscriptions/delivery.d.ts +101 -0
  115. package/dist/subscriptions/delivery.d.ts.map +1 -0
  116. package/dist/subscriptions/delivery.js +165 -0
  117. package/dist/subscriptions/delivery.js.map +1 -0
  118. package/dist/subscriptions/evaluate.d.ts +119 -0
  119. package/dist/subscriptions/evaluate.d.ts.map +1 -0
  120. package/dist/subscriptions/evaluate.js +326 -0
  121. package/dist/subscriptions/evaluate.js.map +1 -0
  122. package/dist/subscriptions/scheduler.d.ts +97 -0
  123. package/dist/subscriptions/scheduler.d.ts.map +1 -0
  124. package/dist/subscriptions/scheduler.js +332 -0
  125. package/dist/subscriptions/scheduler.js.map +1 -0
  126. package/dist/subscriptions/stream.d.ts +40 -0
  127. package/dist/subscriptions/stream.d.ts.map +1 -0
  128. package/dist/subscriptions/stream.js +38 -0
  129. package/dist/subscriptions/stream.js.map +1 -0
  130. package/dist/version.d.ts +3 -0
  131. package/dist/version.d.ts.map +1 -0
  132. package/dist/version.js +25 -0
  133. package/dist/version.js.map +1 -0
  134. package/dist/webhookSignature.d.ts +64 -0
  135. package/dist/webhookSignature.d.ts.map +1 -0
  136. package/dist/webhookSignature.js +91 -0
  137. package/dist/webhookSignature.js.map +1 -0
  138. package/llms.txt +39 -2
  139. package/package.json +17 -14
@@ -0,0 +1,326 @@
1
+ import { ANOMALY_MIN_TRAILING, ANOMALY_TRAILING_BUCKETS, BUCKET_SECONDS, bucketMeasureFor, computeBaseline, computeMover, detectAnomalies, floorToBucket, inWindow, isBucketableMetric, rollupWindow, summarizeRows, } from "@uptimizr/db";
2
+ import { parseDurationMs } from "@uptimizr/schema";
3
+ /** Windows up to this long default to hourly buckets; longer ones to daily. */
4
+ const HOURLY_WINDOW_CEILING_MS = 7 * 86_400_000;
5
+ /** Resolve the series grain: the declared one, else hourly for short windows. */
6
+ export function bucketFor(sub) {
7
+ if (sub.evaluate.bucket != null)
8
+ return sub.evaluate.bucket;
9
+ const windowMs = parseDurationMs(sub.evaluate.window) ?? HOURLY_WINDOW_CEILING_MS;
10
+ return windowMs <= HOURLY_WINDOW_CEILING_MS ? "hour" : "day";
11
+ }
12
+ /**
13
+ * The window one evaluation measures: `[floor(now − window), now)`.
14
+ *
15
+ * `since` is floored so the series is bucket-aligned; `until` is not, so the
16
+ * partial bucket containing *this minute* is included. See the module note.
17
+ */
18
+ export function resolveWindow(windowMs, bucket, now) {
19
+ const width = BUCKET_SECONDS[bucket] * 1000;
20
+ const since = floorToBucket(now - Math.max(windowMs, width), bucket);
21
+ return { since, until: now };
22
+ }
23
+ /** Compare two numbers with a subscription's operator. */
24
+ export function compare(left, op, right) {
25
+ switch (op) {
26
+ case "<":
27
+ return left < right;
28
+ case "<=":
29
+ return left <= right;
30
+ case ">":
31
+ return left > right;
32
+ case ">=":
33
+ return left >= right;
34
+ case "==":
35
+ return left === right;
36
+ case "!=":
37
+ return left !== right;
38
+ }
39
+ }
40
+ /** Sum the denominators of a window's rows. */
41
+ function sampleOf(rows) {
42
+ let total = 0;
43
+ for (const row of rows)
44
+ total += Number(row.sample_size) || 0;
45
+ return total;
46
+ }
47
+ /** The non-null values of a window's rows, in bucket order. */
48
+ function valuesOf(rows) {
49
+ return rows.filter((row) => row.value != null).map((row) => Number(row.value));
50
+ }
51
+ /** Round to the precision the firing payload reports numbers at. */
52
+ function round(value) {
53
+ return value == null || !Number.isFinite(value) ? null : Math.round(value * 1e6) / 1e6;
54
+ }
55
+ /**
56
+ * The column a `threshold` predicate on `metric` may name — its registry
57
+ * `comparable.primary`, or `null` when the metric declares none.
58
+ */
59
+ export function thresholdColumnFor(metric) {
60
+ return metric.comparable?.primary ?? null;
61
+ }
62
+ /** Whether a metric can back a store-evaluated subscription at all. */
63
+ export function isSubscribableMetric(metricId) {
64
+ return isBucketableMetric(metricId);
65
+ }
66
+ /** The effective `minSample` for a threshold: the predicate's, else the registry's. */
67
+ function minSampleFor(sub, metric) {
68
+ if (sub.predicate.kind === "threshold" && sub.predicate.minSample != null) {
69
+ return sub.predicate.minSample;
70
+ }
71
+ return metric.comparable?.minSample ?? 1;
72
+ }
73
+ /** A not-fired result carrying the numbers the caller still wants to see. */
74
+ function quiet(reason, window, bucket, series, extra = {}) {
75
+ return {
76
+ fired: false,
77
+ reason,
78
+ window,
79
+ value: null,
80
+ expected: null,
81
+ sampleSize: sampleOf(series),
82
+ dimensionValue: null,
83
+ series,
84
+ bucket,
85
+ ...extra,
86
+ };
87
+ }
88
+ /**
89
+ * Evaluate one subscription now.
90
+ *
91
+ * Every predicate kind but `presence` issues **one** bucket read; `movers` and
92
+ * `new_value` cover both their windows with a single spanning scan, the same
93
+ * trick `movers` itself uses, so both windows are guaranteed to have seen one
94
+ * snapshot of the data.
95
+ */
96
+ export async function evaluateSubscription(deps, sub, metric) {
97
+ const now = (deps.now ?? Date.now)();
98
+ const bucket = bucketFor(sub);
99
+ const windowMs = parseDurationMs(sub.evaluate.window) ?? 3_600_000;
100
+ const window = resolveWindow(windowMs, bucket, now);
101
+ const scene = sub.filters.scene;
102
+ const read = (opts) => deps.readBuckets({
103
+ metric: metric.id,
104
+ bucket,
105
+ since: window.since,
106
+ until: window.until,
107
+ scene,
108
+ ...opts,
109
+ });
110
+ switch (sub.predicate.kind) {
111
+ case "presence": {
112
+ const active = deps.activeSessions?.() ?? 0;
113
+ const fired = compare(active, sub.predicate.op, sub.predicate.value);
114
+ return {
115
+ fired,
116
+ reason: `${active} live session(s); predicate is presence ${sub.predicate.op} ${sub.predicate.value}`,
117
+ window: { since: now, until: now },
118
+ value: active,
119
+ expected: sub.predicate.value,
120
+ sampleSize: active,
121
+ dimensionValue: null,
122
+ series: [],
123
+ bucket,
124
+ };
125
+ }
126
+ case "threshold": {
127
+ const rows = await read({});
128
+ const values = valuesOf(rows);
129
+ const sampleSize = sampleOf(rows);
130
+ const minSample = minSampleFor(sub, metric);
131
+ if (sampleSize < minSample) {
132
+ return quiet(`window held ${sampleSize} sample(s), below minSample ${minSample}`, window, bucket, rows);
133
+ }
134
+ const rollup = bucketMeasureFor(metric.id)?.rollup ?? "mean";
135
+ const value = rollupWindow(values, rollup);
136
+ if (value == null) {
137
+ return quiet(`no value for ${metric.id} in the window`, window, bucket, rows);
138
+ }
139
+ const fired = compare(value, sub.predicate.op, sub.predicate.value);
140
+ const column = sub.predicate.column;
141
+ return {
142
+ fired,
143
+ reason: fired
144
+ ? `${metric.id}.${column} is ${round(value)} — ${sub.predicate.op} ${sub.predicate.value} over ${sub.evaluate.window}`
145
+ : `${metric.id}.${column} is ${round(value)}, which is not ${sub.predicate.op} ${sub.predicate.value}`,
146
+ window,
147
+ value: round(value),
148
+ expected: sub.predicate.value,
149
+ sampleSize,
150
+ dimensionValue: null,
151
+ series: rows,
152
+ bucket,
153
+ };
154
+ }
155
+ case "movers": {
156
+ // One spanning scan covering the current window and the equal one before
157
+ // it, split in TypeScript — half the reads, and one snapshot of the data.
158
+ const reference = { since: window.since - windowMs, until: window.since };
159
+ const rows = await read({ since: reference.since });
160
+ const current = rows.filter((row) => inWindow(row.bucket, window));
161
+ const previous = rows.filter((row) => inWindow(row.bucket, reference));
162
+ const sampleSize = sampleOf(current);
163
+ const minSample = minSampleFor(sub, metric);
164
+ if (sampleSize < minSample) {
165
+ return quiet(`window held ${sampleSize} sample(s), below minSample ${minSample}`, window, bucket, current);
166
+ }
167
+ const mover = computeMover({
168
+ metric: metric.id,
169
+ direction: metric.comparable?.direction ?? "neutral",
170
+ minSample,
171
+ rollup: bucketMeasureFor(metric.id)?.rollup ?? "sum",
172
+ current,
173
+ reference: previous,
174
+ });
175
+ // `deltaPct` is a signed *fraction* of the reference level; the predicate
176
+ // is declared in percent, which is what an operator writes down.
177
+ const pct = mover.deltaPct == null ? null : mover.deltaPct * 100;
178
+ if (pct == null) {
179
+ return quiet(`no comparable previous window for ${metric.id}`, window, bucket, current, {
180
+ value: mover.current,
181
+ });
182
+ }
183
+ const wanted = sub.predicate.direction ?? "any";
184
+ const directionOk = wanted === "any" ? true : wanted === "up" ? pct > 0 : pct < 0;
185
+ const fired = directionOk && Math.abs(pct) >= sub.predicate.pct;
186
+ return {
187
+ fired,
188
+ reason: fired
189
+ ? `${metric.id} moved ${round(pct)}% versus the previous ${sub.evaluate.window} (threshold ±${sub.predicate.pct}%)`
190
+ : `${metric.id} moved ${round(pct)}%, inside the ±${sub.predicate.pct}% threshold`,
191
+ window,
192
+ value: mover.current,
193
+ expected: mover.previous,
194
+ sampleSize,
195
+ dimensionValue: null,
196
+ series: current,
197
+ bucket,
198
+ };
199
+ }
200
+ case "anomaly": {
201
+ // Anomaly detection needs trailing history to know what "normal" was, so
202
+ // the read extends back over the primitive's own trailing window. Only
203
+ // buckets inside the *evaluation* window can fire — the history is context,
204
+ // not findings, or every restart would re-alert on last week.
205
+ const trailing = ANOMALY_TRAILING_BUCKETS[bucket];
206
+ const width = BUCKET_SECONDS[bucket] * 1000;
207
+ const historySince = floorToBucket(window.since - trailing * width, bucket);
208
+ const rows = await read({ since: historySince });
209
+ if (rows.length < ANOMALY_MIN_TRAILING + 1) {
210
+ return quiet(`only ${rows.length} bucket(s) of history; anomalies need more than ${ANOMALY_MIN_TRAILING}`, window, bucket, rows.filter((row) => inWindow(row.bucket, window)));
211
+ }
212
+ const anomalies = detectAnomalies(metric.id, scene, rows, {
213
+ bucket,
214
+ sensitivity: sub.predicate.sensitivity,
215
+ }).filter((row) => inWindow(row.bucketStart, window));
216
+ const inside = rows.filter((row) => inWindow(row.bucket, window));
217
+ if (anomalies.length === 0) {
218
+ return quiet(`no anomalous ${bucket} bucket for ${metric.id}`, window, bucket, inside);
219
+ }
220
+ // The most extreme finding inside the window is the one worth waking
221
+ // someone for; the rest are in the same episode.
222
+ const worst = anomalies.reduce((a, b) => (Math.abs(b.z ?? 0) > Math.abs(a.z ?? 0) ? b : a));
223
+ return {
224
+ fired: true,
225
+ reason: `${metric.id} was ${worst.kind} at ${new Date(worst.bucketStart).toISOString()}: ` +
226
+ `${round(worst.value)} against an expected ${round(worst.expected)} (robust z ${round(worst.z)})`,
227
+ window,
228
+ value: worst.value,
229
+ expected: worst.expected,
230
+ sampleSize: sampleOf(inside),
231
+ dimensionValue: worst.contributor?.value ?? null,
232
+ series: inside,
233
+ bucket,
234
+ };
235
+ }
236
+ case "new_value": {
237
+ // Same spanning-scan trick as `movers`, grouped by the watched dimension:
238
+ // a value present in the current window and absent from the one before it
239
+ // is new. Bounded by construction — one grouped read, no retained state.
240
+ const reference = { since: window.since - windowMs, until: window.since };
241
+ const rows = await read({ since: reference.since, groupBy: sub.predicate.dimension });
242
+ const seen = new Set();
243
+ for (const row of rows) {
244
+ if (inWindow(row.bucket, reference) && row.dimension_value != null) {
245
+ seen.add(row.dimension_value);
246
+ }
247
+ }
248
+ const fresh = [];
249
+ for (const row of rows) {
250
+ if (!inWindow(row.bucket, window))
251
+ continue;
252
+ const value = row.dimension_value;
253
+ // `''` is the store's "unattributed", not a value anyone named — firing
254
+ // on it would report "a new scene appeared" for every unlabelled event.
255
+ if (value == null || value === "" || seen.has(value))
256
+ continue;
257
+ if (!fresh.includes(value))
258
+ fresh.push(value);
259
+ }
260
+ const inside = rows.filter((row) => inWindow(row.bucket, window));
261
+ if (fresh.length === 0) {
262
+ return quiet(`no unseen ${sub.predicate.dimension} value in the window`, window, bucket, inside);
263
+ }
264
+ const first = fresh[0];
265
+ return {
266
+ fired: true,
267
+ reason: `new ${sub.predicate.dimension} ${JSON.stringify(first)}` +
268
+ (fresh.length > 1 ? ` (and ${fresh.length - 1} more)` : "") +
269
+ ` on ${metric.id}`,
270
+ window,
271
+ value: fresh.length,
272
+ expected: seen.size,
273
+ sampleSize: sampleOf(inside),
274
+ dimensionValue: first,
275
+ series: inside,
276
+ bucket,
277
+ };
278
+ }
279
+ }
280
+ }
281
+ /** Shape an evaluation into the firing record stored and delivered. */
282
+ export function toFiring(sub, result, at) {
283
+ return {
284
+ subscriptionId: sub.id,
285
+ name: sub.name,
286
+ metric: sub.metric,
287
+ predicate: sub.predicate.kind,
288
+ at,
289
+ window: { since: result.window.since, until: result.window.until },
290
+ value: result.value,
291
+ expected: result.expected,
292
+ sampleSize: result.sampleSize,
293
+ scene: sub.filters.scene ?? null,
294
+ reason: result.reason,
295
+ dimensionValue: result.dimensionValue,
296
+ };
297
+ }
298
+ /**
299
+ * The bounded `format=summary` block a webhook carries alongside the firing
300
+ * (sketch §F.3: "so the receiver can act without a second call").
301
+ *
302
+ * It is the registry's own summary of `insight_baseline` over exactly the window
303
+ * that fired — a real registry metric with real declared units and caveats,
304
+ * rather than a hand-rolled bag of numbers. That is deliberate: the receiver
305
+ * reads the same envelope it would get from `GET /api/v1/insights/baseline`, so
306
+ * anything that can already render one can render this.
307
+ *
308
+ * `null` when the predicate read no series (`presence`) or the summariser
309
+ * declines the rows.
310
+ */
311
+ export function summaryFor(sub, result) {
312
+ if (result.series.length === 0)
313
+ return null;
314
+ const baseline = computeBaseline(sub.metric, sub.filters.scene, result.series);
315
+ const summary = summarizeRows("insight_baseline", [baseline], {
316
+ range: { since: result.window.since, until: result.window.until },
317
+ filters: {
318
+ metric: sub.metric,
319
+ scene: sub.filters.scene,
320
+ bucket: result.bucket,
321
+ window: sub.evaluate.window,
322
+ },
323
+ });
324
+ return summary == null ? null : summary;
325
+ }
326
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/subscriptions/evaluate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import type { SubscriptionRecord } from "@uptimizr/db";
2
+ import type { CollectorConfig } from "../config.js";
3
+ import type { LiveBus } from "../liveBus.js";
4
+ import type { CollectorStore } from "../store.js";
5
+ import { type EvaluationResult } from "./evaluate.js";
6
+ import type { SubscriptionStream } from "./stream.js";
7
+ /**
8
+ * **The subscription scheduler** (#311, ADR 0051 §6 / sketch §F.2).
9
+ *
10
+ * One in-process scheduler for the whole collector. Its job is small and its
11
+ * constraints are the interesting part:
12
+ *
13
+ * - **Every subscription owns one timer**, firing on its own `evaluate.every`.
14
+ * Even the bus-backed predicates have one: `presence <= 0` ("nobody is in the
15
+ * scene") can only become true when events *stop* arriving, so a scheduler
16
+ * that only woke on bus traffic could never report it.
17
+ * - **The bus is an accelerator, not the clock.** `presence` and `new_value`
18
+ * additionally watch `LiveBus.subscribe` so they react in seconds rather than
19
+ * at the next tick. The state each one keeps is bounded
20
+ * ({@link NEW_VALUE_MAX_TRIGGERS}) and the trigger is rate-limited
21
+ * ({@link BUS_MIN_INTERVAL_MS}), so a firehose cannot turn into a query storm.
22
+ * The *answer* still comes from the store evaluation, so the bus can only
23
+ * change when a subscription is checked, never what it concludes.
24
+ * - **At most {@link CollectorConfig.subscriptionsMaxConcurrent} evaluations run
25
+ * at once**, and at most one per subscription. A tick that arrives while the
26
+ * previous one is still in flight is dropped, not queued — a subscription that
27
+ * cannot keep up with its own interval must not grow a backlog.
28
+ * - **Cooldown is enforced before delivery, not before evaluation.** The
29
+ * evaluation is cheap and its result is what `POST …/test` and the dashboard
30
+ * read; the *firing* is what costs a webhook.
31
+ * - **Every timer is `unref`'d and cleared by {@link SubscriptionScheduler.stop}.**
32
+ * A test that starts the scheduler and closes the app leaves nothing behind.
33
+ */
34
+ /** Smallest gap between two bus-triggered evaluations of one subscription. */
35
+ export declare const BUS_MIN_INTERVAL_MS = 5000;
36
+ /**
37
+ * Distinct dimension values one `new_value` watcher remembers having triggered
38
+ * on. Past this the set is cleared and the watcher falls back to its timer — the
39
+ * predicate is for low-cardinality dimensions ("a new scene", "a new custom
40
+ * event"), and an unbounded set on a hot bus is exactly the kind of state
41
+ * ADR 0032 §6 exists to prevent.
42
+ */
43
+ export declare const NEW_VALUE_MAX_TRIGGERS = 256;
44
+ /** How often the scheduler re-reads the store to pick up external changes. */
45
+ export declare const RELOAD_INTERVAL_MS = 60000;
46
+ export interface SchedulerDeps {
47
+ store: CollectorStore;
48
+ config: CollectorConfig;
49
+ liveBus: LiveBus;
50
+ stream: SubscriptionStream;
51
+ /** Structured logger; a Fastify instance's `log` in production. */
52
+ log: {
53
+ warn: (obj: unknown, msg?: string) => void;
54
+ info: (obj: unknown, msg?: string) => void;
55
+ };
56
+ /** Injected for tests. Defaults to the global `fetch`. */
57
+ fetchImpl?: typeof fetch;
58
+ /** Injected for tests. Defaults to `Date.now`. */
59
+ now?: () => number;
60
+ /** Injected for tests, so webhook backoff does not sleep for real. */
61
+ sleep?: (ms: number) => Promise<void>;
62
+ /**
63
+ * Fraction in `[0, 1)` used to jitter a subscription's first evaluation.
64
+ * Defaults to `Math.random`; tests pass `() => 0` for a deterministic phase.
65
+ */
66
+ jitter?: () => number;
67
+ }
68
+ export interface RunOptions {
69
+ /** Deliver a firing (record it, fan it out, POST it). `false` for a dry run. */
70
+ deliver?: boolean;
71
+ }
72
+ export interface SubscriptionScheduler {
73
+ /** Reconcile timers and bus watchers against the store. Idempotent. */
74
+ reload(): Promise<void>;
75
+ /**
76
+ * Evaluate one subscription now, outside its schedule. Returns the evaluation
77
+ * so `POST …/test` can answer with it. Honours cooldown when delivering.
78
+ *
79
+ * `null` means "not now": the subscription already had an evaluation in
80
+ * flight, or the collector was at its concurrency cap. The route turns that
81
+ * into a `409` rather than quietly running a second one in parallel.
82
+ */
83
+ runOnce(sub: SubscriptionRecord, options?: RunOptions): Promise<EvaluationResult | null>;
84
+ /** Subscriptions currently scheduled. */
85
+ readonly scheduledCount: number;
86
+ /** Evaluations in flight (for tests asserting the concurrency cap). */
87
+ readonly inFlight: number;
88
+ /** Clear every timer and bus watcher. Idempotent. */
89
+ stop(): void;
90
+ }
91
+ /**
92
+ * Create the scheduler. It does nothing until {@link SubscriptionScheduler.reload}
93
+ * is called, which `app.ts` does once at startup and every CRUD write does after
94
+ * it changes something.
95
+ */
96
+ export declare function createSubscriptionScheduler(deps: SchedulerDeps): SubscriptionScheduler;
97
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/subscriptions/scheduler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAA8C,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,OAAQ,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAUzC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,mEAAmE;IACnE,GAAG,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IAChG,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,sEAAsE;IACtE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,uEAAuE;IACvE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB;;;;;;;OAOG;IACH,OAAO,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACzF,yCAAyC;IACzC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,IAAI,IAAI,IAAI,CAAC;CACd;AA+BD;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,aAAa,GAAG,qBAAqB,CAgRtF"}