@noego/telemetry 0.0.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 (120) hide show
  1. package/README.md +130 -0
  2. package/dist/browser/index.cjs +1346 -0
  3. package/dist/browser/index.cjs.map +1 -0
  4. package/dist/browser/index.d.cts +5 -0
  5. package/dist/browser/index.d.ts +5 -0
  6. package/dist/browser/index.js +1318 -0
  7. package/dist/browser/index.js.map +1 -0
  8. package/dist/browser_telemetry_client-BzBPQm4m.d.cts +29 -0
  9. package/dist/browser_telemetry_client-Ch_VzwEa.d.ts +29 -0
  10. package/dist/cli/telemetry_bin.cjs +5531 -0
  11. package/dist/cli/telemetry_bin.cjs.map +1 -0
  12. package/dist/cli/telemetry_bin.d.cts +1 -0
  13. package/dist/cli/telemetry_bin.d.ts +1 -0
  14. package/dist/cli/telemetry_bin.js +5517 -0
  15. package/dist/cli/telemetry_bin.js.map +1 -0
  16. package/dist/cli/telemetry_cli.cjs +5523 -0
  17. package/dist/cli/telemetry_cli.cjs.map +1 -0
  18. package/dist/cli/telemetry_cli.d.cts +92 -0
  19. package/dist/cli/telemetry_cli.d.ts +92 -0
  20. package/dist/cli/telemetry_cli.js +5497 -0
  21. package/dist/cli/telemetry_cli.js.map +1 -0
  22. package/dist/client_defaults-B5tC-TgG.d.ts +8 -0
  23. package/dist/client_defaults-C9LnSX4j.d.cts +8 -0
  24. package/dist/core/index.cjs +2354 -0
  25. package/dist/core/index.cjs.map +1 -0
  26. package/dist/core/index.d.cts +168 -0
  27. package/dist/core/index.d.ts +168 -0
  28. package/dist/core/index.js +2318 -0
  29. package/dist/core/index.js.map +1 -0
  30. package/dist/electron/index.cjs +1379 -0
  31. package/dist/electron/index.cjs.map +1 -0
  32. package/dist/electron/index.d.cts +31 -0
  33. package/dist/electron/index.d.ts +31 -0
  34. package/dist/electron/index.js +1353 -0
  35. package/dist/electron/index.js.map +1 -0
  36. package/dist/envelope.types-rOLpWXRC.d.cts +42 -0
  37. package/dist/envelope.types-rOLpWXRC.d.ts +42 -0
  38. package/dist/in_memory_batch_transport-DKW9EZnU.d.cts +16 -0
  39. package/dist/in_memory_batch_transport-oOwNxXnA.d.ts +16 -0
  40. package/dist/index.cjs +3325 -0
  41. package/dist/index.cjs.map +1 -0
  42. package/dist/index.d.cts +66 -0
  43. package/dist/index.d.ts +66 -0
  44. package/dist/index.js +3285 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/maintenance_coordinator.types-B-bZUNvw.d.cts +127 -0
  47. package/dist/maintenance_coordinator.types-CBA5y74b.d.ts +127 -0
  48. package/dist/metric_definition_contract-BR6a_GzF.d.cts +12 -0
  49. package/dist/metric_definition_contract-l4i4SaAp.d.ts +12 -0
  50. package/dist/node/index.cjs +7157 -0
  51. package/dist/node/index.cjs.map +1 -0
  52. package/dist/node/index.d.cts +74 -0
  53. package/dist/node/index.d.ts +74 -0
  54. package/dist/node/index.js +7122 -0
  55. package/dist/node/index.js.map +1 -0
  56. package/dist/node/producer/index.cjs +1322 -0
  57. package/dist/node/producer/index.cjs.map +1 -0
  58. package/dist/node/producer/index.d.cts +21 -0
  59. package/dist/node/producer/index.d.ts +21 -0
  60. package/dist/node/producer/index.js +1294 -0
  61. package/dist/node/producer/index.js.map +1 -0
  62. package/dist/query.types-Bntwh6KF.d.cts +186 -0
  63. package/dist/query.types-DySoqYop.d.ts +186 -0
  64. package/dist/result.types-171Huujb.d.cts +9 -0
  65. package/dist/result.types-171Huujb.d.ts +9 -0
  66. package/dist/sqlite/index.cjs +3409 -0
  67. package/dist/sqlite/index.cjs.map +1 -0
  68. package/dist/sqlite/index.d.cts +77 -0
  69. package/dist/sqlite/index.d.ts +77 -0
  70. package/dist/sqlite/index.js +3382 -0
  71. package/dist/sqlite/index.js.map +1 -0
  72. package/dist/storage.types-BTCGVxOM.d.ts +76 -0
  73. package/dist/storage.types-DjuUMinz.d.cts +76 -0
  74. package/dist/telemetry_client-BXw8WH_F.d.ts +141 -0
  75. package/dist/telemetry_client-CYaPYfOl.d.cts +141 -0
  76. package/dist/telemetry_configuration_parser-C7Et_gwG.d.ts +39 -0
  77. package/dist/telemetry_configuration_parser-R-niZQRZ.d.cts +39 -0
  78. package/dist/testing/index.cjs +215 -0
  79. package/dist/testing/index.cjs.map +1 -0
  80. package/dist/testing/index.d.cts +32 -0
  81. package/dist/testing/index.d.ts +32 -0
  82. package/dist/testing/index.js +187 -0
  83. package/dist/testing/index.js.map +1 -0
  84. package/dist/trace-adapter/index.cjs +1034 -0
  85. package/dist/trace-adapter/index.cjs.map +1 -0
  86. package/dist/trace-adapter/index.d.cts +72 -0
  87. package/dist/trace-adapter/index.d.ts +72 -0
  88. package/dist/trace-adapter/index.js +1007 -0
  89. package/dist/trace-adapter/index.js.map +1 -0
  90. package/dist/transport.types-BAl_TRtU.d.cts +99 -0
  91. package/dist/transport.types-DDnZoKWi.d.ts +99 -0
  92. package/migrations/1783695629569_raw_events.down.sql +1 -0
  93. package/migrations/1783695629569_raw_events.up.sql +30 -0
  94. package/migrations/1783695630053_batch_acknowledgements.down.sql +1 -0
  95. package/migrations/1783695630053_batch_acknowledgements.up.sql +13 -0
  96. package/migrations/1783695630960_ingest_watermarks.down.sql +1 -0
  97. package/migrations/1783695630960_ingest_watermarks.up.sql +9 -0
  98. package/migrations/1783695631522_metric_definitions.down.sql +1 -0
  99. package/migrations/1783695631522_metric_definitions.up.sql +10 -0
  100. package/migrations/1783695631966_metric_samples.down.sql +1 -0
  101. package/migrations/1783695631966_metric_samples.up.sql +15 -0
  102. package/migrations/1783695632417_metric_rollups.down.sql +1 -0
  103. package/migrations/1783695632417_metric_rollups.up.sql +22 -0
  104. package/migrations/1783695632860_rollup_completions.down.sql +1 -0
  105. package/migrations/1783695632860_rollup_completions.up.sql +17 -0
  106. package/migrations/1783695633302_extraction_failures.down.sql +1 -0
  107. package/migrations/1783695633302_extraction_failures.up.sql +10 -0
  108. package/migrations/1783695633748_maintenance_runs.down.sql +1 -0
  109. package/migrations/1783695633748_maintenance_runs.up.sql +12 -0
  110. package/migrations/1783695634221_reader_leases.down.sql +1 -0
  111. package/migrations/1783695634221_reader_leases.up.sql +9 -0
  112. package/migrations/1783702703104_committed_drop_counters.down.sql +1 -0
  113. package/migrations/1783702703104_committed_drop_counters.up.sql +11 -0
  114. package/migrations/1783705542684_rollup_completion_state.down.sql +2 -0
  115. package/migrations/1783705542684_rollup_completion_state.up.sql +6 -0
  116. package/migrations/1784309799539_metric_samples_event_index.down.sql +1 -0
  117. package/migrations/1784309799539_metric_samples_event_index.up.sql +2 -0
  118. package/native/noego_telemetry_native.node +0 -0
  119. package/package.json +137 -0
  120. package/proper.json +8 -0
package/dist/index.js ADDED
@@ -0,0 +1,3285 @@
1
+ /* @noego/telemetry */
2
+
3
+ // src/core/helper/canonical_json.ts
4
+ var UTF8_ENCODER = new TextEncoder();
5
+ function compareCanonicalKeys(left, right) {
6
+ if (left === right) return 0;
7
+ if (/^[\x00-\x7f]*$/.test(left) && /^[\x00-\x7f]*$/.test(right)) {
8
+ return left < right ? -1 : 1;
9
+ }
10
+ const leftBytes = UTF8_ENCODER.encode(left);
11
+ const rightBytes = UTF8_ENCODER.encode(right);
12
+ const length = Math.min(leftBytes.length, rightBytes.length);
13
+ for (let index = 0; index < length; index += 1) {
14
+ const difference = (leftBytes[index] ?? 0) - (rightBytes[index] ?? 0);
15
+ if (difference !== 0) return difference;
16
+ }
17
+ return leftBytes.length - rightBytes.length;
18
+ }
19
+ function canonicalJson(value) {
20
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
21
+ return JSON.stringify(value);
22
+ }
23
+ if (typeof value === "number") {
24
+ if (!Number.isFinite(value)) {
25
+ throw new Error("Canonical telemetry JSON cannot encode a non-finite number");
26
+ }
27
+ return JSON.stringify(value);
28
+ }
29
+ if (Array.isArray(value)) {
30
+ const items = value.map((item) => canonicalJson(item));
31
+ return `[${items.join(",")}]`;
32
+ }
33
+ if (typeof value !== "object") {
34
+ throw new Error(`Canonical telemetry JSON cannot encode ${typeof value}`);
35
+ }
36
+ const entries = Object.entries(value).sort(([left], [right]) => compareCanonicalKeys(left, right));
37
+ const properties = entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`);
38
+ return `{${properties.join(",")}}`;
39
+ }
40
+ function telemetryUtf8Length(value) {
41
+ return new TextEncoder().encode(value).byteLength;
42
+ }
43
+ function telemetryChecksum(value) {
44
+ const bytes = new TextEncoder().encode(canonicalJson(value));
45
+ let hash = 0xcbf29ce484222325n;
46
+ const prime = 0x100000001b3n;
47
+ for (const byte of bytes) {
48
+ hash ^= BigInt(byte);
49
+ hash = BigInt.asUintN(64, hash * prime);
50
+ }
51
+ return hash.toString(16).padStart(16, "0");
52
+ }
53
+
54
+ // src/core/bounded_metadata_snapshotter.ts
55
+ var SENTINEL_CIRCULAR = "[telemetry:circular]";
56
+ var SENTINEL_TRUNCATED = "[telemetry:truncated]";
57
+ var BoundedMetadataSnapshotter = class {
58
+ constructor(limits) {
59
+ this.limits = limits;
60
+ this.validateLimits();
61
+ }
62
+ limits;
63
+ snapshot(input) {
64
+ const state = {
65
+ bytes: 0,
66
+ properties: 0,
67
+ truncatedValues: 0,
68
+ redactedValues: 0,
69
+ serializationFailures: 0,
70
+ seen: /* @__PURE__ */ new WeakSet()
71
+ };
72
+ let normalized;
73
+ try {
74
+ normalized = this.normalize(input, "$", 0, state);
75
+ } catch {
76
+ state.serializationFailures += 1;
77
+ normalized = { __telemetry: "[telemetry:snapshot-failed]" };
78
+ }
79
+ const root = this.ensureObject(normalized);
80
+ const canonical = canonicalJson(root);
81
+ const metadata = telemetryUtf8Length(canonical) <= this.limits.maxBytes ? root : Object.freeze({ __telemetry: SENTINEL_TRUNCATED });
82
+ if (metadata !== root) {
83
+ state.truncatedValues += 1;
84
+ }
85
+ const stats = Object.freeze({
86
+ estimatedBytes: Math.min(telemetryUtf8Length(canonical), this.limits.maxBytes),
87
+ truncatedValues: state.truncatedValues,
88
+ redactedValues: state.redactedValues,
89
+ serializationFailures: state.serializationFailures
90
+ });
91
+ return Object.freeze({ metadata, stats });
92
+ }
93
+ normalize(input, path, depth, state) {
94
+ const replacement = this.redactionFor(path);
95
+ if (replacement !== null) {
96
+ state.redactedValues += 1;
97
+ return replacement;
98
+ }
99
+ if (depth > this.limits.maxDepth || state.bytes >= this.limits.maxBytes) {
100
+ state.truncatedValues += 1;
101
+ return SENTINEL_TRUNCATED;
102
+ }
103
+ if (input === null || typeof input === "boolean") {
104
+ return input;
105
+ }
106
+ if (typeof input === "string") {
107
+ return this.normalizeString(input, state);
108
+ }
109
+ if (typeof input === "number") {
110
+ return Number.isFinite(input) ? input : `[telemetry:number:${String(input)}]`;
111
+ }
112
+ if (typeof input === "bigint") {
113
+ return `[telemetry:bigint:${input.toString()}]`;
114
+ }
115
+ if (typeof input === "undefined") {
116
+ return "[telemetry:undefined]";
117
+ }
118
+ if (typeof input === "symbol") {
119
+ return `[telemetry:symbol:${input.description ?? ""}]`;
120
+ }
121
+ if (typeof input === "function") {
122
+ return "[telemetry:function]";
123
+ }
124
+ if (input instanceof Error) {
125
+ return this.normalizeError(input, path, depth, state);
126
+ }
127
+ if (typeof input !== "object") {
128
+ return `[telemetry:unsupported:${typeof input}]`;
129
+ }
130
+ if (state.seen.has(input)) {
131
+ return SENTINEL_CIRCULAR;
132
+ }
133
+ state.seen.add(input);
134
+ try {
135
+ return Array.isArray(input) ? this.normalizeArray(input, path, depth, state) : this.normalizeObject(input, path, depth, state);
136
+ } finally {
137
+ state.seen.delete(input);
138
+ }
139
+ }
140
+ normalizeString(input, state) {
141
+ const stringLimit = Math.min(this.limits.maxStringLength, Math.max(0, this.limits.maxBytes - state.bytes));
142
+ const truncated = input.length > stringLimit;
143
+ const normalized = truncated ? input.slice(0, stringLimit) : input;
144
+ state.bytes += telemetryUtf8Length(normalized);
145
+ if (truncated) {
146
+ state.truncatedValues += 1;
147
+ }
148
+ return normalized;
149
+ }
150
+ normalizeArray(input, path, depth, state) {
151
+ const length = Math.min(input.length, this.limits.maxArrayLength);
152
+ const values = [];
153
+ for (let index = 0; index < length; index += 1) {
154
+ let item;
155
+ try {
156
+ item = input[index];
157
+ } catch {
158
+ state.serializationFailures += 1;
159
+ item = "[telemetry:property-read-failed]";
160
+ }
161
+ values.push(this.normalize(item, `${path}[${index}]`, depth + 1, state));
162
+ }
163
+ if (input.length > length) {
164
+ values.push(SENTINEL_TRUNCATED);
165
+ state.truncatedValues += 1;
166
+ }
167
+ return Object.freeze(values);
168
+ }
169
+ normalizeObject(input, path, depth, state) {
170
+ let keys;
171
+ try {
172
+ keys = Object.keys(input).sort((left, right) => left.localeCompare(right));
173
+ } catch {
174
+ state.serializationFailures += 1;
175
+ return Object.freeze({ __telemetry: "[telemetry:keys-failed]" });
176
+ }
177
+ const output = {};
178
+ for (const key of keys) {
179
+ if (state.properties >= this.limits.maxProperties) {
180
+ output.__telemetry = SENTINEL_TRUNCATED;
181
+ state.truncatedValues += 1;
182
+ break;
183
+ }
184
+ state.properties += 1;
185
+ state.bytes += telemetryUtf8Length(key);
186
+ const childPath = path === "$" ? key : `${path}.${key}`;
187
+ const redaction = this.redactionFor(childPath);
188
+ if (redaction !== null) {
189
+ output[key] = redaction;
190
+ state.redactedValues += 1;
191
+ continue;
192
+ }
193
+ let property;
194
+ try {
195
+ property = Reflect.get(input, key);
196
+ } catch {
197
+ state.serializationFailures += 1;
198
+ property = "[telemetry:property-read-failed]";
199
+ }
200
+ output[key] = this.normalize(property, childPath, depth + 1, state);
201
+ }
202
+ return Object.freeze(output);
203
+ }
204
+ normalizeError(error, path, depth, state) {
205
+ const name = this.readErrorField(error, "name", state);
206
+ const message = this.readErrorField(error, "message", state);
207
+ const stack = this.readErrorField(error, "stack", state).slice(0, this.limits.maxErrorStackLength);
208
+ return Object.freeze({
209
+ name: this.normalize(name, `${path}.name`, depth + 1, state),
210
+ message: this.normalize(message, `${path}.message`, depth + 1, state),
211
+ stack: this.normalize(stack, `${path}.stack`, depth + 1, state)
212
+ });
213
+ }
214
+ readErrorField(error, field, state) {
215
+ try {
216
+ return typeof error[field] === "string" ? error[field] : "";
217
+ } catch {
218
+ state.serializationFailures += 1;
219
+ return "[telemetry:error-field-read-failed]";
220
+ }
221
+ }
222
+ ensureObject(value) {
223
+ return this.isJsonObject(value) ? value : Object.freeze({ value });
224
+ }
225
+ isJsonObject(value) {
226
+ return typeof value === "object" && value !== null && !Array.isArray(value);
227
+ }
228
+ redactionFor(path) {
229
+ const matchingRule = this.limits.redactions.find((rule) => rule.path === path);
230
+ return matchingRule?.replacement ?? null;
231
+ }
232
+ validateLimits() {
233
+ const limits = [
234
+ this.limits.maxBytes,
235
+ this.limits.maxDepth,
236
+ this.limits.maxProperties,
237
+ this.limits.maxArrayLength,
238
+ this.limits.maxStringLength,
239
+ this.limits.maxErrorStackLength
240
+ ];
241
+ const hasInvalidLimit = limits.some((limit) => !Number.isInteger(limit) || limit <= 0);
242
+ if (hasInvalidLimit) {
243
+ throw new Error("Metadata snapshot limits must be positive integers");
244
+ }
245
+ }
246
+ };
247
+
248
+ // src/core/metric_definition_contract.ts
249
+ var MetricDefinitionContract = class {
250
+ normalize(definition) {
251
+ const parsedSelector = this.parseSelector(definition.selector);
252
+ if (!parsedSelector.ok) {
253
+ return parsedSelector;
254
+ }
255
+ const bracketGroupBy = parsedSelector.value.groupBy;
256
+ const configuredGroupBy = definition.groupBy;
257
+ const bothSpecifyDifferentGrouping = bracketGroupBy.length > 0 && configuredGroupBy.length > 0 && bracketGroupBy.join(".") !== configuredGroupBy.join(".");
258
+ if (bothSpecifyDifferentGrouping) {
259
+ return { ok: false, error: `Metric ${definition.name} has conflicting selector and groupBy declarations` };
260
+ }
261
+ const groupBy = configuredGroupBy.length > 0 ? configuredGroupBy : bracketGroupBy;
262
+ const basicError = this.validateBasic(definition, parsedSelector.value.selector, groupBy);
263
+ if (basicError !== null) {
264
+ return { ok: false, error: basicError };
265
+ }
266
+ if (parsedSelector.value.wildcardUngrouped && definition.measurements.some((measurement) => measurement.kind !== "count")) {
267
+ return {
268
+ ok: false,
269
+ error: `Metric ${definition.name} uses [*] and may only declare count`
270
+ };
271
+ }
272
+ return {
273
+ ok: true,
274
+ value: Object.freeze({
275
+ ...definition,
276
+ selector: parsedSelector.value.selector,
277
+ groupBy: Object.freeze([...groupBy]),
278
+ resolutionsMs: Object.freeze([...definition.resolutionsMs].sort((left, right) => left - right)),
279
+ measurements: Object.freeze([...definition.measurements]),
280
+ targetIds: Object.freeze([...definition.targetIds]),
281
+ cardinality: Object.freeze({ ...definition.cardinality })
282
+ })
283
+ };
284
+ }
285
+ matches(selector, label) {
286
+ const selectorSegments = selector.split(".");
287
+ const labelSegments = label.split(".");
288
+ return this.matchSegments(selectorSegments, labelSegments, 0, 0);
289
+ }
290
+ parseSelector(selector) {
291
+ if (selector.startsWith("[")) {
292
+ return { ok: false, error: "A metric selector cannot omit its event label or glob" };
293
+ }
294
+ const bracketStart = selector.lastIndexOf("[");
295
+ if (bracketStart < 0) {
296
+ return { ok: true, value: { selector, groupBy: [], wildcardUngrouped: false } };
297
+ }
298
+ if (!selector.endsWith("]")) {
299
+ return { ok: false, error: `Invalid metric selector ${selector}` };
300
+ }
301
+ const eventSelector = selector.slice(0, bracketStart);
302
+ const bracket = selector.slice(bracketStart + 1, -1).trim();
303
+ if (!eventSelector || !bracket) {
304
+ return { ok: false, error: `Invalid metric selector ${selector}` };
305
+ }
306
+ return {
307
+ ok: true,
308
+ value: {
309
+ selector: eventSelector,
310
+ groupBy: bracket === "*" ? [] : [bracket],
311
+ wildcardUngrouped: bracket === "*"
312
+ }
313
+ };
314
+ }
315
+ validateBasic(definition, selector, groupBy) {
316
+ if (!/^[A-Za-z0-9_*][A-Za-z0-9_.:*\-]*$/.test(selector)) {
317
+ return `Metric ${definition.name} has an invalid selector`;
318
+ }
319
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(definition.name)) {
320
+ return `Metric name ${definition.name} is invalid`;
321
+ }
322
+ if (!Number.isInteger(definition.version) || definition.version <= 0) {
323
+ return `Metric ${definition.name} must have a positive integer version`;
324
+ }
325
+ if (definition.measurements.length === 0) {
326
+ return `Metric ${definition.name} must declare at least one measurement`;
327
+ }
328
+ const invalidResolution = definition.resolutionsMs.some(
329
+ (resolution) => !Number.isInteger(resolution) || resolution <= 0
330
+ );
331
+ const duplicateResolution = new Set(definition.resolutionsMs).size !== definition.resolutionsMs.length;
332
+ if (invalidResolution || duplicateResolution || definition.resolutionsMs.length === 0) {
333
+ return `Metric ${definition.name} has invalid or duplicate resolutions`;
334
+ }
335
+ const missingCardinality = groupBy.find((field) => definition.cardinality[field] === void 0);
336
+ if (missingCardinality) {
337
+ return `Metric ${definition.name} is missing cardinality policy for ${missingCardinality}`;
338
+ }
339
+ const invalidCardinality = Object.values(definition.cardinality).some(
340
+ (rule) => !Number.isInteger(rule.maxValuesPerBucket) || rule.maxValuesPerBucket <= 0
341
+ );
342
+ if (invalidCardinality) {
343
+ return `Metric ${definition.name} has an invalid cardinality limit`;
344
+ }
345
+ return null;
346
+ }
347
+ matchSegments(selector, label, selectorIndex, labelIndex) {
348
+ if (selectorIndex === selector.length) {
349
+ return labelIndex === label.length;
350
+ }
351
+ const segment = selector[selectorIndex];
352
+ if (segment === "**") {
353
+ if (selectorIndex === selector.length - 1) {
354
+ return true;
355
+ }
356
+ for (let next = labelIndex; next <= label.length; next += 1) {
357
+ if (this.matchSegments(selector, label, selectorIndex + 1, next)) {
358
+ return true;
359
+ }
360
+ }
361
+ return false;
362
+ }
363
+ if (labelIndex >= label.length) {
364
+ return false;
365
+ }
366
+ const segmentMatches = segment === "*" || segment === label[labelIndex];
367
+ return segmentMatches && this.matchSegments(selector, label, selectorIndex + 1, labelIndex + 1);
368
+ }
369
+ };
370
+
371
+ // src/core/metric_extractor.ts
372
+ var MetricExtractor = class {
373
+ constructor(definitions) {
374
+ this.definitions = definitions;
375
+ }
376
+ definitions;
377
+ contract = new MetricDefinitionContract();
378
+ extract(envelopes) {
379
+ const samples = [];
380
+ const failures = [];
381
+ for (const envelope of envelopes) {
382
+ for (const definition of this.definitions) {
383
+ if (!this.contract.matches(definition.selector, envelope.label)) {
384
+ continue;
385
+ }
386
+ const extracted = this.extractDefinition(definition, envelope);
387
+ if (extracted.ok) {
388
+ samples.push(extracted.sample);
389
+ } else if (definition.missingField === "record-failure") {
390
+ failures.push({
391
+ metricName: definition.name,
392
+ definitionVersion: definition.version,
393
+ producerId: envelope.producerId,
394
+ sequence: envelope.sequence,
395
+ reason: extracted.reason
396
+ });
397
+ }
398
+ }
399
+ }
400
+ return Object.freeze({
401
+ samples: Object.freeze(samples),
402
+ failures: Object.freeze(failures)
403
+ });
404
+ }
405
+ extractDefinition(definition, envelope) {
406
+ const dimensions = {};
407
+ for (const field of definition.groupBy) {
408
+ const dimension = this.readField(envelope, field);
409
+ if (!this.isDimensionValue(dimension)) {
410
+ return { ok: false, reason: `Missing or invalid dimension ${field}` };
411
+ }
412
+ dimensions[field] = dimension;
413
+ }
414
+ const values = { __count: 1 };
415
+ for (const measurement of definition.measurements) {
416
+ if (measurement.kind === "count") {
417
+ continue;
418
+ }
419
+ const measured = this.readField(envelope, measurement.field);
420
+ if (typeof measured !== "number" || !Number.isFinite(measured)) {
421
+ return { ok: false, reason: `Missing or non-numeric measurement ${measurement.field}` };
422
+ }
423
+ values[measurement.field] = measured;
424
+ }
425
+ const frozenDimensions = Object.freeze(dimensions);
426
+ return {
427
+ ok: true,
428
+ sample: Object.freeze({
429
+ metricName: definition.name,
430
+ definitionVersion: definition.version,
431
+ occurredAtMs: envelope.occurredAtMs,
432
+ producerId: envelope.producerId,
433
+ sequence: envelope.sequence,
434
+ dimensions: frozenDimensions,
435
+ dimensionKey: canonicalJson(frozenDimensions),
436
+ values: Object.freeze(values),
437
+ targetIds: definition.targetIds
438
+ })
439
+ };
440
+ }
441
+ readField(envelope, path) {
442
+ const topLevel = this.readTopLevel(envelope, path);
443
+ if (topLevel !== void 0) {
444
+ return topLevel;
445
+ }
446
+ const segments = path.split(".");
447
+ let current = envelope.metadata;
448
+ for (const segment of segments) {
449
+ if (!this.isJsonObject(current)) {
450
+ return void 0;
451
+ }
452
+ const nextValue = current[segment];
453
+ if (nextValue === void 0) {
454
+ return void 0;
455
+ }
456
+ current = nextValue;
457
+ }
458
+ return current;
459
+ }
460
+ readTopLevel(envelope, path) {
461
+ switch (path) {
462
+ case "conversationId":
463
+ return envelope.conversationId;
464
+ case "correlationId":
465
+ return envelope.correlationId;
466
+ case "source":
467
+ return envelope.source;
468
+ case "processKind":
469
+ return envelope.processKind;
470
+ case "level":
471
+ return envelope.level;
472
+ case "label":
473
+ return envelope.label;
474
+ case "producerId":
475
+ return envelope.producerId;
476
+ default:
477
+ return void 0;
478
+ }
479
+ }
480
+ isDimensionValue(value) {
481
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
482
+ }
483
+ isJsonObject(value) {
484
+ return typeof value === "object" && value !== null && !Array.isArray(value);
485
+ }
486
+ };
487
+
488
+ // src/core/telemetry_acknowledgement_contract.ts
489
+ var MAX_TARGET_STATES = 1024;
490
+ var MAX_TARGET_ID_BYTES = 256;
491
+ var MAX_REASON_BYTES = 512;
492
+ var TelemetryAcknowledgementContract = class {
493
+ exceptionReason(cause) {
494
+ try {
495
+ const value = cause instanceof Error ? cause.message : cause;
496
+ return this.truncateReason(String(value));
497
+ } catch {
498
+ return "Unknown telemetry transport failure";
499
+ }
500
+ }
501
+ validateAndDetach(batch, value) {
502
+ try {
503
+ const initialError = this.acknowledgementError(batch, value);
504
+ if (initialError !== null) {
505
+ return { ok: false, error: initialError };
506
+ }
507
+ const detached = JSON.parse(canonicalJson(value));
508
+ const detachedError = this.acknowledgementError(batch, detached);
509
+ if (detachedError !== null) {
510
+ return { ok: false, error: detachedError };
511
+ }
512
+ return { ok: true, value: this.freezeAcknowledgement(detached) };
513
+ } catch {
514
+ return { ok: false, error: "acknowledgement could not be inspected" };
515
+ }
516
+ }
517
+ acknowledgementError(batch, value) {
518
+ if (!this.isRecord(value) || value.idempotencyKey !== batch.idempotencyKey) {
519
+ return "acknowledgement identity is missing or mismatched";
520
+ }
521
+ const targetsError = this.targetStatesError(value.targetStates);
522
+ if (targetsError !== null) {
523
+ return targetsError;
524
+ }
525
+ switch (value.kind) {
526
+ case "committed":
527
+ return this.committedAcknowledgementError(batch, value);
528
+ case "retryable":
529
+ return this.retryableAcknowledgementError(value);
530
+ case "rejected":
531
+ return this.rejectedAcknowledgementError(value);
532
+ case "conflict":
533
+ return this.conflictAcknowledgementError(value);
534
+ default:
535
+ return "acknowledgement kind is invalid";
536
+ }
537
+ }
538
+ committedAcknowledgementError(batch, value) {
539
+ const valid = this.hasExactKeys(value, [
540
+ "kind",
541
+ "idempotencyKey",
542
+ "committedAtMs",
543
+ "acceptedEvents",
544
+ "duplicate",
545
+ "targetStates"
546
+ ]) && Number.isSafeInteger(value.committedAtMs) && Number(value.committedAtMs) >= 0 && value.acceptedEvents === batch.envelopes.length && typeof value.duplicate === "boolean";
547
+ return valid ? null : "committed acknowledgement fields are invalid";
548
+ }
549
+ retryableAcknowledgementError(value) {
550
+ const valid = this.hasExactKeys(value, [
551
+ "kind",
552
+ "idempotencyKey",
553
+ "reason",
554
+ "retryAfterMs",
555
+ "targetStates"
556
+ ]) && this.isBoundedReason(value.reason) && Number.isFinite(value.retryAfterMs) && Number(value.retryAfterMs) >= 0 && Number(value.retryAfterMs) <= Number.MAX_SAFE_INTEGER;
557
+ return valid ? null : "retryable acknowledgement fields are invalid";
558
+ }
559
+ rejectedAcknowledgementError(value) {
560
+ const codes = ["invalid-batch", "unsupported-version", "no-required-terminal", "closed"];
561
+ const valid = this.hasExactKeys(value, [
562
+ "kind",
563
+ "idempotencyKey",
564
+ "code",
565
+ "reason",
566
+ "targetStates"
567
+ ]) && codes.includes(String(value.code)) && this.isBoundedReason(value.reason);
568
+ return valid ? null : "rejected acknowledgement fields are invalid";
569
+ }
570
+ conflictAcknowledgementError(value) {
571
+ const valid = this.hasExactKeys(value, ["kind", "idempotencyKey", "reason", "targetStates"]) && this.isBoundedReason(value.reason);
572
+ return valid ? null : "conflict acknowledgement fields are invalid";
573
+ }
574
+ targetStatesError(value) {
575
+ if (!Array.isArray(value) || value.length > MAX_TARGET_STATES || !this.hasExactArrayIndexes(value)) {
576
+ return "acknowledgement target states are invalid";
577
+ }
578
+ const targetIds = /* @__PURE__ */ new Set();
579
+ for (const target of value) {
580
+ if (!this.isValidTargetState(target) || targetIds.has(target.targetId)) {
581
+ return "acknowledgement target states are invalid";
582
+ }
583
+ targetIds.add(target.targetId);
584
+ }
585
+ return null;
586
+ }
587
+ isValidTargetState(value) {
588
+ if (!this.isRecord(value) || !this.hasExactKeys(value, ["targetId", "delivery", "state", "attempts", "reason"])) {
589
+ return false;
590
+ }
591
+ const deliveries = ["required", "best-effort"];
592
+ const states = ["pending", "committed", "failed", "abandoned"];
593
+ return typeof value.targetId === "string" && value.targetId.length > 0 && telemetryUtf8Length(value.targetId) <= MAX_TARGET_ID_BYTES && deliveries.includes(String(value.delivery)) && states.includes(String(value.state)) && Number.isSafeInteger(value.attempts) && Number(value.attempts) >= 0 && (value.reason === null || this.isBoundedReason(value.reason));
594
+ }
595
+ freezeAcknowledgement(value) {
596
+ const targetStates = value.targetStates.map((target) => Object.freeze({ ...target }));
597
+ return Object.freeze({ ...value, targetStates: Object.freeze(targetStates) });
598
+ }
599
+ isRecord(value) {
600
+ return typeof value === "object" && value !== null && !Array.isArray(value);
601
+ }
602
+ hasExactKeys(value, expected) {
603
+ const keys = Reflect.ownKeys(value);
604
+ return keys.length === expected.length && keys.every((key) => typeof key === "string" && expected.includes(key));
605
+ }
606
+ hasExactArrayIndexes(value) {
607
+ const keys = Reflect.ownKeys(value);
608
+ if (keys.length !== value.length + 1 || !keys.includes("length")) {
609
+ return false;
610
+ }
611
+ return value.every((_item, index) => keys.includes(String(index)));
612
+ }
613
+ isBoundedReason(value) {
614
+ return typeof value === "string" && telemetryUtf8Length(value) <= MAX_REASON_BYTES;
615
+ }
616
+ truncateReason(value) {
617
+ const encoded = new TextEncoder().encode(value);
618
+ if (encoded.length <= MAX_REASON_BYTES) {
619
+ return value;
620
+ }
621
+ const decoder = new TextDecoder("utf-8", { fatal: true });
622
+ for (let length = MAX_REASON_BYTES; length > 0; length -= 1) {
623
+ try {
624
+ return decoder.decode(encoded.subarray(0, length));
625
+ } catch {
626
+ }
627
+ }
628
+ return "";
629
+ }
630
+ };
631
+
632
+ // src/core/helper/client_defaults.ts
633
+ var DEFAULT_TELEMETRY_ADMISSION_POLICY = Object.freeze({
634
+ maxQueuedEvents: 2e3,
635
+ maxQueuedBytes: 2e6,
636
+ maxBatchEvents: 128,
637
+ maxBatchBytes: 256e3,
638
+ maxInFlightBatches: 1,
639
+ retryLimit: 3,
640
+ retryBaseDelayMs: 25,
641
+ sampleRates: Object.freeze({
642
+ low: 1,
643
+ normal: 1,
644
+ high: 1
645
+ })
646
+ });
647
+ var DEFAULT_METADATA_SNAPSHOT_LIMITS = Object.freeze({
648
+ maxBytes: 32768,
649
+ maxDepth: 8,
650
+ maxProperties: 256,
651
+ maxArrayLength: 128,
652
+ maxStringLength: 4096,
653
+ maxErrorStackLength: 8192,
654
+ redactions: Object.freeze([])
655
+ });
656
+ var DEFAULT_TELEMETRY_BATCH_VALIDATION_LIMITS = Object.freeze({
657
+ maxBatchEvents: 1024,
658
+ maxBatchBytes: 1e6,
659
+ maxEventBytes: 256e3,
660
+ maxMetadataBytes: 131072,
661
+ maxMetadataDepth: 16,
662
+ maxMetadataProperties: 1024,
663
+ maxMetadataArrayLength: 1024,
664
+ maxMetadataStringBytes: 65536,
665
+ maxDropCounters: 15,
666
+ maxIdentityBytes: 1024
667
+ });
668
+
669
+ // src/core/telemetry_batch_contract.ts
670
+ var BATCH_KEYS = Object.freeze([
671
+ "schemaVersion",
672
+ "producerId",
673
+ "sequenceStart",
674
+ "sequenceEnd",
675
+ "idempotencyKey",
676
+ "checksum",
677
+ "envelopes",
678
+ "dropCounters"
679
+ ]);
680
+ var ENVELOPE_KEYS = Object.freeze([
681
+ "schemaVersion",
682
+ "eventId",
683
+ "producerId",
684
+ "sequence",
685
+ "occurredAtMs",
686
+ "monotonicNs",
687
+ "source",
688
+ "processKind",
689
+ "level",
690
+ "label",
691
+ "conversationId",
692
+ "correlationId",
693
+ "metadata"
694
+ ]);
695
+ var DROP_COUNTER_KEYS = Object.freeze(["reason", "priority", "count"]);
696
+ var DROP_REASONS = /* @__PURE__ */ new Set(["capacity", "sampled", "stopped", "invalid", "transport"]);
697
+ var DROP_PRIORITIES = /* @__PURE__ */ new Set(["low", "normal", "high"]);
698
+ var TelemetryBatchContract = class _TelemetryBatchContract {
699
+ static schemaVersion = 1;
700
+ static checksumAlgorithm = "fnv1a64-canonical-json-v1";
701
+ limits;
702
+ constructor(limits = {}) {
703
+ this.limits = Object.freeze({
704
+ ...DEFAULT_TELEMETRY_BATCH_VALIDATION_LIMITS,
705
+ ...limits
706
+ });
707
+ this.validateLimits();
708
+ }
709
+ create(envelopes, dropCounters) {
710
+ try {
711
+ return this.createChecked(envelopes, dropCounters);
712
+ } catch {
713
+ return { ok: false, error: "Telemetry batch creation failed safe validation" };
714
+ }
715
+ }
716
+ validate(batch) {
717
+ try {
718
+ return this.validateChecked(batch);
719
+ } catch {
720
+ return { ok: false, error: "Telemetry batch validation failed safely" };
721
+ }
722
+ }
723
+ createChecked(envelopes, dropCounters) {
724
+ const payloadError = this.validatePayload(envelopes, dropCounters);
725
+ if (payloadError !== null) {
726
+ return { ok: false, error: payloadError };
727
+ }
728
+ const payloadBytesError = this.payloadBytesError(envelopes, dropCounters);
729
+ if (payloadBytesError !== null) {
730
+ return { ok: false, error: payloadBytesError };
731
+ }
732
+ const first = envelopes[0];
733
+ const last = envelopes[envelopes.length - 1];
734
+ if (!first || !last) {
735
+ return { ok: false, error: "A telemetry batch must contain a first and last envelope" };
736
+ }
737
+ const producerId = first.producerId;
738
+ const sequenceStart = first.sequence;
739
+ const sequenceEnd = last.sequence;
740
+ const checksumPayload = this.checksumPayload(
741
+ producerId,
742
+ sequenceStart,
743
+ sequenceEnd,
744
+ envelopes,
745
+ dropCounters
746
+ );
747
+ const checksum = telemetryChecksum(checksumPayload);
748
+ const idempotencyKey = `tb1:${producerId}:${sequenceStart}-${sequenceEnd}:${checksum}`;
749
+ const batch = Object.freeze({
750
+ schemaVersion: _TelemetryBatchContract.schemaVersion,
751
+ producerId,
752
+ sequenceStart,
753
+ sequenceEnd,
754
+ idempotencyKey,
755
+ checksum,
756
+ envelopes: Object.freeze([...envelopes]),
757
+ dropCounters: Object.freeze([...dropCounters])
758
+ });
759
+ const framingError = this.validateFraming(batch);
760
+ if (framingError !== null) {
761
+ return { ok: false, error: framingError };
762
+ }
763
+ const byteError = this.serializedByteError(batch, this.limits.maxBatchBytes, "batch");
764
+ return byteError === null ? { ok: true, value: this.detachBatch(batch) } : { ok: false, error: byteError };
765
+ }
766
+ validateChecked(batch) {
767
+ if (!this.isRecord(batch)) {
768
+ return { ok: false, error: "Telemetry batch must be a plain object" };
769
+ }
770
+ const schemaVersion = batch.schemaVersion;
771
+ if (!Number.isSafeInteger(schemaVersion)) {
772
+ return { ok: false, error: "Telemetry batch schema version must be a safe integer" };
773
+ }
774
+ if (schemaVersion !== _TelemetryBatchContract.schemaVersion) {
775
+ return { ok: false, error: `Unsupported telemetry schema version ${String(schemaVersion)}` };
776
+ }
777
+ if (!this.hasExactKeys(batch, BATCH_KEYS)) {
778
+ return { ok: false, error: "Telemetry batch fields do not match the schema" };
779
+ }
780
+ const framingError = this.validateFraming(batch);
781
+ if (framingError !== null) {
782
+ return { ok: false, error: framingError };
783
+ }
784
+ const recreated = this.create(
785
+ batch.envelopes,
786
+ batch.dropCounters
787
+ );
788
+ if (!recreated.ok) {
789
+ return recreated;
790
+ }
791
+ const identityError = this.compareFraming(batch, recreated.value);
792
+ return identityError === null ? { ok: true, value: recreated.value } : { ok: false, error: identityError };
793
+ }
794
+ validatePayload(envelopes, dropCounters) {
795
+ if (!Array.isArray(envelopes) || envelopes.length === 0) {
796
+ return "A telemetry batch must contain at least one envelope";
797
+ }
798
+ if (envelopes.length > this.limits.maxBatchEvents) {
799
+ return `Telemetry batch exceeds ${this.limits.maxBatchEvents} envelopes`;
800
+ }
801
+ const envelopeError = this.validateEnvelopes(envelopes);
802
+ return envelopeError ?? this.validateDropCounters(dropCounters);
803
+ }
804
+ validateEnvelopes(envelopes) {
805
+ let previousSequence = -1;
806
+ let producerId = null;
807
+ for (const envelope of envelopes) {
808
+ const envelopeError = this.validateEnvelope(envelope, producerId, previousSequence);
809
+ if (envelopeError !== null) {
810
+ return envelopeError;
811
+ }
812
+ const typedEnvelope = envelope;
813
+ producerId = typedEnvelope.producerId;
814
+ previousSequence = typedEnvelope.sequence;
815
+ }
816
+ return null;
817
+ }
818
+ validateEnvelope(envelope, expectedProducerId, previousSequence) {
819
+ if (!this.isRecord(envelope) || !this.hasExactKeys(envelope, ENVELOPE_KEYS)) {
820
+ return "Telemetry envelope fields do not match the schema";
821
+ }
822
+ const identityError = this.validateEnvelopeIdentity(envelope, expectedProducerId, previousSequence);
823
+ if (identityError !== null) {
824
+ return identityError;
825
+ }
826
+ const valueError = this.validateEnvelopeValues(envelope);
827
+ if (valueError !== null) {
828
+ return valueError;
829
+ }
830
+ const metadataError = this.validateMetadata(envelope.metadata);
831
+ return metadataError ?? this.serializedByteError(envelope, this.limits.maxEventBytes, "event");
832
+ }
833
+ validateEnvelopeIdentity(envelope, expectedProducerId, previousSequence) {
834
+ if (envelope.schemaVersion !== _TelemetryBatchContract.schemaVersion) {
835
+ return "Telemetry envelope schema version does not match its batch";
836
+ }
837
+ if (!this.isIdentity(envelope.producerId)) {
838
+ return "Telemetry envelope producer identity is invalid";
839
+ }
840
+ if (!this.isSequence(envelope.sequence) || envelope.sequence <= previousSequence) {
841
+ return "Telemetry envelope sequences must be nonnegative safe integers in increasing order";
842
+ }
843
+ if (expectedProducerId !== null && envelope.producerId !== expectedProducerId) {
844
+ return "A telemetry batch cannot mix producer identities";
845
+ }
846
+ const eventId = `${envelope.producerId}:${envelope.sequence}`;
847
+ if (envelope.eventId !== eventId || telemetryUtf8Length(eventId) > this.limits.maxIdentityBytes) {
848
+ return "Telemetry envelope event identity is invalid";
849
+ }
850
+ return null;
851
+ }
852
+ validateEnvelopeValues(envelope) {
853
+ if (!Number.isSafeInteger(envelope.occurredAtMs) || Number(envelope.occurredAtMs) < 0) {
854
+ return "Telemetry envelope occurredAtMs must be a nonnegative safe integer";
855
+ }
856
+ if (!this.isMonotonicTimestamp(envelope.monotonicNs)) {
857
+ return "Telemetry envelope monotonicNs is invalid";
858
+ }
859
+ if (!this.isIdentity(envelope.source) || !this.isIdentity(envelope.processKind)) {
860
+ return "Telemetry envelope source and process kind are invalid";
861
+ }
862
+ if (!this.isIdentity(envelope.level)) {
863
+ return "Telemetry envelope level is invalid";
864
+ }
865
+ if (!this.isLabel(envelope.label)) {
866
+ return "Telemetry envelope label is invalid";
867
+ }
868
+ if (!this.isConversationId(envelope.conversationId)) {
869
+ return "Telemetry envelope conversation identity is invalid";
870
+ }
871
+ if (!this.isOptionalBoundedString(envelope.correlationId)) {
872
+ return "Telemetry envelope correlation identity is invalid";
873
+ }
874
+ return null;
875
+ }
876
+ validateMetadata(metadata) {
877
+ if (!this.isRecord(metadata)) {
878
+ return "Telemetry envelope metadata must be a plain JSON object";
879
+ }
880
+ const state = { properties: 0, bytes: 0, active: /* @__PURE__ */ new WeakSet() };
881
+ const jsonError = this.validateJsonValue(metadata, 0, state);
882
+ return jsonError ?? this.serializedByteError(metadata, this.limits.maxMetadataBytes, "metadata");
883
+ }
884
+ validateJsonValue(value, depth, state) {
885
+ if (value === null || typeof value === "boolean") {
886
+ state.bytes += value === null ? 4 : 5;
887
+ return null;
888
+ }
889
+ if (typeof value === "number") {
890
+ if (!Number.isFinite(value)) {
891
+ return "Telemetry metadata contains a non-finite number";
892
+ }
893
+ state.bytes += String(value).length;
894
+ return this.metadataBudgetError(state);
895
+ }
896
+ if (typeof value === "string") {
897
+ if (!this.isWithinUtf8Limit(value, this.limits.maxMetadataStringBytes)) {
898
+ return "Telemetry metadata string exceeds the receiver limit";
899
+ }
900
+ state.bytes += telemetryUtf8Length(value);
901
+ return this.metadataBudgetError(state);
902
+ }
903
+ if (typeof value !== "object") {
904
+ return "Telemetry metadata contains a non-JSON value";
905
+ }
906
+ if (depth > this.limits.maxMetadataDepth) {
907
+ return "Telemetry metadata exceeds the receiver depth limit";
908
+ }
909
+ return Array.isArray(value) ? this.validateJsonArray(value, depth, state) : this.validateJsonObject(value, depth, state);
910
+ }
911
+ validateJsonArray(value, depth, state) {
912
+ if (value.length > this.limits.maxMetadataArrayLength) {
913
+ return "Telemetry metadata array exceeds the receiver length limit";
914
+ }
915
+ const keys = Object.keys(value);
916
+ if (keys.length !== value.length || Object.getOwnPropertySymbols(value).length > 0) {
917
+ return "Telemetry metadata arrays must be dense JSON arrays";
918
+ }
919
+ return this.validateJsonChildren(value, value, depth, state);
920
+ }
921
+ validateJsonObject(value, depth, state) {
922
+ if (!this.isRecord(value)) {
923
+ return "Telemetry metadata contains a non-plain object";
924
+ }
925
+ const keys = Object.keys(value);
926
+ if (Object.getOwnPropertySymbols(value).length > 0) {
927
+ return "Telemetry metadata objects cannot contain symbol keys";
928
+ }
929
+ state.properties += keys.length;
930
+ if (state.properties > this.limits.maxMetadataProperties) {
931
+ return "Telemetry metadata exceeds the receiver property limit";
932
+ }
933
+ if (keys.some((key) => !this.isWithinUtf8Limit(key, this.limits.maxMetadataStringBytes))) {
934
+ return "Telemetry metadata property name exceeds the receiver string limit";
935
+ }
936
+ for (const key of keys) {
937
+ state.bytes += telemetryUtf8Length(key);
938
+ }
939
+ const budgetError = this.metadataBudgetError(state);
940
+ if (budgetError !== null) {
941
+ return budgetError;
942
+ }
943
+ return this.validateJsonChildren(value, keys.map((key) => Reflect.get(value, key)), depth, state);
944
+ }
945
+ validateJsonChildren(owner, values, depth, state) {
946
+ if (state.active.has(owner)) {
947
+ return "Telemetry metadata cannot contain a cycle";
948
+ }
949
+ state.active.add(owner);
950
+ try {
951
+ for (const value of values) {
952
+ const error = this.validateJsonValue(value, depth + 1, state);
953
+ if (error !== null) {
954
+ return error;
955
+ }
956
+ }
957
+ return null;
958
+ } finally {
959
+ state.active.delete(owner);
960
+ }
961
+ }
962
+ validateDropCounters(dropCounters) {
963
+ if (!Array.isArray(dropCounters) || dropCounters.length > this.limits.maxDropCounters) {
964
+ return "Telemetry drop counters exceed the fixed receiver limit";
965
+ }
966
+ const identities = /* @__PURE__ */ new Set();
967
+ let total = 0;
968
+ for (const counter of dropCounters) {
969
+ const error = this.validateDropCounter(counter, identities, total);
970
+ if (error !== null) {
971
+ return error;
972
+ }
973
+ total += counter.count;
974
+ }
975
+ return null;
976
+ }
977
+ validateDropCounter(counter, identities, currentTotal) {
978
+ if (!this.isRecord(counter) || !this.hasExactKeys(counter, DROP_COUNTER_KEYS)) {
979
+ return "Telemetry drop counter fields do not match the schema";
980
+ }
981
+ if (!DROP_REASONS.has(String(counter.reason)) || !DROP_PRIORITIES.has(String(counter.priority))) {
982
+ return "Telemetry drop counter identity is invalid";
983
+ }
984
+ if (!Number.isSafeInteger(counter.count) || Number(counter.count) <= 0) {
985
+ return "Telemetry drop counter count must be a positive safe integer";
986
+ }
987
+ if (currentTotal > Number.MAX_SAFE_INTEGER - Number(counter.count)) {
988
+ return "Telemetry drop counter total exceeds the safe integer range";
989
+ }
990
+ const identity = `${counter.reason}:${counter.priority}`;
991
+ if (identities.has(identity)) {
992
+ return "Telemetry drop counter identities must be unique within a batch";
993
+ }
994
+ identities.add(identity);
995
+ return null;
996
+ }
997
+ validateFraming(batch) {
998
+ if (!this.isIdentity(batch.producerId)) {
999
+ return "Telemetry batch producer identity is invalid";
1000
+ }
1001
+ if (!this.isSequence(batch.sequenceStart) || !this.isSequence(batch.sequenceEnd)) {
1002
+ return "Telemetry batch sequence range must use nonnegative safe integers";
1003
+ }
1004
+ if (Number(batch.sequenceEnd) < Number(batch.sequenceStart)) {
1005
+ return "Telemetry batch sequence range is reversed";
1006
+ }
1007
+ if (typeof batch.checksum !== "string" || !/^[a-f0-9]{16}$/.test(batch.checksum)) {
1008
+ return "Telemetry batch checksum is invalid";
1009
+ }
1010
+ if (typeof batch.idempotencyKey !== "string" || !this.isWithinUtf8Limit(batch.idempotencyKey, this.limits.maxIdentityBytes)) {
1011
+ return "Telemetry batch idempotency identity is invalid";
1012
+ }
1013
+ return null;
1014
+ }
1015
+ compareFraming(batch, recreated) {
1016
+ const matches = recreated.checksum === batch.checksum && recreated.idempotencyKey === batch.idempotencyKey && recreated.sequenceStart === batch.sequenceStart && recreated.sequenceEnd === batch.sequenceEnd && recreated.producerId === batch.producerId;
1017
+ return matches ? null : "Telemetry batch framing or checksum is invalid";
1018
+ }
1019
+ checksumPayload(producerId, sequenceStart, sequenceEnd, envelopes, dropCounters) {
1020
+ return {
1021
+ schemaVersion: _TelemetryBatchContract.schemaVersion,
1022
+ producerId,
1023
+ sequenceStart,
1024
+ sequenceEnd,
1025
+ envelopes,
1026
+ dropCounters
1027
+ };
1028
+ }
1029
+ serializedByteError(value, maximum, kind) {
1030
+ const bytes = telemetryUtf8Length(canonicalJson(value));
1031
+ return bytes <= maximum ? null : `Telemetry ${kind} exceeds ${maximum} bytes`;
1032
+ }
1033
+ payloadBytesError(envelopes, dropCounters) {
1034
+ let bytes = 0;
1035
+ for (const envelope of envelopes) {
1036
+ bytes += telemetryUtf8Length(canonicalJson(envelope));
1037
+ if (bytes > this.limits.maxBatchBytes) {
1038
+ return `Telemetry batch exceeds ${this.limits.maxBatchBytes} bytes`;
1039
+ }
1040
+ }
1041
+ bytes += telemetryUtf8Length(canonicalJson(dropCounters));
1042
+ return bytes > this.limits.maxBatchBytes ? `Telemetry batch exceeds ${this.limits.maxBatchBytes} bytes` : null;
1043
+ }
1044
+ metadataBudgetError(state) {
1045
+ return state.bytes > this.limits.maxMetadataBytes ? `Telemetry metadata exceeds ${this.limits.maxMetadataBytes} bytes` : null;
1046
+ }
1047
+ detachBatch(batch) {
1048
+ const detached = JSON.parse(canonicalJson(batch));
1049
+ return this.deepFreeze(detached);
1050
+ }
1051
+ deepFreeze(value) {
1052
+ if (typeof value !== "object" || value === null || Object.isFrozen(value)) {
1053
+ return value;
1054
+ }
1055
+ for (const child of Object.values(value)) {
1056
+ this.deepFreeze(child);
1057
+ }
1058
+ return Object.freeze(value);
1059
+ }
1060
+ isRecord(value) {
1061
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1062
+ return false;
1063
+ }
1064
+ const prototype = Object.getPrototypeOf(value);
1065
+ return prototype === null || prototype.constructor?.name === "Object";
1066
+ }
1067
+ hasExactKeys(value, expected) {
1068
+ const keys = Object.keys(value);
1069
+ return Object.getOwnPropertySymbols(value).length === 0 && keys.length === expected.length && expected.every((key) => Object.prototype.hasOwnProperty.call(value, key));
1070
+ }
1071
+ isSequence(value) {
1072
+ return Number.isSafeInteger(value) && Number(value) >= 0;
1073
+ }
1074
+ isIdentity(value) {
1075
+ return typeof value === "string" && value.length <= this.limits.maxIdentityBytes && value.trim().length > 0 && this.isWithinUtf8Limit(value, this.limits.maxIdentityBytes) && !/[\u0000-\u001f\u007f]/.test(value);
1076
+ }
1077
+ isLabel(value) {
1078
+ return typeof value === "string" && value.length <= Math.min(256, this.limits.maxIdentityBytes) && /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(value) && this.isWithinUtf8Limit(value, Math.min(256, this.limits.maxIdentityBytes));
1079
+ }
1080
+ isMonotonicTimestamp(value) {
1081
+ return value === null || typeof value === "string" && value.length <= this.limits.maxIdentityBytes && /^\d+$/.test(value) && this.isWithinUtf8Limit(value, this.limits.maxIdentityBytes);
1082
+ }
1083
+ isConversationId(value) {
1084
+ return value === null || typeof value === "number" && Number.isSafeInteger(value) || typeof value === "string" && this.isWithinUtf8Limit(value, this.limits.maxIdentityBytes) && !/[\u0000-\u001f\u007f]/.test(value);
1085
+ }
1086
+ isOptionalBoundedString(value) {
1087
+ return value === null || typeof value === "string" && this.isWithinUtf8Limit(value, this.limits.maxIdentityBytes) && !/[\u0000-\u001f\u007f]/.test(value);
1088
+ }
1089
+ isWithinUtf8Limit(value, maximum) {
1090
+ return value.length <= maximum && telemetryUtf8Length(value) <= maximum;
1091
+ }
1092
+ validateLimits() {
1093
+ const values = Object.values(this.limits);
1094
+ if (values.some((value) => !Number.isSafeInteger(value) || value <= 0)) {
1095
+ throw new Error("Telemetry batch validation limits must be positive safe integers");
1096
+ }
1097
+ if (this.limits.maxMetadataBytes > this.limits.maxEventBytes || this.limits.maxEventBytes > this.limits.maxBatchBytes) {
1098
+ throw new Error("Telemetry receiver byte limits must satisfy metadata <= event <= batch");
1099
+ }
1100
+ }
1101
+ };
1102
+
1103
+ // src/core/helper/telemetry_deadline_guard.ts
1104
+ var MAX_TIMER_DELAY_MS = 2147483647;
1105
+ var TelemetryDeadlineGuard = class {
1106
+ async settle(operation, deadlineMs, now) {
1107
+ const observed = operation.then(
1108
+ (value) => ({ kind: "completed", value }),
1109
+ (error) => ({ kind: "failed", error })
1110
+ );
1111
+ const remainingMs = deadlineMs - now();
1112
+ if (!Number.isFinite(remainingMs) || remainingMs <= 0) {
1113
+ return { kind: "deadline-exhausted" };
1114
+ }
1115
+ let timer = null;
1116
+ const outcome = await Promise.race([
1117
+ observed,
1118
+ new Promise((resolve) => {
1119
+ timer = setTimeout(
1120
+ () => resolve({ kind: "deadline-exhausted" }),
1121
+ Math.min(remainingMs, MAX_TIMER_DELAY_MS)
1122
+ );
1123
+ })
1124
+ ]);
1125
+ if (timer !== null) {
1126
+ clearTimeout(timer);
1127
+ }
1128
+ return outcome;
1129
+ }
1130
+ };
1131
+
1132
+ // src/core/telemetry_batch_router.ts
1133
+ var TelemetryBatchRouter = class {
1134
+ constructor(options) {
1135
+ this.options = options;
1136
+ this.transportId = options.routerId;
1137
+ this.validateRoutes();
1138
+ this.capabilities = Object.freeze({
1139
+ maxBatchBytes: Math.min(...options.routes.map((route) => route.transport.capabilities.maxBatchBytes)),
1140
+ acknowledgement: options.volatile ? "receipt" : "durable",
1141
+ ordering: options.routes.every((route) => route.transport.capabilities.ordering === "producer") ? "producer" : "none",
1142
+ boundedCapacity: Math.min(...options.routes.map((route) => route.transport.capabilities.boundedCapacity))
1143
+ });
1144
+ }
1145
+ options;
1146
+ transportId;
1147
+ capabilities;
1148
+ acknowledgementContract = new TelemetryAcknowledgementContract();
1149
+ deadlineGuard = new TelemetryDeadlineGuard();
1150
+ committed = /* @__PURE__ */ new Map();
1151
+ async send(batch) {
1152
+ const matchingRoutes = this.options.routes.filter((route) => route.matches(batch));
1153
+ const hasRequiredRoute = matchingRoutes.some((route) => route.delivery === "required");
1154
+ if (!hasRequiredRoute && !this.options.volatile) {
1155
+ return {
1156
+ kind: "rejected",
1157
+ idempotencyKey: batch.idempotencyKey,
1158
+ code: "no-required-terminal",
1159
+ reason: "No required durable route matched this batch",
1160
+ targetStates: this.unmatchedStates(batch, matchingRoutes)
1161
+ };
1162
+ }
1163
+ const committedRoutes = this.committed.get(batch.idempotencyKey) ?? /* @__PURE__ */ new Set();
1164
+ const routeResults = await this.sendMatchingRoutes(batch, matchingRoutes, committedRoutes);
1165
+ this.remember(batch.idempotencyKey, committedRoutes);
1166
+ return this.aggregateAcknowledgement(batch, matchingRoutes, routeResults);
1167
+ }
1168
+ async flush(deadlineMs) {
1169
+ await this.runLifecycleUntilDeadline(
1170
+ deadlineMs,
1171
+ (transport) => transport.flush(deadlineMs)
1172
+ );
1173
+ }
1174
+ async close(deadlineMs) {
1175
+ await this.runLifecycleUntilDeadline(
1176
+ deadlineMs,
1177
+ (transport) => transport.close(deadlineMs)
1178
+ );
1179
+ }
1180
+ async runLifecycleUntilDeadline(deadlineMs, run) {
1181
+ const operations = this.options.routes.map((route) => {
1182
+ return Promise.resolve().then(() => run(route.transport));
1183
+ });
1184
+ const outcome = await this.deadlineGuard.settle(
1185
+ Promise.all(operations),
1186
+ deadlineMs,
1187
+ this.options.now
1188
+ );
1189
+ if (outcome.kind === "failed") {
1190
+ throw outcome.error;
1191
+ }
1192
+ }
1193
+ async sendMatchingRoutes(batch, routes, committedRoutes) {
1194
+ return Promise.all(routes.map(async (route) => {
1195
+ if (committedRoutes.has(route.routeId)) {
1196
+ return { route, acknowledgement: this.alreadyCommitted(batch), attempts: 0 };
1197
+ }
1198
+ const acknowledgement = await this.sendSafely(route.transport, batch);
1199
+ if (acknowledgement.kind === "committed") {
1200
+ committedRoutes.add(route.routeId);
1201
+ }
1202
+ return { route, acknowledgement, attempts: 1 };
1203
+ }));
1204
+ }
1205
+ aggregateAcknowledgement(batch, matchingRoutes, routeResults) {
1206
+ const targetStates = this.deliveryStates(batch, routeResults, matchingRoutes);
1207
+ const requiredResults = routeResults.filter(({ route }) => route.delivery === "required");
1208
+ const conflict = requiredResults.find(({ acknowledgement }) => acknowledgement.kind === "conflict");
1209
+ const conflictAcknowledgement = conflict?.acknowledgement;
1210
+ if (conflictAcknowledgement?.kind === "conflict") {
1211
+ return {
1212
+ kind: "conflict",
1213
+ idempotencyKey: batch.idempotencyKey,
1214
+ reason: conflictAcknowledgement.reason,
1215
+ targetStates
1216
+ };
1217
+ }
1218
+ const rejected = requiredResults.find(({ acknowledgement }) => acknowledgement.kind === "rejected");
1219
+ const rejectedAcknowledgement = rejected?.acknowledgement;
1220
+ if (rejectedAcknowledgement?.kind === "rejected") {
1221
+ return {
1222
+ kind: "rejected",
1223
+ idempotencyKey: batch.idempotencyKey,
1224
+ code: rejectedAcknowledgement.code,
1225
+ reason: rejectedAcknowledgement.reason,
1226
+ targetStates
1227
+ };
1228
+ }
1229
+ const retryable = requiredResults.find(({ acknowledgement }) => acknowledgement.kind === "retryable");
1230
+ const retryableAcknowledgement = retryable?.acknowledgement;
1231
+ if (retryableAcknowledgement?.kind === "retryable") {
1232
+ return {
1233
+ kind: "retryable",
1234
+ idempotencyKey: batch.idempotencyKey,
1235
+ reason: retryableAcknowledgement.reason,
1236
+ retryAfterMs: retryableAcknowledgement.retryAfterMs,
1237
+ targetStates
1238
+ };
1239
+ }
1240
+ return {
1241
+ kind: "committed",
1242
+ idempotencyKey: batch.idempotencyKey,
1243
+ committedAtMs: this.options.now(),
1244
+ acceptedEvents: batch.envelopes.length,
1245
+ duplicate: routeResults.length > 0 && routeResults.every(({ attempts }) => attempts === 0),
1246
+ targetStates
1247
+ };
1248
+ }
1249
+ deliveryStates(batch, routeResults, matchingRoutes) {
1250
+ return [
1251
+ ...routeResults.map(({ route, acknowledgement, attempts }) => this.deliveryState(
1252
+ route.routeId,
1253
+ route.delivery,
1254
+ acknowledgement,
1255
+ attempts
1256
+ )),
1257
+ ...this.unmatchedStates(batch, matchingRoutes)
1258
+ ];
1259
+ }
1260
+ async sendSafely(transport, batch) {
1261
+ try {
1262
+ const received = await transport.send(batch);
1263
+ const validated = this.acknowledgementContract.validateAndDetach(batch, received);
1264
+ return validated.ok ? validated.value : this.invalidAcknowledgement(batch, validated.error);
1265
+ } catch (cause) {
1266
+ return {
1267
+ kind: "retryable",
1268
+ idempotencyKey: batch.idempotencyKey,
1269
+ reason: this.acknowledgementContract.exceptionReason(cause),
1270
+ retryAfterMs: 25,
1271
+ targetStates: []
1272
+ };
1273
+ }
1274
+ }
1275
+ invalidAcknowledgement(batch, error) {
1276
+ return {
1277
+ kind: "retryable",
1278
+ idempotencyKey: batch.idempotencyKey,
1279
+ reason: `Invalid telemetry acknowledgement: ${error}`,
1280
+ retryAfterMs: 25,
1281
+ targetStates: []
1282
+ };
1283
+ }
1284
+ alreadyCommitted(batch) {
1285
+ return {
1286
+ kind: "committed",
1287
+ idempotencyKey: batch.idempotencyKey,
1288
+ committedAtMs: this.options.now(),
1289
+ acceptedEvents: batch.envelopes.length,
1290
+ duplicate: true,
1291
+ targetStates: []
1292
+ };
1293
+ }
1294
+ deliveryState(routeId, delivery, acknowledgement, attempts) {
1295
+ if (acknowledgement.kind === "committed") {
1296
+ return { targetId: routeId, delivery, state: "committed", attempts, reason: null };
1297
+ }
1298
+ return {
1299
+ targetId: routeId,
1300
+ delivery,
1301
+ state: "failed",
1302
+ attempts,
1303
+ reason: acknowledgement.reason
1304
+ };
1305
+ }
1306
+ unmatchedStates(_batch, matchingRoutes) {
1307
+ const matchingIds = new Set(matchingRoutes.map((route) => route.routeId));
1308
+ return this.options.routes.filter((route) => !matchingIds.has(route.routeId)).map((route) => ({
1309
+ targetId: route.routeId,
1310
+ delivery: route.delivery,
1311
+ state: "abandoned",
1312
+ attempts: 0,
1313
+ reason: "route-not-matched"
1314
+ }));
1315
+ }
1316
+ validateRoutes() {
1317
+ if (!this.options.routerId.trim() || this.options.routes.length === 0) {
1318
+ throw new Error("A telemetry batch router requires an id and at least one route");
1319
+ }
1320
+ if (this.options.maxRememberedBatches !== void 0 && (!Number.isSafeInteger(this.options.maxRememberedBatches) || this.options.maxRememberedBatches <= 0)) {
1321
+ throw new Error("Telemetry batch router memory must be a positive safe integer");
1322
+ }
1323
+ const routeIds = this.options.routes.map((route) => route.routeId);
1324
+ if (new Set(routeIds).size !== routeIds.length) {
1325
+ throw new Error("Telemetry batch route ids must be unique");
1326
+ }
1327
+ const requiredRoutes = this.options.routes.filter((route) => route.delivery === "required");
1328
+ const hasNonDurableRequiredRoute = requiredRoutes.some(
1329
+ (route) => route.transport.capabilities.acknowledgement !== "durable"
1330
+ );
1331
+ if (!this.options.volatile && (requiredRoutes.length === 0 || hasNonDurableRequiredRoute)) {
1332
+ throw new Error("A non-volatile router requires at least one durable required route");
1333
+ }
1334
+ }
1335
+ remember(idempotencyKey, routes) {
1336
+ this.committed.set(idempotencyKey, routes);
1337
+ const maximum = this.options.maxRememberedBatches ?? 1024;
1338
+ while (this.committed.size > maximum) {
1339
+ const oldest = this.committed.keys().next().value;
1340
+ if (typeof oldest !== "string") {
1341
+ return;
1342
+ }
1343
+ this.committed.delete(oldest);
1344
+ }
1345
+ }
1346
+ };
1347
+
1348
+ // src/core/telemetry_client.ts
1349
+ var TelemetryClient = class {
1350
+ constructor(options) {
1351
+ this.options = options;
1352
+ this.validateOptions();
1353
+ this.snapshotter = new BoundedMetadataSnapshotter(options.metadata);
1354
+ }
1355
+ options;
1356
+ snapshotter;
1357
+ batchContract = new TelemetryBatchContract();
1358
+ queue = [];
1359
+ dropCounts = /* @__PURE__ */ new Map();
1360
+ state = "accepting";
1361
+ nextSequence = 0;
1362
+ queuedBytes = 0;
1363
+ inFlightBatches = 0;
1364
+ drainScheduled = false;
1365
+ activeDrain = null;
1366
+ lastAcknowledgement = null;
1367
+ trace(level, label, metadata = {}) {
1368
+ return this.emit(level, label, metadata);
1369
+ }
1370
+ debug(label, metadata = {}) {
1371
+ return this.emit("debug", label, metadata);
1372
+ }
1373
+ info(label, metadata = {}) {
1374
+ return this.emit("info", label, metadata);
1375
+ }
1376
+ warn(label, metadata = {}) {
1377
+ return this.emit("warn", label, metadata);
1378
+ }
1379
+ error(label, metadata = {}) {
1380
+ return this.emit("error", label, metadata);
1381
+ }
1382
+ fatal(label, metadata = {}) {
1383
+ return this.emit("fatal", label, metadata);
1384
+ }
1385
+ emit(level, label, metadata) {
1386
+ return this.admit({
1387
+ source: this.options.source,
1388
+ processKind: this.options.processKind,
1389
+ level,
1390
+ label,
1391
+ occurredAtMs: this.options.now(),
1392
+ monotonicNs: this.options.monotonicNowNs(),
1393
+ conversationId: null,
1394
+ correlationId: null,
1395
+ metadata
1396
+ });
1397
+ }
1398
+ admit(input) {
1399
+ const sequence = this.nextSequence;
1400
+ this.nextSequence += 1;
1401
+ if (this.state !== "accepting") {
1402
+ this.incrementDrop("stopped", this.priorityFor(input.level));
1403
+ return { kind: "dropped", producerId: this.options.producerId, sequence, reason: "client-stopped" };
1404
+ }
1405
+ const hasInvalidIdentity = !this.isValidLabel(input.label) || !input.source.trim() || !input.processKind.trim() || !Number.isFinite(input.occurredAtMs);
1406
+ if (hasInvalidIdentity) {
1407
+ this.incrementDrop("invalid", this.priorityFor(input.level));
1408
+ return { kind: "dropped", producerId: this.options.producerId, sequence, reason: "invalid-label" };
1409
+ }
1410
+ const priority = this.priorityFor(input.level);
1411
+ if (!this.isSampled(priority)) {
1412
+ this.incrementDrop("sampled", priority);
1413
+ return { kind: "dropped", producerId: this.options.producerId, sequence, reason: "sampled" };
1414
+ }
1415
+ const snapshot = this.snapshotter.snapshot(input.metadata);
1416
+ const extractedCorrelation = this.resolveCorrelation(snapshot.metadata);
1417
+ const correlation = {
1418
+ conversationId: input.conversationId ?? extractedCorrelation.conversationId,
1419
+ correlationId: input.correlationId ?? extractedCorrelation.correlationId
1420
+ };
1421
+ const envelope = this.createEnvelope(sequence, input, snapshot.metadata, correlation);
1422
+ const bytes = telemetryUtf8Length(canonicalJson(envelope));
1423
+ const single = this.batchContract.create([envelope], []);
1424
+ if (!single.ok) {
1425
+ this.incrementDrop("invalid", priority);
1426
+ return { kind: "dropped", producerId: this.options.producerId, sequence, reason: "invalid-envelope" };
1427
+ }
1428
+ const framedBytes = telemetryUtf8Length(canonicalJson(single.value));
1429
+ const queueWouldOverflow = this.queue.length >= this.options.admission.maxQueuedEvents || this.queuedBytes + bytes > this.options.admission.maxQueuedBytes || framedBytes > this.options.admission.maxBatchBytes;
1430
+ if (queueWouldOverflow) {
1431
+ this.incrementDrop("capacity", priority);
1432
+ return { kind: "dropped", producerId: this.options.producerId, sequence, reason: "capacity" };
1433
+ }
1434
+ this.queue.push(Object.freeze({ envelope, bytes }));
1435
+ this.queuedBytes += bytes;
1436
+ this.scheduleDrain();
1437
+ return {
1438
+ kind: "admitted",
1439
+ producerId: this.options.producerId,
1440
+ sequence,
1441
+ eventId: envelope.eventId
1442
+ };
1443
+ }
1444
+ status() {
1445
+ return Object.freeze({
1446
+ state: this.state,
1447
+ producerId: this.options.producerId,
1448
+ nextSequence: this.nextSequence,
1449
+ queuedEvents: this.queue.length,
1450
+ queuedBytes: this.queuedBytes,
1451
+ inFlightBatches: this.inFlightBatches,
1452
+ dropCounters: this.dropCounterSnapshot(),
1453
+ lastAcknowledgement: this.lastAcknowledgement
1454
+ });
1455
+ }
1456
+ async flush(deadlineMs) {
1457
+ this.scheduleDrain();
1458
+ while ((this.queue.length > 0 || this.activeDrain !== null) && this.options.now() < deadlineMs) {
1459
+ const currentDrain = this.activeDrain;
1460
+ if (currentDrain) {
1461
+ await Promise.race([currentDrain, this.options.delay(1)]);
1462
+ } else {
1463
+ await this.options.delay(1);
1464
+ }
1465
+ }
1466
+ await this.settleExternalShutdown(
1467
+ () => this.options.transport.flush(deadlineMs),
1468
+ deadlineMs
1469
+ );
1470
+ }
1471
+ async stop(deadlineMs) {
1472
+ if (this.state === "stopped") {
1473
+ return;
1474
+ }
1475
+ this.state = "draining";
1476
+ await this.flush(deadlineMs);
1477
+ const abandoned = this.queue.splice(0, this.queue.length);
1478
+ if (abandoned.length > 0) {
1479
+ this.incrementDropBy("transport", "normal", abandoned.length);
1480
+ this.queuedBytes = 0;
1481
+ }
1482
+ await this.settleExternalShutdown(
1483
+ () => this.options.transport.close(deadlineMs),
1484
+ deadlineMs
1485
+ );
1486
+ this.state = "stopped";
1487
+ }
1488
+ createEnvelope(sequence, input, metadata, correlation) {
1489
+ return Object.freeze({
1490
+ schemaVersion: TelemetryBatchContract.schemaVersion,
1491
+ eventId: `${this.options.producerId}:${sequence}`,
1492
+ producerId: this.options.producerId,
1493
+ sequence,
1494
+ occurredAtMs: input.occurredAtMs,
1495
+ monotonicNs: input.monotonicNs,
1496
+ source: input.source,
1497
+ processKind: input.processKind,
1498
+ level: input.level.trim().toLowerCase() || "info",
1499
+ label: input.label,
1500
+ conversationId: correlation.conversationId,
1501
+ correlationId: correlation.correlationId,
1502
+ metadata
1503
+ });
1504
+ }
1505
+ resolveCorrelation(metadata) {
1506
+ const conversationValue = metadata.conversationId ?? metadata.threadId ?? null;
1507
+ const conversationId = typeof conversationValue === "string" || typeof conversationValue === "number" ? conversationValue : null;
1508
+ const correlationValue = metadata.correlationId;
1509
+ const correlationId = typeof correlationValue === "string" ? correlationValue : null;
1510
+ return { conversationId, correlationId };
1511
+ }
1512
+ scheduleDrain() {
1513
+ if (this.drainScheduled || this.activeDrain !== null || this.queue.length === 0) {
1514
+ return;
1515
+ }
1516
+ this.drainScheduled = true;
1517
+ try {
1518
+ this.options.schedule(() => {
1519
+ this.drainScheduled = false;
1520
+ const drain = this.drainLoop();
1521
+ this.activeDrain = drain;
1522
+ const settle = () => {
1523
+ this.activeDrain = null;
1524
+ if (this.queue.length > 0) {
1525
+ this.scheduleDrain();
1526
+ }
1527
+ };
1528
+ void drain.then(settle, settle);
1529
+ });
1530
+ } catch {
1531
+ this.drainScheduled = false;
1532
+ }
1533
+ }
1534
+ async drainLoop() {
1535
+ while (this.queue.length > 0) {
1536
+ const dropSnapshot = this.dropCounterSnapshot();
1537
+ const prepared = this.prepareBatch(dropSnapshot);
1538
+ if (prepared === null) {
1539
+ continue;
1540
+ }
1541
+ this.inFlightBatches += 1;
1542
+ try {
1543
+ const acknowledgement = await this.deliverWithRetry(prepared.batch);
1544
+ this.lastAcknowledgement = acknowledgement;
1545
+ if (acknowledgement.kind === "committed") {
1546
+ this.subtractDropSnapshot(prepared.dropCounters);
1547
+ } else {
1548
+ this.incrementDropBy("transport", "normal", prepared.eventCount);
1549
+ }
1550
+ } finally {
1551
+ this.inFlightBatches -= 1;
1552
+ }
1553
+ }
1554
+ }
1555
+ prepareBatch(dropCounters) {
1556
+ const entries = this.peekBatchEntries();
1557
+ const withCounters = this.findFittingBatch(entries, dropCounters);
1558
+ const prepared = withCounters ?? this.findFittingBatch(entries, []);
1559
+ if (prepared === null) {
1560
+ const invalid = this.queue.shift();
1561
+ if (invalid !== void 0) {
1562
+ this.queuedBytes -= invalid.bytes;
1563
+ this.incrementDrop("invalid", this.priorityFor(invalid.envelope.level));
1564
+ }
1565
+ return null;
1566
+ }
1567
+ for (let index = 0; index < prepared.eventCount; index += 1) {
1568
+ const removed = this.queue.shift();
1569
+ if (removed !== void 0) {
1570
+ this.queuedBytes -= removed.bytes;
1571
+ }
1572
+ }
1573
+ return prepared;
1574
+ }
1575
+ peekBatchEntries() {
1576
+ const entries = [];
1577
+ let batchBytes = 0;
1578
+ while (entries.length < this.options.admission.maxBatchEvents) {
1579
+ const next = this.queue[entries.length];
1580
+ if (!next) {
1581
+ break;
1582
+ }
1583
+ const exceedsBatchBytes = entries.length > 0 && batchBytes + next.bytes > this.options.admission.maxBatchBytes;
1584
+ if (exceedsBatchBytes) {
1585
+ break;
1586
+ }
1587
+ batchBytes += next.bytes;
1588
+ entries.push(next);
1589
+ }
1590
+ return entries;
1591
+ }
1592
+ findFittingBatch(entries, dropCounters) {
1593
+ for (let eventCount = entries.length; eventCount > 0; eventCount -= 1) {
1594
+ const created = this.batchContract.create(
1595
+ entries.slice(0, eventCount).map((entry) => entry.envelope),
1596
+ dropCounters
1597
+ );
1598
+ if (created.ok && telemetryUtf8Length(canonicalJson(created.value)) <= this.options.admission.maxBatchBytes) {
1599
+ return Object.freeze({ batch: created.value, dropCounters, eventCount });
1600
+ }
1601
+ }
1602
+ return null;
1603
+ }
1604
+ async deliverWithRetry(batch) {
1605
+ let attempt = 0;
1606
+ while (attempt <= this.options.admission.retryLimit) {
1607
+ let acknowledgement;
1608
+ try {
1609
+ const received = await this.options.transport.send(batch);
1610
+ acknowledgement = this.normalizeAcknowledgement(batch, received, attempt);
1611
+ } catch (cause) {
1612
+ acknowledgement = {
1613
+ kind: "retryable",
1614
+ idempotencyKey: batch.idempotencyKey,
1615
+ reason: this.errorMessage(cause),
1616
+ retryAfterMs: this.retryDelay(attempt),
1617
+ targetStates: []
1618
+ };
1619
+ }
1620
+ if (acknowledgement.kind !== "retryable" || attempt === this.options.admission.retryLimit) {
1621
+ return acknowledgement;
1622
+ }
1623
+ const requestedDelay = Math.max(acknowledgement.retryAfterMs, this.retryDelay(attempt));
1624
+ await this.options.delay(requestedDelay);
1625
+ attempt += 1;
1626
+ }
1627
+ return {
1628
+ kind: "retryable",
1629
+ idempotencyKey: batch.idempotencyKey,
1630
+ reason: "retry-limit-exhausted",
1631
+ retryAfterMs: 0,
1632
+ targetStates: []
1633
+ };
1634
+ }
1635
+ retryDelay(attempt) {
1636
+ return Math.min(
1637
+ Number.MAX_SAFE_INTEGER,
1638
+ this.options.admission.retryBaseDelayMs * 2 ** attempt
1639
+ );
1640
+ }
1641
+ normalizeAcknowledgement(batch, value, attempt) {
1642
+ try {
1643
+ const error = this.acknowledgementError(batch, value);
1644
+ return error === null ? this.detachAcknowledgement(value) : this.invalidAcknowledgement(batch, error, attempt);
1645
+ } catch {
1646
+ return this.invalidAcknowledgement(batch, "acknowledgement could not be inspected", attempt);
1647
+ }
1648
+ }
1649
+ acknowledgementError(batch, value) {
1650
+ if (!this.isRecord(value) || value.idempotencyKey !== batch.idempotencyKey) {
1651
+ return "acknowledgement identity is missing or mismatched";
1652
+ }
1653
+ const targetsError = this.targetStatesError(value.targetStates);
1654
+ if (targetsError !== null) {
1655
+ return targetsError;
1656
+ }
1657
+ switch (value.kind) {
1658
+ case "committed":
1659
+ return this.committedAcknowledgementError(batch, value);
1660
+ case "retryable":
1661
+ return this.retryableAcknowledgementError(value);
1662
+ case "rejected":
1663
+ return this.rejectedAcknowledgementError(value);
1664
+ case "conflict":
1665
+ return this.conflictAcknowledgementError(value);
1666
+ default:
1667
+ return "acknowledgement kind is invalid";
1668
+ }
1669
+ }
1670
+ committedAcknowledgementError(batch, value) {
1671
+ const valid = this.hasExactKeys(value, [
1672
+ "kind",
1673
+ "idempotencyKey",
1674
+ "committedAtMs",
1675
+ "acceptedEvents",
1676
+ "duplicate",
1677
+ "targetStates"
1678
+ ]) && Number.isSafeInteger(value.committedAtMs) && Number(value.committedAtMs) >= 0 && value.acceptedEvents === batch.envelopes.length && typeof value.duplicate === "boolean";
1679
+ return valid ? null : "committed acknowledgement fields are invalid";
1680
+ }
1681
+ retryableAcknowledgementError(value) {
1682
+ const valid = this.hasExactKeys(value, [
1683
+ "kind",
1684
+ "idempotencyKey",
1685
+ "reason",
1686
+ "retryAfterMs",
1687
+ "targetStates"
1688
+ ]) && this.isBoundedReason(value.reason) && Number.isFinite(value.retryAfterMs) && Number(value.retryAfterMs) >= 0 && Number(value.retryAfterMs) <= Number.MAX_SAFE_INTEGER;
1689
+ return valid ? null : "retryable acknowledgement fields are invalid";
1690
+ }
1691
+ rejectedAcknowledgementError(value) {
1692
+ const codes = ["invalid-batch", "unsupported-version", "no-required-terminal", "closed"];
1693
+ const valid = this.hasExactKeys(value, [
1694
+ "kind",
1695
+ "idempotencyKey",
1696
+ "code",
1697
+ "reason",
1698
+ "targetStates"
1699
+ ]) && codes.includes(String(value.code)) && this.isBoundedReason(value.reason);
1700
+ return valid ? null : "rejected acknowledgement fields are invalid";
1701
+ }
1702
+ conflictAcknowledgementError(value) {
1703
+ const valid = this.hasExactKeys(value, ["kind", "idempotencyKey", "reason", "targetStates"]) && this.isBoundedReason(value.reason);
1704
+ return valid ? null : "conflict acknowledgement fields are invalid";
1705
+ }
1706
+ targetStatesError(value) {
1707
+ if (!Array.isArray(value) || value.length > 1024) {
1708
+ return "acknowledgement target states are invalid";
1709
+ }
1710
+ const targetIds = /* @__PURE__ */ new Set();
1711
+ for (const target of value) {
1712
+ if (!this.isValidTargetState(target) || targetIds.has(target.targetId)) {
1713
+ return "acknowledgement target states are invalid";
1714
+ }
1715
+ targetIds.add(target.targetId);
1716
+ }
1717
+ return null;
1718
+ }
1719
+ isValidTargetState(value) {
1720
+ if (!this.isRecord(value) || !this.hasExactKeys(value, ["targetId", "delivery", "state", "attempts", "reason"])) {
1721
+ return false;
1722
+ }
1723
+ const deliveries = ["required", "best-effort"];
1724
+ const states = ["pending", "committed", "failed", "abandoned"];
1725
+ return typeof value.targetId === "string" && value.targetId.length > 0 && telemetryUtf8Length(value.targetId) <= 256 && deliveries.includes(String(value.delivery)) && states.includes(String(value.state)) && Number.isSafeInteger(value.attempts) && Number(value.attempts) >= 0 && (value.reason === null || this.isBoundedReason(value.reason));
1726
+ }
1727
+ invalidAcknowledgement(batch, error, attempt) {
1728
+ return {
1729
+ kind: "retryable",
1730
+ idempotencyKey: batch.idempotencyKey,
1731
+ reason: `Invalid telemetry acknowledgement: ${error}`,
1732
+ retryAfterMs: this.retryDelay(attempt),
1733
+ targetStates: []
1734
+ };
1735
+ }
1736
+ detachAcknowledgement(value) {
1737
+ const detached = JSON.parse(canonicalJson(value));
1738
+ const targetStates = Object.freeze(detached.targetStates.map((target) => Object.freeze({ ...target })));
1739
+ return Object.freeze({ ...detached, targetStates });
1740
+ }
1741
+ isRecord(value) {
1742
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1743
+ }
1744
+ hasExactKeys(value, expected) {
1745
+ const keys = Object.keys(value);
1746
+ return Object.getOwnPropertySymbols(value).length === 0 && keys.length === expected.length && expected.every((key) => Object.prototype.hasOwnProperty.call(value, key));
1747
+ }
1748
+ isBoundedReason(value) {
1749
+ return typeof value === "string" && telemetryUtf8Length(value) <= 512;
1750
+ }
1751
+ dropCounterSnapshot() {
1752
+ const counters = [...this.dropCounts.entries()].filter(([, count]) => count > 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, count]) => {
1753
+ const [reason, priority] = key.split(":");
1754
+ return Object.freeze({
1755
+ reason,
1756
+ priority,
1757
+ count
1758
+ });
1759
+ });
1760
+ return Object.freeze(counters);
1761
+ }
1762
+ subtractDropSnapshot(snapshot) {
1763
+ for (const counter of snapshot) {
1764
+ const key = `${counter.reason}:${counter.priority}`;
1765
+ const current = this.dropCounts.get(key) ?? 0;
1766
+ this.dropCounts.set(key, Math.max(0, current - counter.count));
1767
+ }
1768
+ }
1769
+ incrementDrop(reason, priority) {
1770
+ this.incrementDropBy(reason, priority, 1);
1771
+ }
1772
+ incrementDropBy(reason, priority, count) {
1773
+ const key = `${reason}:${priority}`;
1774
+ const current = this.dropCounts.get(key) ?? 0;
1775
+ this.dropCounts.set(key, Math.min(Number.MAX_SAFE_INTEGER, current + count));
1776
+ }
1777
+ priorityFor(level) {
1778
+ const normalized = level.trim().toLowerCase();
1779
+ if (normalized === "error" || normalized === "fatal") {
1780
+ return "high";
1781
+ }
1782
+ if (normalized === "trace" || normalized === "debug") {
1783
+ return "low";
1784
+ }
1785
+ return "normal";
1786
+ }
1787
+ isSampled(priority) {
1788
+ const sampleRate = this.options.admission.sampleRates[priority];
1789
+ return sampleRate >= 1 || this.options.random() < sampleRate;
1790
+ }
1791
+ isValidLabel(label) {
1792
+ return /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(label) && label.length <= 256;
1793
+ }
1794
+ errorMessage(cause) {
1795
+ if (cause instanceof Error) {
1796
+ return cause.message.slice(0, 512);
1797
+ }
1798
+ return String(cause).slice(0, 512);
1799
+ }
1800
+ async settleExternalShutdown(work, deadlineMs) {
1801
+ const operation = Promise.resolve().then(work);
1802
+ const remainingMs = deadlineMs - this.options.now();
1803
+ if (!Number.isFinite(remainingMs) || remainingMs <= 0) {
1804
+ void operation.catch(() => void 0);
1805
+ return;
1806
+ }
1807
+ await new Promise((resolve, reject) => {
1808
+ let settled = false;
1809
+ const finish = (failed, error) => {
1810
+ if (settled) {
1811
+ return;
1812
+ }
1813
+ settled = true;
1814
+ clearTimeout(timer);
1815
+ if (failed) {
1816
+ reject(error);
1817
+ } else {
1818
+ resolve();
1819
+ }
1820
+ };
1821
+ const timer = setTimeout(
1822
+ () => finish(false),
1823
+ Math.min(remainingMs, 2147483647)
1824
+ );
1825
+ void operation.then(
1826
+ () => finish(false),
1827
+ (error) => finish(true, error)
1828
+ );
1829
+ });
1830
+ }
1831
+ validateOptions() {
1832
+ this.validateIdentityOptions();
1833
+ this.validateOrderedDeliveryOptions();
1834
+ this.validateCapacityOptions();
1835
+ this.validateBatchEventOptions();
1836
+ this.validateBatchByteOptions();
1837
+ this.validateMetadataOptions();
1838
+ this.validateRetryOptions();
1839
+ this.validateSampleRateOptions();
1840
+ }
1841
+ validateIdentityOptions() {
1842
+ if (!this.options.source.trim() || !this.options.processKind.trim() || !this.options.producerId.trim()) {
1843
+ throw new Error("Telemetry client source, process kind, and producer id are required");
1844
+ }
1845
+ }
1846
+ validateOrderedDeliveryOptions() {
1847
+ if (this.options.admission.maxInFlightBatches !== 1) {
1848
+ throw new Error("Ordered telemetry clients currently require maxInFlightBatches to equal 1");
1849
+ }
1850
+ }
1851
+ validateCapacityOptions() {
1852
+ const capacities = [
1853
+ this.options.admission.maxQueuedEvents,
1854
+ this.options.admission.maxQueuedBytes,
1855
+ this.options.admission.maxBatchEvents,
1856
+ this.options.admission.maxBatchBytes,
1857
+ this.options.admission.maxInFlightBatches,
1858
+ this.options.transport.capabilities.maxBatchBytes,
1859
+ this.options.transport.capabilities.boundedCapacity
1860
+ ];
1861
+ if (capacities.some((capacity) => !Number.isSafeInteger(capacity) || capacity <= 0)) {
1862
+ throw new Error("Telemetry client and transport capacities must be positive safe integers");
1863
+ }
1864
+ }
1865
+ validateBatchEventOptions() {
1866
+ if (this.options.admission.maxBatchEvents > this.options.admission.maxQueuedEvents || this.options.admission.maxBatchEvents > this.batchContract.limits.maxBatchEvents) {
1867
+ throw new Error("Telemetry maxBatchEvents must not exceed queue or receiver capacity");
1868
+ }
1869
+ }
1870
+ validateBatchByteOptions() {
1871
+ if (this.options.admission.maxBatchBytes > this.options.admission.maxQueuedBytes || this.options.admission.maxBatchBytes > this.options.transport.capabilities.maxBatchBytes || this.options.admission.maxBatchBytes > this.batchContract.limits.maxBatchBytes) {
1872
+ throw new Error("Telemetry maxBatchBytes must not exceed queue, transport, or receiver capacity");
1873
+ }
1874
+ }
1875
+ validateMetadataOptions() {
1876
+ if (this.options.metadata.maxBytes > this.options.admission.maxBatchBytes) {
1877
+ throw new Error("Telemetry metadata bytes must not exceed maxBatchBytes");
1878
+ }
1879
+ }
1880
+ validateRetryOptions() {
1881
+ if (!Number.isSafeInteger(this.options.admission.retryLimit) || this.options.admission.retryLimit < 0 || !Number.isFinite(this.options.admission.retryBaseDelayMs) || this.options.admission.retryBaseDelayMs < 0) {
1882
+ throw new Error("Telemetry retry limit and delay must be nonnegative finite values");
1883
+ }
1884
+ }
1885
+ validateSampleRateOptions() {
1886
+ const rates = Object.values(this.options.admission.sampleRates);
1887
+ if (rates.some((rate) => !Number.isFinite(rate) || rate < 0 || rate > 1)) {
1888
+ throw new Error("Telemetry sample rates must be between zero and one");
1889
+ }
1890
+ }
1891
+ };
1892
+
1893
+ // src/core/telemetry_storage_router.ts
1894
+ var START_ROLLBACK_TIMEOUT_MS = 5e3;
1895
+ var TelemetryStorageRouter = class {
1896
+ constructor(options) {
1897
+ this.options = options;
1898
+ this.targetId = options.routerId;
1899
+ this.validateOptions();
1900
+ this.capabilities = Object.freeze({
1901
+ rawPersistence: options.targets.every((target) => target.transport.capabilities.rawPersistence),
1902
+ metricSamples: options.targets.every((target) => target.transport.capabilities.metricSamples),
1903
+ rollups: options.targets.some((target) => target.transport.capabilities.rollups),
1904
+ queries: options.targets.some((target) => target.transport.capabilities.queries),
1905
+ retention: options.targets.some((target) => target.transport.capabilities.retention),
1906
+ maintenance: options.targets.some((target) => target.transport.capabilities.maintenance)
1907
+ });
1908
+ }
1909
+ options;
1910
+ targetId;
1911
+ capabilities;
1912
+ acknowledgementContract = new TelemetryAcknowledgementContract();
1913
+ deadlineGuard = new TelemetryDeadlineGuard();
1914
+ committed = /* @__PURE__ */ new Map();
1915
+ definitionTargets = /* @__PURE__ */ new Map();
1916
+ state = "created";
1917
+ detail = null;
1918
+ async start(definitions) {
1919
+ if (this.state !== "created" && this.state !== "stopped") {
1920
+ return { ok: false, error: `Cannot start telemetry storage router from state ${this.state}` };
1921
+ }
1922
+ this.state = "starting";
1923
+ this.detail = null;
1924
+ this.definitionTargets.clear();
1925
+ for (const definition of definitions) {
1926
+ this.definitionTargets.set(
1927
+ `${definition.name}:${definition.version}`,
1928
+ Object.freeze([...definition.targetIds])
1929
+ );
1930
+ }
1931
+ const attemptedTargets = [];
1932
+ const bestEffortFailures = [];
1933
+ for (const target of this.options.targets) {
1934
+ attemptedTargets.push(target);
1935
+ const targetDefinitions = definitions.filter((definition) => {
1936
+ return definition.targetIds.length === 0 || definition.targetIds.includes(target.targetId);
1937
+ });
1938
+ const started = await this.startTarget(target, targetDefinitions);
1939
+ if (started.ok) {
1940
+ continue;
1941
+ }
1942
+ const failure = `${target.targetId}: ${started.error}`;
1943
+ if (target.delivery === "required") {
1944
+ await this.rollbackAttemptedTargets(attemptedTargets, failure);
1945
+ return { ok: false, error: failure };
1946
+ }
1947
+ bestEffortFailures.push(failure);
1948
+ }
1949
+ this.state = "ready";
1950
+ this.detail = bestEffortFailures.length > 0 ? `Best-effort target startup failures: ${bestEffortFailures.join("; ")}` : null;
1951
+ return { ok: true, value: void 0 };
1952
+ }
1953
+ async persist(request) {
1954
+ const matchingTargets = this.options.targets.filter((target) => target.matches(request.batch));
1955
+ const hasRequiredTarget = matchingTargets.some((target) => target.delivery === "required");
1956
+ if (!hasRequiredTarget && !this.options.volatile) {
1957
+ return {
1958
+ kind: "rejected",
1959
+ idempotencyKey: request.batch.idempotencyKey,
1960
+ code: "no-required-terminal",
1961
+ reason: "No required storage target matched this batch",
1962
+ targetStates: this.unmatchedStates(matchingTargets)
1963
+ };
1964
+ }
1965
+ const committedTargets = this.committed.get(request.batch.idempotencyKey) ?? /* @__PURE__ */ new Set();
1966
+ const targetResults = await this.persistMatchingTargets(request, matchingTargets, committedTargets);
1967
+ this.remember(request.batch.idempotencyKey, committedTargets);
1968
+ return this.aggregateAcknowledgement(request, matchingTargets, targetResults);
1969
+ }
1970
+ async timeline(query) {
1971
+ const target = this.resolveQueryTarget(query.targetId);
1972
+ if (!target.ok) {
1973
+ return target;
1974
+ }
1975
+ return target.value.transport.timeline(Object.freeze({ ...query, targetId: target.value.targetId }));
1976
+ }
1977
+ async metrics(query) {
1978
+ const target = this.resolveQueryTarget(query.targetId);
1979
+ if (!target.ok) {
1980
+ return target;
1981
+ }
1982
+ return target.value.transport.metrics(Object.freeze({ ...query, targetId: target.value.targetId }));
1983
+ }
1984
+ async materializeRollups(nowMs, maxWorkMs) {
1985
+ return this.runMaintenance("rollup", (target) => target.materializeRollups(nowMs, maxWorkMs));
1986
+ }
1987
+ async enforceRetention(nowMs, shouldContinue) {
1988
+ return this.runMaintenance("retention", (target) => target.enforceRetention(nowMs, shouldContinue));
1989
+ }
1990
+ async checkpoint() {
1991
+ return this.runMaintenance("checkpoint", (target) => target.checkpoint());
1992
+ }
1993
+ async reclaim(maxPages) {
1994
+ return this.runMaintenance("reclaim", (target) => target.reclaim(maxPages));
1995
+ }
1996
+ async compact(deadlineMs) {
1997
+ return this.runMaintenance("compaction", (target) => target.compact(deadlineMs));
1998
+ }
1999
+ status() {
2000
+ const states = this.options.targets.map((target) => target.transport.status());
2001
+ const requiredIds = new Set(this.options.targets.filter((target) => target.delivery === "required").map((target) => target.targetId));
2002
+ const failedRequired = states.find((state) => {
2003
+ return requiredIds.has(state.targetId) && state.state === "failed";
2004
+ });
2005
+ return {
2006
+ targetId: this.targetId,
2007
+ state: failedRequired ? "failed" : this.state,
2008
+ detail: this.state === "failed" ? this.detail : failedRequired?.detail ?? this.detail,
2009
+ writerLeaseHeld: states.some((state) => state.writerLeaseHeld)
2010
+ };
2011
+ }
2012
+ async stop(deadlineMs) {
2013
+ const operations = this.options.targets.map((target) => {
2014
+ return Promise.resolve().then(() => target.transport.stop(deadlineMs));
2015
+ });
2016
+ const outcome = await this.deadlineGuard.settle(
2017
+ Promise.allSettled(operations),
2018
+ deadlineMs,
2019
+ this.options.now
2020
+ );
2021
+ if (outcome.kind === "deadline-exhausted") {
2022
+ this.state = "failed";
2023
+ this.detail = "Telemetry storage router stop deadline exceeded";
2024
+ return;
2025
+ }
2026
+ if (outcome.kind === "failed") {
2027
+ this.state = "failed";
2028
+ this.detail = `Telemetry storage router stop failed: ${this.errorMessage(outcome.error)}`;
2029
+ throw outcome.error;
2030
+ }
2031
+ const failed = outcome.value.find((result) => result.status === "rejected");
2032
+ if (failed) {
2033
+ this.state = "failed";
2034
+ this.detail = `Telemetry storage router stop failed: ${this.errorMessage(failed.reason)}`;
2035
+ throw failed.reason;
2036
+ }
2037
+ this.state = "stopped";
2038
+ this.detail = null;
2039
+ }
2040
+ targetStatuses() {
2041
+ return this.options.targets.map((target) => target.transport.status());
2042
+ }
2043
+ async startTarget(target, definitions) {
2044
+ try {
2045
+ return await target.transport.start(definitions);
2046
+ } catch (cause) {
2047
+ return { ok: false, error: this.errorMessage(cause) };
2048
+ }
2049
+ }
2050
+ async rollbackAttemptedTargets(attemptedTargets, primaryFailure) {
2051
+ const deadlineMs = this.options.now() + START_ROLLBACK_TIMEOUT_MS;
2052
+ const rollbackTargets = [...attemptedTargets].reverse();
2053
+ const settled = /* @__PURE__ */ new Map();
2054
+ const operations = rollbackTargets.map(async (target) => {
2055
+ const result = await this.stopRollbackTarget(target, deadlineMs);
2056
+ settled.set(target.targetId, result);
2057
+ });
2058
+ const outcome = await this.deadlineGuard.settle(
2059
+ Promise.all(operations),
2060
+ deadlineMs,
2061
+ this.options.now
2062
+ );
2063
+ const timedOut = outcome.kind === "deadline-exhausted";
2064
+ const cleanupFailures = rollbackTargets.flatMap((target) => {
2065
+ const result = settled.get(target.targetId);
2066
+ if (result === void 0) {
2067
+ return timedOut ? [`${target.targetId}: rollback deadline exceeded`] : [];
2068
+ }
2069
+ return result.error === null ? [] : [`${target.targetId}: ${result.error}`];
2070
+ });
2071
+ this.state = "failed";
2072
+ this.detail = cleanupFailures.length > 0 ? `${primaryFailure}; rollback cleanup failures: ${cleanupFailures.join("; ")}` : primaryFailure;
2073
+ }
2074
+ async stopRollbackTarget(target, deadlineMs) {
2075
+ try {
2076
+ await target.transport.stop(deadlineMs);
2077
+ return { targetId: target.targetId, error: null };
2078
+ } catch (cause) {
2079
+ return { targetId: target.targetId, error: this.errorMessage(cause) };
2080
+ }
2081
+ }
2082
+ async persistMatchingTargets(request, targets, committedTargets) {
2083
+ return Promise.all(targets.map(async (target) => {
2084
+ if (committedTargets.has(target.targetId)) {
2085
+ return { target, acknowledgement: this.alreadyCommitted(request), attempts: 0 };
2086
+ }
2087
+ const targetRequest = this.requestForTarget(request, target.targetId);
2088
+ const acknowledgement = await this.persistSafely(target, targetRequest);
2089
+ if (acknowledgement.kind === "committed") {
2090
+ committedTargets.add(target.targetId);
2091
+ }
2092
+ return { target, acknowledgement, attempts: 1 };
2093
+ }));
2094
+ }
2095
+ aggregateAcknowledgement(request, matchingTargets, targetResults) {
2096
+ const targetStates = this.deliveryStates(targetResults, matchingTargets);
2097
+ const requiredResults = targetResults.filter(({ target }) => target.delivery === "required");
2098
+ const conflict = requiredResults.find(({ acknowledgement }) => acknowledgement.kind === "conflict");
2099
+ const conflictAcknowledgement = conflict?.acknowledgement;
2100
+ if (conflictAcknowledgement?.kind === "conflict") {
2101
+ return {
2102
+ kind: "conflict",
2103
+ idempotencyKey: request.batch.idempotencyKey,
2104
+ reason: conflictAcknowledgement.reason,
2105
+ targetStates
2106
+ };
2107
+ }
2108
+ const rejected = requiredResults.find(({ acknowledgement }) => acknowledgement.kind === "rejected");
2109
+ const rejectedAcknowledgement = rejected?.acknowledgement;
2110
+ if (rejectedAcknowledgement?.kind === "rejected") {
2111
+ return {
2112
+ kind: "rejected",
2113
+ idempotencyKey: request.batch.idempotencyKey,
2114
+ code: rejectedAcknowledgement.code,
2115
+ reason: rejectedAcknowledgement.reason,
2116
+ targetStates
2117
+ };
2118
+ }
2119
+ const retryable = requiredResults.find(({ acknowledgement }) => acknowledgement.kind === "retryable");
2120
+ const retryableAcknowledgement = retryable?.acknowledgement;
2121
+ if (retryableAcknowledgement?.kind === "retryable") {
2122
+ return {
2123
+ kind: "retryable",
2124
+ idempotencyKey: request.batch.idempotencyKey,
2125
+ reason: retryableAcknowledgement.reason,
2126
+ retryAfterMs: retryableAcknowledgement.retryAfterMs,
2127
+ targetStates
2128
+ };
2129
+ }
2130
+ return {
2131
+ kind: "committed",
2132
+ idempotencyKey: request.batch.idempotencyKey,
2133
+ committedAtMs: this.options.now(),
2134
+ acceptedEvents: request.batch.envelopes.length,
2135
+ duplicate: targetResults.length > 0 && targetResults.every(({ attempts }) => attempts === 0),
2136
+ targetStates
2137
+ };
2138
+ }
2139
+ deliveryStates(targetResults, matchingTargets) {
2140
+ return [
2141
+ ...targetResults.map(({ target, acknowledgement, attempts }) => this.deliveryState(
2142
+ target,
2143
+ acknowledgement,
2144
+ attempts
2145
+ )),
2146
+ ...this.unmatchedStates(matchingTargets)
2147
+ ];
2148
+ }
2149
+ requestForTarget(request, targetId) {
2150
+ const samples = request.samples.filter((sample) => {
2151
+ return sample.targetIds.length === 0 || sample.targetIds.includes(targetId);
2152
+ });
2153
+ const extractionFailures = request.extractionFailures.filter((failure) => {
2154
+ const targetIds = this.definitionTargets.get(`${failure.metricName}:${failure.definitionVersion}`);
2155
+ return targetIds === void 0 || targetIds.length === 0 || targetIds.includes(targetId);
2156
+ });
2157
+ return Object.freeze({
2158
+ batch: request.batch,
2159
+ samples: Object.freeze(samples),
2160
+ extractionFailures: Object.freeze(extractionFailures)
2161
+ });
2162
+ }
2163
+ async persistSafely(target, request) {
2164
+ try {
2165
+ const received = await target.transport.persist(request);
2166
+ const validated = this.acknowledgementContract.validateAndDetach(request.batch, received);
2167
+ return validated.ok ? validated.value : this.invalidAcknowledgement(request, validated.error);
2168
+ } catch (cause) {
2169
+ return {
2170
+ kind: "retryable",
2171
+ idempotencyKey: request.batch.idempotencyKey,
2172
+ reason: this.acknowledgementContract.exceptionReason(cause),
2173
+ retryAfterMs: 50,
2174
+ targetStates: []
2175
+ };
2176
+ }
2177
+ }
2178
+ invalidAcknowledgement(request, error) {
2179
+ return {
2180
+ kind: "retryable",
2181
+ idempotencyKey: request.batch.idempotencyKey,
2182
+ reason: `Invalid telemetry acknowledgement: ${error}`,
2183
+ retryAfterMs: 50,
2184
+ targetStates: []
2185
+ };
2186
+ }
2187
+ alreadyCommitted(request) {
2188
+ return {
2189
+ kind: "committed",
2190
+ idempotencyKey: request.batch.idempotencyKey,
2191
+ committedAtMs: this.options.now(),
2192
+ acceptedEvents: request.batch.envelopes.length,
2193
+ duplicate: true,
2194
+ targetStates: []
2195
+ };
2196
+ }
2197
+ deliveryState(target, acknowledgement, attempts) {
2198
+ return acknowledgement.kind === "committed" ? {
2199
+ targetId: target.targetId,
2200
+ delivery: target.delivery,
2201
+ state: "committed",
2202
+ attempts,
2203
+ reason: null
2204
+ } : {
2205
+ targetId: target.targetId,
2206
+ delivery: target.delivery,
2207
+ state: "failed",
2208
+ attempts,
2209
+ reason: acknowledgement.reason
2210
+ };
2211
+ }
2212
+ unmatchedStates(matchingTargets) {
2213
+ const matchingIds = new Set(matchingTargets.map((target) => target.targetId));
2214
+ return this.options.targets.filter((target) => !matchingIds.has(target.targetId)).map((target) => ({
2215
+ targetId: target.targetId,
2216
+ delivery: target.delivery,
2217
+ state: "abandoned",
2218
+ attempts: 0,
2219
+ reason: "route-not-matched"
2220
+ }));
2221
+ }
2222
+ resolveQueryTarget(requestedTargetId) {
2223
+ const queryable = this.options.targets.filter((target2) => target2.transport.capabilities.queries);
2224
+ if (queryable.length === 0) {
2225
+ return {
2226
+ ok: false,
2227
+ error: { kind: "unsupported", message: "No configured telemetry target supports queries" }
2228
+ };
2229
+ }
2230
+ const resolvedId = requestedTargetId ?? this.options.defaultTargetId ?? (queryable.length === 1 ? queryable[0]?.targetId ?? null : null);
2231
+ if (resolvedId === null) {
2232
+ return {
2233
+ ok: false,
2234
+ error: { kind: "ambiguous-target", message: "Multiple queryable targets exist and no default was configured" }
2235
+ };
2236
+ }
2237
+ const target = queryable.find((candidate) => candidate.targetId === resolvedId);
2238
+ if (!target) {
2239
+ return { ok: false, error: { kind: "unknown-target", message: `Unknown query target ${resolvedId}` } };
2240
+ }
2241
+ return { ok: true, value: target };
2242
+ }
2243
+ async runMaintenance(stage, run) {
2244
+ let affectedRows = 0;
2245
+ let affectedPages = 0;
2246
+ let elapsedMs = 0;
2247
+ for (const target of this.options.targets) {
2248
+ if (!target.transport.capabilities.maintenance) {
2249
+ continue;
2250
+ }
2251
+ const completed = await run(target.transport);
2252
+ if (!completed.ok && target.delivery === "required") {
2253
+ return { ok: false, error: `${target.targetId}: ${completed.error}` };
2254
+ }
2255
+ if (completed.ok) {
2256
+ affectedRows += completed.value.affectedRows;
2257
+ affectedPages += completed.value.affectedPages;
2258
+ elapsedMs += completed.value.elapsedMs;
2259
+ }
2260
+ }
2261
+ return {
2262
+ ok: true,
2263
+ value: { stage, affectedRows, affectedPages, elapsedMs, detail: null }
2264
+ };
2265
+ }
2266
+ validateOptions() {
2267
+ if (!this.options.routerId.trim() || this.options.targets.length === 0) {
2268
+ throw new Error("A telemetry storage router requires an id and at least one target");
2269
+ }
2270
+ if (this.options.maxRememberedBatches !== void 0 && (!Number.isSafeInteger(this.options.maxRememberedBatches) || this.options.maxRememberedBatches <= 0)) {
2271
+ throw new Error("Telemetry storage router memory must be a positive safe integer");
2272
+ }
2273
+ const targetIds = this.options.targets.map((target) => target.targetId);
2274
+ if (new Set(targetIds).size !== targetIds.length) {
2275
+ throw new Error("Telemetry storage target ids must be unique");
2276
+ }
2277
+ const requiredTargets = this.options.targets.filter((target) => target.delivery === "required");
2278
+ if (!this.options.volatile && requiredTargets.length === 0) {
2279
+ throw new Error("A non-volatile storage router requires a required target");
2280
+ }
2281
+ const defaultExists = this.options.defaultTargetId === null || targetIds.includes(this.options.defaultTargetId);
2282
+ if (!defaultExists) {
2283
+ throw new Error(`Unknown default telemetry target ${this.options.defaultTargetId}`);
2284
+ }
2285
+ const defaultTarget = this.options.targets.find((target) => target.targetId === this.options.defaultTargetId);
2286
+ if (defaultTarget && !defaultTarget.transport.capabilities.queries) {
2287
+ throw new Error(`Default telemetry target ${defaultTarget.targetId} does not support queries`);
2288
+ }
2289
+ }
2290
+ remember(idempotencyKey, targets) {
2291
+ this.committed.set(idempotencyKey, targets);
2292
+ const maximum = this.options.maxRememberedBatches ?? 1024;
2293
+ while (this.committed.size > maximum) {
2294
+ const oldest = this.committed.keys().next().value;
2295
+ if (typeof oldest !== "string") {
2296
+ return;
2297
+ }
2298
+ this.committed.delete(oldest);
2299
+ }
2300
+ }
2301
+ errorMessage(cause) {
2302
+ return cause instanceof Error ? cause.message : String(cause);
2303
+ }
2304
+ };
2305
+
2306
+ // src/runtime/in_memory_batch_transport.ts
2307
+ var InMemoryBatchTransport = class {
2308
+ constructor(transportId, receiver, maxPendingBatches = 16) {
2309
+ this.transportId = transportId;
2310
+ this.receiver = receiver;
2311
+ this.capabilities = Object.freeze({
2312
+ maxBatchBytes: 1e6,
2313
+ acknowledgement: "durable",
2314
+ ordering: "producer",
2315
+ boundedCapacity: maxPendingBatches
2316
+ });
2317
+ }
2318
+ transportId;
2319
+ capabilities;
2320
+ receiver;
2321
+ pending = 0;
2322
+ closed = false;
2323
+ connect(receiver) {
2324
+ this.receiver = receiver;
2325
+ }
2326
+ async send(batch) {
2327
+ if (this.closed) {
2328
+ return {
2329
+ kind: "rejected",
2330
+ idempotencyKey: batch.idempotencyKey,
2331
+ code: "closed",
2332
+ reason: "In-memory telemetry transport is closed",
2333
+ targetStates: []
2334
+ };
2335
+ }
2336
+ if (this.receiver === null || this.pending >= this.capabilities.boundedCapacity) {
2337
+ return {
2338
+ kind: "retryable",
2339
+ idempotencyKey: batch.idempotencyKey,
2340
+ reason: this.receiver === null ? "No receiver is connected" : "Transport capacity is full",
2341
+ retryAfterMs: 1,
2342
+ targetStates: []
2343
+ };
2344
+ }
2345
+ this.pending += 1;
2346
+ try {
2347
+ return await this.receiver.receive(batch);
2348
+ } finally {
2349
+ this.pending -= 1;
2350
+ }
2351
+ }
2352
+ async flush(deadlineMs) {
2353
+ while (this.pending > 0 && Date.now() < deadlineMs) {
2354
+ const remainingMs = Math.max(0, deadlineMs - Date.now());
2355
+ await new Promise((resolve) => setTimeout(resolve, Math.min(1, remainingMs)));
2356
+ }
2357
+ }
2358
+ async close(deadlineMs) {
2359
+ this.closed = true;
2360
+ await this.flush(deadlineMs);
2361
+ }
2362
+ };
2363
+
2364
+ // src/plugins/telemetry_plugin_registry.ts
2365
+ var TelemetryPluginRegistry = class {
2366
+ constructor(options) {
2367
+ this.options = options;
2368
+ }
2369
+ options;
2370
+ sinks = /* @__PURE__ */ new Map();
2371
+ metrics = /* @__PURE__ */ new Map();
2372
+ sinkChains = /* @__PURE__ */ new Map();
2373
+ pendingBySink = /* @__PURE__ */ new Map();
2374
+ sinkFailures = /* @__PURE__ */ new Map();
2375
+ sinkDrops = /* @__PURE__ */ new Map();
2376
+ registerSink(plugin) {
2377
+ this.assertUnique(plugin.name);
2378
+ const supportsRuntime = plugin.supportedSchemaVersions.some((version) => {
2379
+ return this.options.supportedSchemaVersions.includes(version);
2380
+ });
2381
+ if (!supportsRuntime) {
2382
+ throw new Error(`Sink plugin ${plugin.name} supports no collector schema version`);
2383
+ }
2384
+ this.sinks.set(plugin.name, plugin);
2385
+ }
2386
+ registerMetric(plugin) {
2387
+ this.assertUnique(plugin.name);
2388
+ this.metrics.set(plugin.name, plugin);
2389
+ }
2390
+ extract(batch) {
2391
+ const samples = [];
2392
+ const failures = [];
2393
+ for (const plugin of this.metrics.values()) {
2394
+ try {
2395
+ const extracted = plugin.extract(batch);
2396
+ samples.push(...extracted.samples);
2397
+ failures.push(...extracted.failures);
2398
+ } catch (cause) {
2399
+ for (const envelope of batch.envelopes) {
2400
+ failures.push({
2401
+ metricName: plugin.definition.name,
2402
+ definitionVersion: plugin.definition.version,
2403
+ producerId: envelope.producerId,
2404
+ sequence: envelope.sequence,
2405
+ reason: cause instanceof Error ? cause.message : String(cause)
2406
+ });
2407
+ }
2408
+ }
2409
+ }
2410
+ return { samples, failures };
2411
+ }
2412
+ dispatchCommitted(batch) {
2413
+ for (const sink of this.sinks.values()) {
2414
+ const pending = this.pendingBySink.get(sink.name) ?? 0;
2415
+ if (pending >= this.options.maxPendingPerSink) {
2416
+ this.sinkDrops.set(sink.name, (this.sinkDrops.get(sink.name) ?? 0) + 1);
2417
+ continue;
2418
+ }
2419
+ this.pendingBySink.set(sink.name, pending + 1);
2420
+ const prior = this.sinkChains.get(sink.name) ?? Promise.resolve();
2421
+ const next = prior.then(() => sink.onCommitted(batch)).catch(() => {
2422
+ this.sinkFailures.set(sink.name, (this.sinkFailures.get(sink.name) ?? 0) + 1);
2423
+ }).finally(() => {
2424
+ const remaining = Math.max(0, (this.pendingBySink.get(sink.name) ?? 1) - 1);
2425
+ this.pendingBySink.set(sink.name, remaining);
2426
+ });
2427
+ this.sinkChains.set(sink.name, next);
2428
+ }
2429
+ }
2430
+ status() {
2431
+ return Object.freeze({
2432
+ sinkFailures: Object.freeze(Object.fromEntries(this.sinkFailures)),
2433
+ sinkDrops: Object.freeze(Object.fromEntries(this.sinkDrops)),
2434
+ pendingBySink: Object.freeze(Object.fromEntries(this.pendingBySink))
2435
+ });
2436
+ }
2437
+ async close(deadlineMs, now) {
2438
+ const chains = [...this.sinkChains.values()];
2439
+ while (chains.length > 0 && now() < deadlineMs) {
2440
+ await Promise.race([
2441
+ Promise.allSettled(chains).then(() => void 0),
2442
+ new Promise((resolve) => setTimeout(resolve, 1))
2443
+ ]);
2444
+ const allSettled = [...this.pendingBySink.values()].every((pending) => pending === 0);
2445
+ if (allSettled) {
2446
+ break;
2447
+ }
2448
+ }
2449
+ await Promise.allSettled([...this.sinks.values()].map((sink) => sink.close(deadlineMs)));
2450
+ }
2451
+ assertUnique(name) {
2452
+ const duplicate = this.sinks.has(name) || this.metrics.has(name);
2453
+ if (!name.trim() || duplicate) {
2454
+ throw new Error(`Telemetry plugin name ${name || "<empty>"} is invalid or duplicated`);
2455
+ }
2456
+ }
2457
+ };
2458
+
2459
+ // src/runtime/telemetry_runtime.ts
2460
+ var TelemetryCollectorRuntime = class {
2461
+ constructor(options) {
2462
+ this.options = options;
2463
+ this.batchContract = new TelemetryBatchContract(options.batchValidation);
2464
+ this.storage = this.resolveStorage(options.storage);
2465
+ this.pluginRegistry = new TelemetryPluginRegistry({
2466
+ supportedSchemaVersions: options.schemaVersions,
2467
+ maxPendingPerSink: 64
2468
+ });
2469
+ for (const sink of options.sinkPlugins) {
2470
+ this.pluginRegistry.registerSink(sink);
2471
+ }
2472
+ }
2473
+ options;
2474
+ receiverId = "telemetry-collector";
2475
+ batchContract;
2476
+ definitionContract = new MetricDefinitionContract();
2477
+ storage;
2478
+ pluginRegistry;
2479
+ dropCounts = /* @__PURE__ */ new Map();
2480
+ extractor = null;
2481
+ state = "created";
2482
+ acceptedBatches = 0;
2483
+ committedEvents = 0;
2484
+ rejectedBatches = 0;
2485
+ lastFailure = null;
2486
+ pending = /* @__PURE__ */ new Set();
2487
+ async start() {
2488
+ if (this.state !== "created" && this.state !== "stopped") {
2489
+ throw new Error(`Cannot start telemetry collector from state ${this.state}`);
2490
+ }
2491
+ this.state = "starting";
2492
+ const definitions = this.normalizeDefinitions(this.options.definitions);
2493
+ const capabilityError = this.storageCapabilityError(definitions);
2494
+ if (capabilityError !== null) {
2495
+ this.state = "failed";
2496
+ this.lastFailure = capabilityError;
2497
+ throw new Error(`Telemetry collector storage is incompatible: ${capabilityError}`);
2498
+ }
2499
+ const started = await this.storage.start(definitions);
2500
+ if (!started.ok) {
2501
+ this.state = "failed";
2502
+ this.lastFailure = started.error;
2503
+ throw new Error(`Telemetry collector storage failed to start: ${started.error}`);
2504
+ }
2505
+ this.extractor = new MetricExtractor(definitions);
2506
+ this.state = "ready";
2507
+ }
2508
+ receive(batch) {
2509
+ const operation = this.acceptBatch(batch);
2510
+ this.pending.add(operation);
2511
+ void operation.then(
2512
+ () => this.pending.delete(operation),
2513
+ () => this.pending.delete(operation)
2514
+ );
2515
+ return operation;
2516
+ }
2517
+ status() {
2518
+ const targets = this.storage instanceof TelemetryStorageRouter ? this.storage.targetStatuses() : [this.storage.status()];
2519
+ return Object.freeze({
2520
+ state: this.state,
2521
+ acceptedBatches: this.acceptedBatches,
2522
+ committedEvents: this.committedEvents,
2523
+ rejectedBatches: this.rejectedBatches,
2524
+ dropCounters: this.dropCounterSnapshot(),
2525
+ lastFailure: this.lastFailure,
2526
+ targets: Object.freeze(targets)
2527
+ });
2528
+ }
2529
+ async stop(deadlineMs) {
2530
+ if (this.state === "stopped") {
2531
+ return;
2532
+ }
2533
+ this.state = "draining";
2534
+ while (this.pending.size > 0 && this.options.now() < deadlineMs) {
2535
+ await Promise.race([...this.pending, new Promise((resolve) => setTimeout(resolve, 1))]);
2536
+ }
2537
+ await this.settleExternalShutdown(
2538
+ () => this.pluginRegistry.close(deadlineMs, this.options.now),
2539
+ deadlineMs
2540
+ );
2541
+ await this.settleExternalShutdown(() => this.storage.stop(deadlineMs), deadlineMs);
2542
+ this.state = "stopped";
2543
+ }
2544
+ storageTransport() {
2545
+ return this.storage;
2546
+ }
2547
+ async acceptBatch(batch) {
2548
+ const idempotencyKey = this.safeIdempotencyKey(batch);
2549
+ if (this.state !== "ready" || this.extractor === null) {
2550
+ this.rejectedBatches += 1;
2551
+ return {
2552
+ kind: "rejected",
2553
+ idempotencyKey,
2554
+ code: "closed",
2555
+ reason: `Telemetry collector is ${this.state}`,
2556
+ targetStates: []
2557
+ };
2558
+ }
2559
+ const validated = this.batchContract.validate(batch);
2560
+ if (!validated.ok) {
2561
+ this.rejectedBatches += 1;
2562
+ return {
2563
+ kind: "rejected",
2564
+ idempotencyKey,
2565
+ code: this.validationCode(validated.error),
2566
+ reason: validated.error,
2567
+ targetStates: []
2568
+ };
2569
+ }
2570
+ const acceptedBatch = validated.value;
2571
+ if (!this.options.schemaVersions.includes(acceptedBatch.schemaVersion)) {
2572
+ this.rejectedBatches += 1;
2573
+ return {
2574
+ kind: "rejected",
2575
+ idempotencyKey,
2576
+ code: "unsupported-version",
2577
+ reason: `Unsupported telemetry batch schema ${acceptedBatch.schemaVersion}`,
2578
+ targetStates: []
2579
+ };
2580
+ }
2581
+ this.acceptedBatches += 1;
2582
+ try {
2583
+ return await this.persistAcceptedBatch(acceptedBatch);
2584
+ } catch (cause) {
2585
+ const reason = this.errorMessage(cause);
2586
+ this.lastFailure = reason;
2587
+ return {
2588
+ kind: "retryable",
2589
+ idempotencyKey: acceptedBatch.idempotencyKey,
2590
+ reason: `Telemetry collector pipeline failed: ${reason}`.slice(0, 512),
2591
+ retryAfterMs: 1,
2592
+ targetStates: []
2593
+ };
2594
+ }
2595
+ }
2596
+ async persistAcceptedBatch(batch) {
2597
+ const extractor = this.extractor;
2598
+ if (extractor === null) {
2599
+ throw new Error("Telemetry metric extractor is unavailable");
2600
+ }
2601
+ const declarative = extractor.extract(batch.envelopes);
2602
+ const plugins = this.pluginRegistry.extract(batch);
2603
+ const extraction = this.mergeExtractions(declarative, plugins);
2604
+ const received = await this.storage.persist({
2605
+ batch,
2606
+ samples: extraction.samples,
2607
+ extractionFailures: extraction.failures
2608
+ });
2609
+ const acknowledgement = this.normalizeStorageAcknowledgement(batch, received);
2610
+ if (acknowledgement.kind !== "committed") {
2611
+ this.lastFailure = acknowledgement.reason;
2612
+ return acknowledgement;
2613
+ }
2614
+ if (!acknowledgement.duplicate) {
2615
+ this.committedEvents += acknowledgement.acceptedEvents;
2616
+ this.aggregateDropCounters(batch.dropCounters);
2617
+ this.pluginRegistry.dispatchCommitted(batch);
2618
+ }
2619
+ return acknowledgement;
2620
+ }
2621
+ normalizeStorageAcknowledgement(batch, value) {
2622
+ try {
2623
+ if (!this.isRecord(value) || value.idempotencyKey !== batch.idempotencyKey) {
2624
+ return this.invalidStorageAcknowledgement(batch, "identity is missing or mismatched");
2625
+ }
2626
+ let normalized;
2627
+ switch (value.kind) {
2628
+ case "committed":
2629
+ normalized = this.normalizeCommittedAcknowledgement(batch, value);
2630
+ break;
2631
+ case "retryable":
2632
+ normalized = this.normalizeRetryableAcknowledgement(batch, value);
2633
+ break;
2634
+ case "rejected":
2635
+ normalized = this.normalizeRejectedAcknowledgement(batch, value);
2636
+ break;
2637
+ case "conflict":
2638
+ normalized = this.normalizeConflictAcknowledgement(batch, value);
2639
+ break;
2640
+ default:
2641
+ normalized = null;
2642
+ }
2643
+ return normalized ?? this.invalidStorageAcknowledgement(batch, "fields are invalid");
2644
+ } catch {
2645
+ return this.invalidStorageAcknowledgement(batch, "value could not be inspected");
2646
+ }
2647
+ }
2648
+ normalizeCommittedAcknowledgement(batch, value) {
2649
+ if (!this.hasExactKeys(value, [
2650
+ "kind",
2651
+ "idempotencyKey",
2652
+ "committedAtMs",
2653
+ "acceptedEvents",
2654
+ "duplicate",
2655
+ "targetStates"
2656
+ ])) {
2657
+ return null;
2658
+ }
2659
+ const committedAtMs = value.committedAtMs;
2660
+ const acceptedEvents = value.acceptedEvents;
2661
+ const duplicate = value.duplicate;
2662
+ const targetStates = this.normalizeTargetStates(value.targetStates);
2663
+ const valid = typeof committedAtMs === "number" && Number.isSafeInteger(committedAtMs) && committedAtMs >= 0 && typeof acceptedEvents === "number" && acceptedEvents === batch.envelopes.length && typeof duplicate === "boolean" && targetStates !== null;
2664
+ if (!valid || targetStates === null) {
2665
+ return null;
2666
+ }
2667
+ return Object.freeze({
2668
+ kind: "committed",
2669
+ idempotencyKey: batch.idempotencyKey,
2670
+ committedAtMs,
2671
+ acceptedEvents,
2672
+ duplicate,
2673
+ targetStates
2674
+ });
2675
+ }
2676
+ normalizeRetryableAcknowledgement(batch, value) {
2677
+ if (!this.hasExactKeys(value, [
2678
+ "kind",
2679
+ "idempotencyKey",
2680
+ "reason",
2681
+ "retryAfterMs",
2682
+ "targetStates"
2683
+ ])) {
2684
+ return null;
2685
+ }
2686
+ const reason = value.reason;
2687
+ const retryAfterMs = value.retryAfterMs;
2688
+ const targetStates = this.normalizeTargetStates(value.targetStates);
2689
+ const valid = this.isBoundedText(reason, 512) && typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 && retryAfterMs <= Number.MAX_SAFE_INTEGER && targetStates !== null;
2690
+ if (!valid || typeof reason !== "string" || typeof retryAfterMs !== "number" || targetStates === null) {
2691
+ return null;
2692
+ }
2693
+ return Object.freeze({
2694
+ kind: "retryable",
2695
+ idempotencyKey: batch.idempotencyKey,
2696
+ reason,
2697
+ retryAfterMs,
2698
+ targetStates
2699
+ });
2700
+ }
2701
+ normalizeRejectedAcknowledgement(batch, value) {
2702
+ if (!this.hasExactKeys(value, [
2703
+ "kind",
2704
+ "idempotencyKey",
2705
+ "code",
2706
+ "reason",
2707
+ "targetStates"
2708
+ ])) {
2709
+ return null;
2710
+ }
2711
+ const code = value.code;
2712
+ const reason = value.reason;
2713
+ const targetStates = this.normalizeTargetStates(value.targetStates);
2714
+ if (!this.isRejectionCode(code) || !this.isBoundedText(reason, 512) || typeof reason !== "string" || targetStates === null) {
2715
+ return null;
2716
+ }
2717
+ return Object.freeze({
2718
+ kind: "rejected",
2719
+ idempotencyKey: batch.idempotencyKey,
2720
+ code,
2721
+ reason,
2722
+ targetStates
2723
+ });
2724
+ }
2725
+ normalizeConflictAcknowledgement(batch, value) {
2726
+ if (!this.hasExactKeys(value, ["kind", "idempotencyKey", "reason", "targetStates"])) {
2727
+ return null;
2728
+ }
2729
+ const reason = value.reason;
2730
+ const targetStates = this.normalizeTargetStates(value.targetStates);
2731
+ if (!this.isBoundedText(reason, 512) || typeof reason !== "string" || targetStates === null) {
2732
+ return null;
2733
+ }
2734
+ return Object.freeze({
2735
+ kind: "conflict",
2736
+ idempotencyKey: batch.idempotencyKey,
2737
+ reason,
2738
+ targetStates
2739
+ });
2740
+ }
2741
+ normalizeTargetStates(value) {
2742
+ if (!Array.isArray(value)) {
2743
+ return null;
2744
+ }
2745
+ const targetCount = value.length;
2746
+ if (!Number.isSafeInteger(targetCount) || targetCount < 0 || targetCount > 1024) {
2747
+ return null;
2748
+ }
2749
+ const normalized = [];
2750
+ const targetIds = /* @__PURE__ */ new Set();
2751
+ for (let index = 0; index < targetCount; index += 1) {
2752
+ const target = this.normalizeTargetState(value[index]);
2753
+ if (target === null || targetIds.has(target.targetId)) {
2754
+ return null;
2755
+ }
2756
+ targetIds.add(target.targetId);
2757
+ normalized.push(target);
2758
+ }
2759
+ return Object.freeze(normalized);
2760
+ }
2761
+ normalizeTargetState(value) {
2762
+ if (!this.isRecord(value) || !this.hasExactKeys(value, ["targetId", "delivery", "state", "attempts", "reason"])) {
2763
+ return null;
2764
+ }
2765
+ const targetId = value.targetId;
2766
+ const delivery = value.delivery;
2767
+ const state = value.state;
2768
+ const attempts = value.attempts;
2769
+ const reason = value.reason;
2770
+ if (!this.isTargetId(targetId) || !this.isDeliveryMode(delivery) || !this.isDeliveryState(state) || !this.isAttemptCount(attempts) || !this.isTargetReason(reason)) {
2771
+ return null;
2772
+ }
2773
+ return Object.freeze({ targetId, delivery, state, attempts, reason });
2774
+ }
2775
+ invalidStorageAcknowledgement(batch, detail) {
2776
+ return Object.freeze({
2777
+ kind: "retryable",
2778
+ idempotencyKey: batch.idempotencyKey,
2779
+ reason: `Invalid telemetry storage acknowledgement: ${detail}`.slice(0, 512),
2780
+ retryAfterMs: 1,
2781
+ targetStates: Object.freeze([])
2782
+ });
2783
+ }
2784
+ isRecord(value) {
2785
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2786
+ }
2787
+ hasExactKeys(value, expected) {
2788
+ const keys = Object.keys(value);
2789
+ return Object.getOwnPropertySymbols(value).length === 0 && keys.length === expected.length && expected.every((key) => Object.prototype.hasOwnProperty.call(value, key));
2790
+ }
2791
+ isBoundedText(value, maxBytes) {
2792
+ return typeof value === "string" && value.length <= maxBytes && telemetryUtf8Length(value) <= maxBytes;
2793
+ }
2794
+ isRejectionCode(value) {
2795
+ return value === "invalid-batch" || value === "unsupported-version" || value === "no-required-terminal" || value === "closed";
2796
+ }
2797
+ isTargetId(value) {
2798
+ return this.isBoundedText(value, 256) && value !== "";
2799
+ }
2800
+ isDeliveryMode(value) {
2801
+ return value === "required" || value === "best-effort";
2802
+ }
2803
+ isDeliveryState(value) {
2804
+ return value === "pending" || value === "committed" || value === "failed" || value === "abandoned";
2805
+ }
2806
+ isAttemptCount(value) {
2807
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
2808
+ }
2809
+ isTargetReason(value) {
2810
+ return value === null || this.isBoundedText(value, 512);
2811
+ }
2812
+ aggregateDropCounters(counters) {
2813
+ for (const counter of counters) {
2814
+ const key = `${counter.reason}:${counter.priority}`;
2815
+ const current = this.dropCounts.get(key) ?? 0;
2816
+ this.dropCounts.set(key, Math.min(Number.MAX_SAFE_INTEGER, current + counter.count));
2817
+ }
2818
+ }
2819
+ dropCounterSnapshot() {
2820
+ return Object.freeze([...this.dropCounts.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([key, count]) => {
2821
+ const [reason, priority] = key.split(":");
2822
+ return Object.freeze({
2823
+ reason,
2824
+ priority,
2825
+ count
2826
+ });
2827
+ }));
2828
+ }
2829
+ safeIdempotencyKey(batch) {
2830
+ try {
2831
+ if (typeof batch !== "object" || batch === null) {
2832
+ return "invalid-batch";
2833
+ }
2834
+ const value = Reflect.get(batch, "idempotencyKey");
2835
+ return typeof value === "string" && value.length > 0 ? value.slice(0, 1024) : "invalid-batch";
2836
+ } catch {
2837
+ return "invalid-batch";
2838
+ }
2839
+ }
2840
+ validationCode(error) {
2841
+ return error.startsWith("Unsupported telemetry schema version") ? "unsupported-version" : "invalid-batch";
2842
+ }
2843
+ errorMessage(cause) {
2844
+ return cause instanceof Error ? cause.message.slice(0, 512) : String(cause).slice(0, 512);
2845
+ }
2846
+ async settleExternalShutdown(work, deadlineMs) {
2847
+ const operation = Promise.resolve().then(work);
2848
+ const remainingMs = deadlineMs - this.options.now();
2849
+ if (!Number.isFinite(remainingMs) || remainingMs <= 0) {
2850
+ void operation.catch(() => void 0);
2851
+ return;
2852
+ }
2853
+ await new Promise((resolve, reject) => {
2854
+ let settled = false;
2855
+ const finish = (failed, error) => {
2856
+ if (settled) {
2857
+ return;
2858
+ }
2859
+ settled = true;
2860
+ clearTimeout(timer);
2861
+ if (failed) {
2862
+ reject(error);
2863
+ } else {
2864
+ resolve();
2865
+ }
2866
+ };
2867
+ const timer = setTimeout(
2868
+ () => finish(false),
2869
+ Math.min(remainingMs, 2147483647)
2870
+ );
2871
+ void operation.then(
2872
+ () => finish(false),
2873
+ (error) => finish(true, error)
2874
+ );
2875
+ });
2876
+ }
2877
+ mergeExtractions(left, right) {
2878
+ return Object.freeze({
2879
+ samples: Object.freeze([...left.samples, ...right.samples]),
2880
+ failures: Object.freeze([...left.failures, ...right.failures])
2881
+ });
2882
+ }
2883
+ normalizeDefinitions(definitions) {
2884
+ const normalized = definitions.map((definition) => {
2885
+ const parsed = this.definitionContract.normalize(definition);
2886
+ if (!parsed.ok) {
2887
+ throw new Error(parsed.error);
2888
+ }
2889
+ return parsed.value;
2890
+ });
2891
+ const definitionIds = normalized.map((definition) => `${definition.name}:${definition.version}`);
2892
+ if (new Set(definitionIds).size !== definitionIds.length) {
2893
+ throw new Error("Telemetry metric name/version pairs must be unique");
2894
+ }
2895
+ return Object.freeze(normalized);
2896
+ }
2897
+ storageCapabilityError(definitions) {
2898
+ const required = new Set(this.options.requiredCapabilities ?? []);
2899
+ if (!this.options.volatile) {
2900
+ required.add("rawPersistence");
2901
+ }
2902
+ if (definitions.length > 0) {
2903
+ required.add("metricSamples");
2904
+ required.add("rollups");
2905
+ }
2906
+ const capabilityOrder = [
2907
+ "rawPersistence",
2908
+ "metricSamples",
2909
+ "rollups",
2910
+ "queries",
2911
+ "retention",
2912
+ "maintenance"
2913
+ ];
2914
+ const missing = capabilityOrder.filter((capability) => {
2915
+ return required.has(capability) && this.storage.capabilities[capability] !== true;
2916
+ });
2917
+ return missing.length === 0 ? null : `missing required capabilities: ${missing.join(", ")}`;
2918
+ }
2919
+ resolveStorage(storage) {
2920
+ if (this.isStorageTransport(storage)) {
2921
+ return storage;
2922
+ }
2923
+ return new TelemetryStorageRouter({
2924
+ routerId: "collector-storage-router",
2925
+ targets: storage,
2926
+ defaultTargetId: this.options.defaultTargetId ?? null,
2927
+ volatile: this.options.volatile,
2928
+ now: this.options.now
2929
+ });
2930
+ }
2931
+ isStorageTransport(storage) {
2932
+ return !Array.isArray(storage) && typeof storage === "object" && storage !== null && "persist" in storage;
2933
+ }
2934
+ };
2935
+
2936
+ // src/maintenance/maintenance_coordinator.ts
2937
+ var MaintenanceCoordinator = class {
2938
+ constructor(options) {
2939
+ this.options = options;
2940
+ if (options.intervalMs <= 0 || options.maxWorkMs <= 0 || options.maxReclaimPages < 0) {
2941
+ throw new Error("Telemetry maintenance interval and budgets are invalid");
2942
+ }
2943
+ }
2944
+ options;
2945
+ running = false;
2946
+ stopped = true;
2947
+ intervalHandle = null;
2948
+ activeCompletion = Promise.resolve();
2949
+ resolveActiveCompletion = () => void 0;
2950
+ start() {
2951
+ if (!this.stopped) {
2952
+ return;
2953
+ }
2954
+ this.stopped = false;
2955
+ this.intervalHandle = this.options.createInterval(() => {
2956
+ void this.run().catch(() => void 0);
2957
+ }, this.options.intervalMs);
2958
+ }
2959
+ async run() {
2960
+ if (this.stopped) {
2961
+ return { kind: "skipped", reason: "stopped" };
2962
+ }
2963
+ if (this.running) {
2964
+ return { kind: "skipped", reason: "already-running" };
2965
+ }
2966
+ this.running = true;
2967
+ this.beginActiveRun();
2968
+ let runId = "maintenance-run-unavailable";
2969
+ let startedAtMs = 0;
2970
+ const stages = [];
2971
+ try {
2972
+ runId = this.options.createRunId();
2973
+ startedAtMs = this.options.now();
2974
+ this.publish({
2975
+ type: "maintenance.started",
2976
+ occurredAtMs: startedAtMs,
2977
+ runId,
2978
+ payload: { stages: [], failedStage: null, error: null }
2979
+ });
2980
+ const started = await this.record({
2981
+ runId,
2982
+ kind: "scheduled",
2983
+ state: "started",
2984
+ startedAtMs,
2985
+ completedAtMs: null,
2986
+ stages: [],
2987
+ failedStage: null,
2988
+ error: null
2989
+ });
2990
+ if (!started.ok) {
2991
+ return await this.failed(runId, startedAtMs, stages, "rollup", started.error, "scheduled");
2992
+ }
2993
+ const rollup = await this.options.storage.materializeRollups(this.options.now(), this.options.maxWorkMs);
2994
+ if (!rollup.ok) {
2995
+ return await this.failed(runId, startedAtMs, stages, "rollup", rollup.error, "scheduled");
2996
+ }
2997
+ stages.push(rollup.value);
2998
+ const retention = await this.options.storage.enforceRetention(
2999
+ this.options.now(),
3000
+ () => !this.stopped
3001
+ );
3002
+ if (!retention.ok) {
3003
+ return await this.failed(runId, startedAtMs, stages, "retention", retention.error, "scheduled");
3004
+ }
3005
+ stages.push(retention.value);
3006
+ const checkpoint = await this.options.storage.checkpoint();
3007
+ if (!checkpoint.ok) {
3008
+ return await this.failed(runId, startedAtMs, stages, "checkpoint", checkpoint.error, "scheduled");
3009
+ }
3010
+ stages.push(checkpoint.value);
3011
+ const reclaim = await this.options.storage.reclaim(this.options.maxReclaimPages);
3012
+ if (!reclaim.ok) {
3013
+ return await this.failed(runId, startedAtMs, stages, "reclaim", reclaim.error, "scheduled");
3014
+ }
3015
+ stages.push(reclaim.value);
3016
+ const result = {
3017
+ kind: "completed",
3018
+ runId,
3019
+ stages: Object.freeze(stages)
3020
+ };
3021
+ const completedAtMs = this.options.now();
3022
+ const recorded = await this.record({
3023
+ runId,
3024
+ kind: "scheduled",
3025
+ state: "completed",
3026
+ startedAtMs,
3027
+ completedAtMs,
3028
+ stages: result.stages,
3029
+ failedStage: null,
3030
+ error: null
3031
+ });
3032
+ if (!recorded.ok) {
3033
+ return await this.failed(
3034
+ runId,
3035
+ startedAtMs,
3036
+ stages,
3037
+ "reclaim",
3038
+ recorded.error,
3039
+ "scheduled"
3040
+ );
3041
+ }
3042
+ this.publish({
3043
+ type: "maintenance.completed",
3044
+ occurredAtMs: completedAtMs,
3045
+ runId,
3046
+ payload: { stages: result.stages, failedStage: null, error: null }
3047
+ });
3048
+ return result;
3049
+ } catch (cause) {
3050
+ return await this.failed(
3051
+ runId,
3052
+ startedAtMs,
3053
+ stages,
3054
+ stages[stages.length - 1]?.stage ?? "rollup",
3055
+ cause instanceof Error ? cause.message : String(cause),
3056
+ "scheduled"
3057
+ );
3058
+ } finally {
3059
+ this.running = false;
3060
+ this.finishActiveRun();
3061
+ }
3062
+ }
3063
+ async runFinal(deadlineMs) {
3064
+ if (!this.stopped) {
3065
+ throw new Error("Final telemetry maintenance requires a stopped, quiescent scheduler");
3066
+ }
3067
+ if (this.running) {
3068
+ return { kind: "skipped", reason: "already-running" };
3069
+ }
3070
+ if (this.options.now() >= deadlineMs) {
3071
+ return { kind: "skipped", reason: "deadline-exhausted" };
3072
+ }
3073
+ this.stopped = false;
3074
+ try {
3075
+ const result = await this.waitForRunResult(this.run(), deadlineMs);
3076
+ return result ?? { kind: "skipped", reason: "deadline-exhausted" };
3077
+ } finally {
3078
+ this.stopped = true;
3079
+ }
3080
+ }
3081
+ async compact(deadlineMs) {
3082
+ if (this.stopped) {
3083
+ return { kind: "skipped", reason: "stopped" };
3084
+ }
3085
+ if (this.running) {
3086
+ return { kind: "skipped", reason: "already-running" };
3087
+ }
3088
+ this.running = true;
3089
+ this.beginActiveRun();
3090
+ const runId = this.options.createRunId();
3091
+ const startedAtMs = this.options.now();
3092
+ this.publish({
3093
+ type: "maintenance.started",
3094
+ occurredAtMs: startedAtMs,
3095
+ runId,
3096
+ payload: { stages: [], failedStage: null, error: null }
3097
+ });
3098
+ try {
3099
+ const started = await this.record({
3100
+ runId,
3101
+ kind: "compaction",
3102
+ state: "started",
3103
+ startedAtMs,
3104
+ completedAtMs: null,
3105
+ stages: [],
3106
+ failedStage: null,
3107
+ error: null
3108
+ });
3109
+ if (!started.ok) {
3110
+ return await this.failed(runId, startedAtMs, [], "compaction", started.error, "compaction");
3111
+ }
3112
+ const compacted = await this.options.storage.compact(deadlineMs);
3113
+ if (!compacted.ok) {
3114
+ return await this.failed(runId, startedAtMs, [], "compaction", compacted.error, "compaction");
3115
+ }
3116
+ const stages = Object.freeze([compacted.value]);
3117
+ const completedAtMs = this.options.now();
3118
+ const recorded = await this.record({
3119
+ runId,
3120
+ kind: "compaction",
3121
+ state: "completed",
3122
+ startedAtMs,
3123
+ completedAtMs,
3124
+ stages,
3125
+ failedStage: null,
3126
+ error: null
3127
+ });
3128
+ if (!recorded.ok) {
3129
+ return await this.failed(
3130
+ runId,
3131
+ startedAtMs,
3132
+ stages,
3133
+ "compaction",
3134
+ recorded.error,
3135
+ "compaction"
3136
+ );
3137
+ }
3138
+ this.publish({
3139
+ type: "maintenance.completed",
3140
+ occurredAtMs: completedAtMs,
3141
+ runId,
3142
+ payload: { stages, failedStage: null, error: null }
3143
+ });
3144
+ return { kind: "completed", runId, stages };
3145
+ } catch (cause) {
3146
+ return await this.failed(
3147
+ runId,
3148
+ startedAtMs,
3149
+ [],
3150
+ "compaction",
3151
+ cause instanceof Error ? cause.message : String(cause),
3152
+ "compaction"
3153
+ );
3154
+ } finally {
3155
+ this.running = false;
3156
+ this.finishActiveRun();
3157
+ }
3158
+ }
3159
+ async stop(deadlineMs) {
3160
+ this.stopped = true;
3161
+ if (this.intervalHandle !== null) {
3162
+ this.options.clearInterval(this.intervalHandle);
3163
+ this.intervalHandle = null;
3164
+ }
3165
+ await this.waitForActiveCompletion(deadlineMs);
3166
+ }
3167
+ beginActiveRun() {
3168
+ this.activeCompletion = new Promise((resolve) => {
3169
+ this.resolveActiveCompletion = resolve;
3170
+ });
3171
+ }
3172
+ finishActiveRun() {
3173
+ this.resolveActiveCompletion();
3174
+ this.resolveActiveCompletion = () => void 0;
3175
+ }
3176
+ async waitForActiveCompletion(deadlineMs) {
3177
+ if (!this.running) {
3178
+ return;
3179
+ }
3180
+ const remainingMs = deadlineMs - this.options.now();
3181
+ if (!Number.isFinite(remainingMs) || remainingMs <= 0) {
3182
+ return;
3183
+ }
3184
+ await new Promise((resolve) => {
3185
+ let settled = false;
3186
+ const finish = () => {
3187
+ if (settled) {
3188
+ return;
3189
+ }
3190
+ settled = true;
3191
+ clearTimeout(timer);
3192
+ resolve();
3193
+ };
3194
+ const timer = setTimeout(finish, Math.min(remainingMs, 2147483647));
3195
+ void this.activeCompletion.then(finish, finish);
3196
+ });
3197
+ }
3198
+ async waitForRunResult(operation, deadlineMs) {
3199
+ const remainingMs = deadlineMs - this.options.now();
3200
+ if (!Number.isFinite(remainingMs) || remainingMs <= 0) {
3201
+ void operation.catch(() => void 0);
3202
+ return null;
3203
+ }
3204
+ return new Promise((resolve, reject) => {
3205
+ let settled = false;
3206
+ const finish = (result, error) => {
3207
+ if (settled) {
3208
+ return;
3209
+ }
3210
+ settled = true;
3211
+ clearTimeout(timer);
3212
+ if (error === void 0) {
3213
+ resolve(result);
3214
+ } else {
3215
+ reject(error);
3216
+ }
3217
+ };
3218
+ const timer = setTimeout(() => finish(null), Math.min(remainingMs, 2147483647));
3219
+ void operation.then(
3220
+ (result) => finish(result),
3221
+ (error) => finish(null, error)
3222
+ );
3223
+ });
3224
+ }
3225
+ async failed(runId, startedAtMs, stages, stage, error, kind) {
3226
+ const completedAtMs = this.options.now();
3227
+ const recorded = await this.record({
3228
+ runId,
3229
+ kind,
3230
+ state: "failed",
3231
+ startedAtMs,
3232
+ completedAtMs,
3233
+ stages,
3234
+ failedStage: stage,
3235
+ error
3236
+ });
3237
+ const resolvedError = recorded.ok ? error : `${error}; maintenance record failed: ${recorded.error}`;
3238
+ const result = {
3239
+ kind: "failed",
3240
+ runId,
3241
+ stages: Object.freeze([...stages]),
3242
+ stage,
3243
+ error: resolvedError
3244
+ };
3245
+ this.publish({
3246
+ type: "maintenance.failed",
3247
+ occurredAtMs: completedAtMs,
3248
+ runId,
3249
+ payload: {
3250
+ stages: result.stages,
3251
+ failedStage: stage,
3252
+ error: resolvedError
3253
+ }
3254
+ });
3255
+ return result;
3256
+ }
3257
+ async record(record) {
3258
+ const recorder = this.options.storage.recordMaintenance;
3259
+ return recorder ? recorder.call(this.options.storage, Object.freeze(record)) : { ok: true, value: void 0 };
3260
+ }
3261
+ publish(event) {
3262
+ try {
3263
+ this.options.observe(Object.freeze(event));
3264
+ } catch {
3265
+ }
3266
+ }
3267
+ };
3268
+ export {
3269
+ BoundedMetadataSnapshotter,
3270
+ DEFAULT_METADATA_SNAPSHOT_LIMITS,
3271
+ DEFAULT_TELEMETRY_ADMISSION_POLICY,
3272
+ DEFAULT_TELEMETRY_BATCH_VALIDATION_LIMITS,
3273
+ InMemoryBatchTransport,
3274
+ MaintenanceCoordinator,
3275
+ MetricDefinitionContract,
3276
+ MetricExtractor,
3277
+ TelemetryAcknowledgementContract,
3278
+ TelemetryBatchContract,
3279
+ TelemetryBatchRouter,
3280
+ TelemetryClient,
3281
+ TelemetryCollectorRuntime,
3282
+ TelemetryPluginRegistry,
3283
+ TelemetryStorageRouter
3284
+ };
3285
+ //# sourceMappingURL=index.js.map