@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
@@ -0,0 +1,2318 @@
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
+ export {
2306
+ BoundedMetadataSnapshotter,
2307
+ DEFAULT_METADATA_SNAPSHOT_LIMITS,
2308
+ DEFAULT_TELEMETRY_ADMISSION_POLICY,
2309
+ DEFAULT_TELEMETRY_BATCH_VALIDATION_LIMITS,
2310
+ MetricDefinitionContract,
2311
+ MetricExtractor,
2312
+ TelemetryAcknowledgementContract,
2313
+ TelemetryBatchContract,
2314
+ TelemetryBatchRouter,
2315
+ TelemetryClient,
2316
+ TelemetryStorageRouter
2317
+ };
2318
+ //# sourceMappingURL=index.js.map