@autter/runtime-node 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,810 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// ../otlp-ingester/src/compat-manifest.json
2
+ var compat_manifest_default = {
3
+ manifestVersion: 1,
4
+ docs: "https://github.com/Autter-dev/autter-runtime/blob/main/docs/COMPATIBILITY.md",
5
+ upgrade: {
6
+ ingester: "docker pull ghcr.io/autter-dev/otlp-ingester:latest and restart it (ClickHouse migrations run at boot)",
7
+ sdk: "npm install {package}@latest"
8
+ },
9
+ features: [
10
+ {
11
+ id: "llm_calls",
12
+ title: "LLM call tracking",
13
+ ingester: "1.1.0",
14
+ migrations: ["0004-llm-calls"],
15
+ sdks: { "@autter/runtime-node": "1.1.0", "@autter/runtime-next": "1.1.0" }
16
+ },
17
+ {
18
+ id: "endpoint_latency",
19
+ title: "Endpoint latency regression detection",
20
+ ingester: "1.3.1",
21
+ migrations: ["0005-latency-histograms"],
22
+ sdks: { "@autter/runtime-node": "1.3.0", "@autter/runtime-next": "1.3.0" }
23
+ },
24
+ {
25
+ id: "browser_network_events",
26
+ title: "Browser network, timing and outcome capture",
27
+ ingester: "1.3.2",
28
+ migrations: [],
29
+ sdks: { "@autter/runtime-browser": "1.3.2", "@autter/runtime-next": "1.3.2" },
30
+ browserEvents: ["request_failure", "timing", "outcome"]
31
+ },
32
+ {
33
+ id: "profiles",
34
+ title: "Profile ingestion",
35
+ ingester: "1.3.2",
36
+ migrations: ["0006-profile-samples"],
37
+ route: "/v1/profiles",
38
+ sdks: {}
39
+ },
40
+ {
41
+ id: "source_maps",
42
+ title: "Source map upload",
43
+ ingester: "1.3.2",
44
+ migrations: ["0007-source-maps"],
45
+ route: "/v1/sourcemaps",
46
+ sdks: {}
47
+ },
48
+ {
49
+ id: "memory_metrics",
50
+ title: "Memory pressure detection",
51
+ ingester: "1.3.3",
52
+ migrations: ["0008-memory-signals", "0010-memory-temporality"],
53
+ sdks: { "@autter/runtime-node": "1.3.3", "@autter/runtime-next": "1.3.3" }
54
+ },
55
+ {
56
+ id: "platform_events",
57
+ title: "Platform OOM/restart events",
58
+ ingester: "1.3.3",
59
+ migrations: ["0008-memory-signals"],
60
+ route: "/v1/platform-events",
61
+ sdks: {}
62
+ },
63
+ {
64
+ id: "csp_violations",
65
+ title: "Browser CSP violation capture",
66
+ ingester: "1.3.4",
67
+ migrations: [],
68
+ sdks: { "@autter/runtime-browser": "1.3.3", "@autter/runtime-next": "1.3.4" },
69
+ browserEvents: ["csp_violation"]
70
+ },
71
+ {
72
+ id: "operation_logging",
73
+ title: "Operation logging",
74
+ ingester: "1.4.0",
75
+ migrations: ["0011-runtime-logs"],
76
+ route: "/v1/logs",
77
+ sdks: { "@autter/runtime-node": "1.4.0", "@autter/runtime-next": "1.4.0" }
78
+ },
79
+ {
80
+ id: "request_summaries",
81
+ title: "Request summaries and the per-route request rollup",
82
+ ingester: "1.5.0",
83
+ migrations: ["0012-runtime-logs-requests", "0014-runtime-request-1m"],
84
+ route: "/v1/logs",
85
+ sdks: { "@autter/runtime-node": "1.5.0", "@autter/runtime-next": "1.5.0", "@autter/runtime-edge": "1.0.0" }
86
+ },
87
+ {
88
+ id: "coded_errors",
89
+ title: "Coded errors and code-based grouping",
90
+ ingester: "1.5.0",
91
+ migrations: ["0013-occurrence-codes"],
92
+ sdks: { "@autter/runtime-node": "1.5.0", "@autter/runtime-next": "1.5.0", "@autter/runtime-browser": "1.4.0", "@autter/runtime-edge": "1.0.0" }
93
+ },
94
+ {
95
+ id: "breadcrumbs",
96
+ title: "Breadcrumbs: the steps before an error",
97
+ ingester: "1.6.0",
98
+ migrations: [],
99
+ sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-node": "1.6.0", "@autter/runtime-next": "1.6.0" }
100
+ },
101
+ {
102
+ id: "source_map_debug_ids",
103
+ title: "Source map debug ids",
104
+ ingester: "1.6.0",
105
+ migrations: ["0017-source-maps-debug-id"],
106
+ route: "/v1/sourcemaps/check",
107
+ sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-next": "1.6.0" }
108
+ },
109
+ {
110
+ id: "fingerprint_override",
111
+ title: "Fingerprint override",
112
+ ingester: "1.6.0",
113
+ migrations: [],
114
+ sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-node": "1.6.0", "@autter/runtime-next": "1.6.0", "@autter/runtime-edge": "1.1.0" }
115
+ },
116
+ {
117
+ id: "browser_trace_propagation",
118
+ title: "Browser trace propagation (browser errors linked to server traces)",
119
+ ingester: "1.6.0",
120
+ migrations: [],
121
+ sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-next": "1.6.0", "@autter/runtime-node": "1.6.0" }
122
+ }
123
+ ]
124
+ };
125
+
126
+ // ../otlp-ingester/src/compat.ts
127
+ var COMPAT_MANIFEST = compat_manifest_default;
128
+ var INGESTER_PACKAGE = "@autter/otlp-ingester";
129
+ var INGESTER_VERSION_HEADER = "x-autter-ingester-version";
130
+ function parseVersion(version) {
131
+ if (typeof version !== "string") return null;
132
+ const match = /^v?(\d{1,6})\.(\d{1,6})\.(\d{1,6})(-[0-9A-Za-z.-]+)?/.exec(
133
+ version.trim()
134
+ );
135
+ if (!match) return null;
136
+ return [
137
+ Number(match[1]),
138
+ Number(match[2]),
139
+ Number(match[3]),
140
+ // A prerelease sorts before its release: 1.4.0-rc.1 < 1.4.0.
141
+ match[4] ? 0 : 1
142
+ ];
143
+ }
144
+ function compareVersions(a, b) {
145
+ const left = parseVersion(a);
146
+ const right = parseVersion(b);
147
+ if (!left || !right) return null;
148
+ for (let i = 0; i < 4; i++) {
149
+ if (left[i] !== right[i]) return left[i] < right[i] ? -1 : 1;
150
+ }
151
+ return 0;
152
+ }
153
+ function versionAtLeast(version, minimum) {
154
+ if (!version) return null;
155
+ const result = compareVersions(version, minimum);
156
+ return result === null ? null : result >= 0;
157
+ }
158
+ function featureById(id, manifest = COMPAT_MANIFEST) {
159
+ return manifest.features.find((feature) => feature.id === id);
160
+ }
161
+ function featuresForBrowserEvents(types, manifest = COMPAT_MANIFEST) {
162
+ const wanted = new Set(types);
163
+ return manifest.features.filter((feature) => _optionalChain([feature, 'access', _ => _.browserEvents, 'optionalAccess', _2 => _2.some, 'call', _3 => _3((type) => wanted.has(type))])).map((feature) => feature.id);
164
+ }
165
+ function ingesterUpgradeHint(manifest = COMPAT_MANIFEST) {
166
+ return `Upgrade the ingester: ${manifest.upgrade.ingester}. See ${manifest.docs}`;
167
+ }
168
+ function sdkUpgradeHint(pkg, manifest) {
169
+ return `Upgrade the SDK: ${manifest.upgrade.sdk.replace("{package}", pkg)}. See ${manifest.docs}`;
170
+ }
171
+ function evaluateCompat(input) {
172
+ const manifest = _nullishCoalesce(input.manifest, () => ( COMPAT_MANIFEST));
173
+ const issues = [];
174
+ const seen = /* @__PURE__ */ new Set();
175
+ for (const id of input.features) {
176
+ if (seen.has(id)) continue;
177
+ seen.add(id);
178
+ const feature = featureById(id, manifest);
179
+ if (!feature) continue;
180
+ const sdkMin = input.sdk ? feature.sdks[input.sdk.name] : void 0;
181
+ if (input.sdk && sdkMin && versionAtLeast(input.sdk.version, sdkMin) === false) {
182
+ const fix2 = sdkUpgradeHint(input.sdk.name, manifest);
183
+ issues.push({
184
+ feature: id,
185
+ title: feature.title,
186
+ kind: "sdk_too_old",
187
+ component: input.sdk.name,
188
+ required: sdkMin,
189
+ actual: input.sdk.version,
190
+ message: `${feature.title} needs ${input.sdk.name} >= ${sdkMin}; yours is ${input.sdk.version}. ${fix2}`,
191
+ fix: fix2
192
+ });
193
+ }
194
+ const ingester = input.ingester;
195
+ if (!ingester) continue;
196
+ const fix = ingesterUpgradeHint(manifest);
197
+ const atLeast = versionAtLeast(ingester.version, feature.ingester);
198
+ if (atLeast === false) {
199
+ issues.push({
200
+ feature: id,
201
+ title: feature.title,
202
+ kind: "ingester_too_old",
203
+ component: INGESTER_PACKAGE,
204
+ required: feature.ingester,
205
+ actual: ingester.version,
206
+ message: `${feature.title} needs ingester >= ${feature.ingester}; yours is ${ingester.version}. ${fix}`,
207
+ fix
208
+ });
209
+ continue;
210
+ }
211
+ if (ingester.version === null && feature.route && _optionalChain([ingester, 'access', _4 => _4.routes, 'optionalAccess', _5 => _5[feature.route]]) === false) {
212
+ const actual = "1.4.0 or older (no /v1/compat)";
213
+ issues.push({
214
+ feature: id,
215
+ title: feature.title,
216
+ kind: "ingester_too_old",
217
+ component: INGESTER_PACKAGE,
218
+ required: feature.ingester,
219
+ actual,
220
+ message: `${feature.title} needs ingester >= ${feature.ingester}; yours is ${actual} and has no ${feature.route} route, so this data is dropped. ${fix}`,
221
+ fix
222
+ });
223
+ continue;
224
+ }
225
+ const schema = ingester.schema;
226
+ if (atLeast && schema && feature.migrations.length) {
227
+ const applied = new Set(schema.applied);
228
+ const missing = feature.migrations.filter((m) => !applied.has(m));
229
+ if (schema.status === "failed" || schema.status === "ready" && missing.length) {
230
+ const schemaFix = `Check the ingester's CLICKHOUSE_URL/credentials and its logs, then restart it so the boot migrations run. See ${manifest.docs}`;
231
+ const what = missing.length ? missing.join(", ") : feature.migrations.join(", ");
232
+ issues.push({
233
+ feature: id,
234
+ title: feature.title,
235
+ kind: "schema_not_applied",
236
+ component: INGESTER_PACKAGE,
237
+ required: feature.migrations.join(", "),
238
+ actual: schema.status === "failed" ? "migrations failed" : `missing ${what}`,
239
+ message: `${feature.title} needs ClickHouse migration ${what}, which ingester ${ingester.version} has not applied (schema ${schema.status}). ${schemaFix}`,
240
+ fix: schemaFix
241
+ });
242
+ }
243
+ }
244
+ }
245
+ return issues;
246
+ }
247
+ function ingesterInfoFromReport(body) {
248
+ if (!body || typeof body !== "object") return null;
249
+ const report = body;
250
+ const version = _optionalChain([report, 'access', _6 => _6.ingester, 'optionalAccess', _7 => _7.version]);
251
+ if (typeof version !== "string" || !parseVersion(version)) return null;
252
+ const schema = report.schema;
253
+ const statuses = ["ready", "pending", "failed", "unconfigured"];
254
+ return {
255
+ version,
256
+ ...schema && statuses.includes(schema.status) && Array.isArray(schema.applied) ? {
257
+ schema: {
258
+ status: schema.status,
259
+ applied: schema.applied.filter((id) => typeof id === "string")
260
+ }
261
+ } : {}
262
+ };
263
+ }
264
+
265
+ // src/lifecycle.ts
266
+ var _core = require('@opentelemetry/core');
267
+ var debugEnabled = process.env.AUTTER_DEBUG === "1" || process.env.AUTTER_DEBUG === "true";
268
+ function setDebugMode(enabled) {
269
+ debugEnabled = enabled;
270
+ }
271
+ function isDebugEnabled() {
272
+ return debugEnabled;
273
+ }
274
+ function debugLog(...args) {
275
+ if (debugEnabled) {
276
+ console.error("[autter]", ...args);
277
+ }
278
+ }
279
+ var TelemetryStats = (_class = class {constructor() { _class.prototype.__init.call(this); }
280
+ /** Captures queued via our APIs and not yet seen leaving an exporter. */
281
+ __init() {this.pendingCaptures = 0}
282
+ markCaptured(count = 1) {
283
+ this.pendingCaptures += count;
284
+ }
285
+ /** An exporter batch left the process successfully. */
286
+ onExported(spanCount) {
287
+ this.pendingCaptures = Math.max(0, this.pendingCaptures - spanCount);
288
+ if (debugEnabled && spanCount > 0) {
289
+ debugLog(`exported ${spanCount} span${spanCount === 1 ? "" : "s"}`);
290
+ }
291
+ }
292
+ markAllFlushed() {
293
+ this.pendingCaptures = 0;
294
+ }
295
+ }, _class);
296
+ var telemetryStats = new TelemetryStats();
297
+ var CountingExporter = class {
298
+ constructor(inner) {
299
+ this.inner = inner;
300
+ }
301
+
302
+ export(spans, resultCallback) {
303
+ this.inner.export(spans, (result) => {
304
+ if (result.code === _core.ExportResultCode.SUCCESS) {
305
+ telemetryStats.onExported(spans.length);
306
+ }
307
+ resultCallback(result);
308
+ });
309
+ }
310
+ shutdown() {
311
+ return this.inner.shutdown();
312
+ }
313
+ forceFlush() {
314
+ return _nullishCoalesce(_optionalChain([this, 'access', _8 => _8.inner, 'access', _9 => _9.forceFlush, 'optionalCall', _10 => _10()]), () => ( Promise.resolve()));
315
+ }
316
+ };
317
+ var flushTargets = /* @__PURE__ */ new Map();
318
+ function registerFlushTarget(key, target) {
319
+ flushTargets.set(key, target);
320
+ }
321
+ function unregisterFlushTargets() {
322
+ flushTargets.clear();
323
+ }
324
+ function activeFlushTargets() {
325
+ return [...flushTargets.values()];
326
+ }
327
+ var DEFAULT_SIGNALS = ["SIGINT", "SIGTERM"];
328
+ var SIGNAL_EXIT_CODES = {
329
+ SIGINT: 130,
330
+ SIGTERM: 143
331
+ };
332
+ function installAutterAutoFlush(options = {}) {
333
+ const logger = _nullishCoalesce(options.logger, () => ( console));
334
+ const log = options.log !== false;
335
+ const warnOnUnflushedExit = options.warnOnUnflushedExit !== false;
336
+ const timeoutMs = Math.max(100, _nullishCoalesce(options.timeoutMs, () => ( 3e3)));
337
+ const signals = _nullishCoalesce(options.signals, () => ( DEFAULT_SIGNALS));
338
+ const flushOnUncaughtException = options.flushOnUncaughtException !== false;
339
+ let disposed = false;
340
+ let inFlight = null;
341
+ const doFlush = async () => {
342
+ const targets = _nullishCoalesce(options.targets, () => ( activeFlushTargets()));
343
+ if (targets.length === 0) return true;
344
+ let timer;
345
+ const timedOut = new Promise((resolve) => {
346
+ timer = setTimeout(() => resolve(false), timeoutMs);
347
+ });
348
+ const drained = Promise.allSettled(
349
+ targets.map(
350
+ (target) => Promise.resolve().then(() => target.forceFlush())
351
+ )
352
+ ).then(
353
+ (results) => results.every((result) => result.status === "fulfilled")
354
+ );
355
+ let ok;
356
+ try {
357
+ ok = await Promise.race([drained, timedOut]);
358
+ } finally {
359
+ clearTimeout(timer);
360
+ }
361
+ if (ok) {
362
+ telemetryStats.markAllFlushed();
363
+ if (log) {
364
+ logger.log("[autter] telemetry flushed");
365
+ }
366
+ } else {
367
+ logger.error(
368
+ `[autter] flush did not finish within ${timeoutMs}ms \u2014 some telemetry may be lost`
369
+ );
370
+ }
371
+ return ok;
372
+ };
373
+ const flush = (reason = "exit") => {
374
+ if (disposed) return Promise.resolve(false);
375
+ if (!inFlight) {
376
+ inFlight = doFlush().finally(() => {
377
+ inFlight = null;
378
+ });
379
+ }
380
+ if (isDebugEnabled()) debugLog(`auto-flush requested on ${reason}`);
381
+ return inFlight;
382
+ };
383
+ const signalCounts = /* @__PURE__ */ new Map();
384
+ let handlingUncaughtException = false;
385
+ const onUncaughtException = (error) => {
386
+ if (process.listenerCount("uncaughtException") > 1) return;
387
+ if (handlingUncaughtException) return;
388
+ handlingUncaughtException = true;
389
+ logger.error(error instanceof Error ? _nullishCoalesce(error.stack, () => ( error.message)) : String(error));
390
+ const keepAlive = setTimeout(() => {
391
+ }, timeoutMs + 50);
392
+ void flush("uncaughtException").finally(() => {
393
+ clearTimeout(keepAlive);
394
+ process.exit(1);
395
+ });
396
+ };
397
+ const onSignal = (signal) => {
398
+ const count = (_nullishCoalesce(signalCounts.get(signal), () => ( 0))) + 1;
399
+ signalCounts.set(signal, count);
400
+ const exitCode = _nullishCoalesce(SIGNAL_EXIT_CODES[signal], () => ( 1));
401
+ if (count > 1) {
402
+ process.exit(exitCode);
403
+ }
404
+ const otherHandlers = process.listenerCount(signal) - 1;
405
+ if (otherHandlers > 0) {
406
+ void flush(signal);
407
+ return;
408
+ }
409
+ void flush(signal).finally(() => {
410
+ process.exit(exitCode);
411
+ });
412
+ };
413
+ const MAX_BEFORE_EXIT_FLUSHES = 10;
414
+ let beforeExitFlushes = 0;
415
+ const onBeforeExit = () => {
416
+ if (disposed) return;
417
+ const targets = _nullishCoalesce(options.targets, () => ( activeFlushTargets()));
418
+ const worthIt = beforeExitFlushes === 0 ? targets.length > 0 : telemetryStats.pendingCaptures > 0;
419
+ if (!worthIt || beforeExitFlushes >= MAX_BEFORE_EXIT_FLUSHES) return;
420
+ beforeExitFlushes++;
421
+ const keepAlive = setTimeout(() => {
422
+ }, timeoutMs);
423
+ void flush("beforeExit").finally(() => clearTimeout(keepAlive));
424
+ };
425
+ const onExit = () => {
426
+ if (!warnOnUnflushedExit || disposed) return;
427
+ const pending = telemetryStats.pendingCaptures;
428
+ if (pending > 0) {
429
+ logger.error(
430
+ `[autter] process exiting with ~${pending} captured item(s) never confirmed exported \u2014 await autter.shutdown() on graceful shutdown`
431
+ );
432
+ }
433
+ };
434
+ const signalHandlers = /* @__PURE__ */ new Map();
435
+ process.on("beforeExit", onBeforeExit);
436
+ if (flushOnUncaughtException) {
437
+ process.on("uncaughtException", onUncaughtException);
438
+ }
439
+ for (const signal of signals) {
440
+ const handler = () => onSignal(signal);
441
+ signalHandlers.set(signal, handler);
442
+ process.on(signal, handler);
443
+ }
444
+ process.on("exit", onExit);
445
+ return {
446
+ flush,
447
+ dispose() {
448
+ disposed = true;
449
+ process.removeListener("beforeExit", onBeforeExit);
450
+ process.removeListener("uncaughtException", onUncaughtException);
451
+ process.removeListener("exit", onExit);
452
+ for (const [signal, handler] of signalHandlers) {
453
+ process.removeListener(signal, handler);
454
+ }
455
+ signalHandlers.clear();
456
+ }
457
+ };
458
+ }
459
+
460
+ // src/compat.ts
461
+ var _http = require('http');
462
+ var _https = require('https');
463
+
464
+ // package.json
465
+ var package_default = {
466
+ name: "@autter/runtime-node",
467
+ version: "1.6.0",
468
+ description: "Autter Runtime for Node.js: request wide events, coded errors, operation logging, same-origin browser relay and a curated OpenTelemetry server tracker",
469
+ license: "MIT",
470
+ type: "module",
471
+ main: "./dist/index.cjs",
472
+ module: "./dist/index.js",
473
+ types: "./dist/index.d.ts",
474
+ exports: {
475
+ ".": {
476
+ types: "./dist/index.d.ts",
477
+ import: "./dist/index.js",
478
+ require: "./dist/index.cjs"
479
+ },
480
+ "./testing": {
481
+ types: "./dist/testing.d.ts",
482
+ import: "./dist/testing.js",
483
+ require: "./dist/testing.cjs"
484
+ },
485
+ "./package.json": "./package.json"
486
+ },
487
+ bin: {
488
+ "autter-runtime": "./dist/cli.js"
489
+ },
490
+ files: [
491
+ "dist"
492
+ ],
493
+ repository: {
494
+ type: "git",
495
+ url: "https://github.com/Autter-dev/autter-runtime.git",
496
+ directory: "packages/runtime-node"
497
+ },
498
+ scripts: {
499
+ build: "node ../../scripts/ensure-built.mjs runtime-core && tsup",
500
+ test: "npm run build -w @autter/runtime-core && npm run build && npm run build -w @autter/otlp-ingester && node --test test/*.test.mjs"
501
+ },
502
+ dependencies: {
503
+ "@opentelemetry/api": "^1.9.0",
504
+ "@opentelemetry/core": "^1.30.1",
505
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.57.1",
506
+ "@opentelemetry/exporter-trace-otlp-http": "^0.57.1",
507
+ "@opentelemetry/instrumentation-http": "^0.57.1",
508
+ "@opentelemetry/resources": "^1.30.1",
509
+ "@opentelemetry/sdk-metrics": "^1.30.1",
510
+ "@opentelemetry/sdk-node": "^0.57.1",
511
+ "@opentelemetry/sdk-trace-base": "^1.30.1",
512
+ "@opentelemetry/semantic-conventions": "^1.28.0"
513
+ },
514
+ devDependencies: {
515
+ "@autter/runtime-core": "0.0.0",
516
+ "@types/node": "^22.10.0",
517
+ express: "^4.21.2",
518
+ fastify: "^5.12.5",
519
+ koa: "^3.2.1",
520
+ tsup: "^8.3.5",
521
+ typescript: "^5.7.0"
522
+ },
523
+ engines: {
524
+ node: ">=20"
525
+ },
526
+ publishConfig: {
527
+ access: "public",
528
+ provenance: true
529
+ },
530
+ homepage: "https://github.com/Autter-dev/autter-runtime#readme",
531
+ bugs: "https://github.com/Autter-dev/autter-runtime/issues",
532
+ keywords: [
533
+ "opentelemetry",
534
+ "otlp",
535
+ "error-tracking",
536
+ "telemetry",
537
+ "nodejs",
538
+ "express"
539
+ ]
540
+ };
541
+
542
+ // src/compat.ts
543
+ var SDK_IDENTITY = {
544
+ name: "@autter/runtime-node",
545
+ version: package_default.version
546
+ };
547
+ var TIMEOUT_MS = 3e3;
548
+ function compatRequest(method, url, options = {}) {
549
+ return new Promise((resolve) => {
550
+ let settled = false;
551
+ const done = (value) => {
552
+ if (settled) return;
553
+ settled = true;
554
+ clearTimeout(timer);
555
+ resolve(value);
556
+ };
557
+ const timer = setTimeout(() => {
558
+ _optionalChain([req, 'optionalAccess', _11 => _11.destroy, 'call', _12 => _12()]);
559
+ done(null);
560
+ }, _nullishCoalesce(options.timeoutMs, () => ( TIMEOUT_MS)));
561
+ if (options.unref !== false) timer.unref();
562
+ let req;
563
+ try {
564
+ const target = new URL(url);
565
+ const send = target.protocol === "https:" ? _https.request : _http.request;
566
+ req = send(
567
+ target,
568
+ {
569
+ method,
570
+ headers: {
571
+ accept: "application/json",
572
+ "user-agent": `${SDK_IDENTITY.name}/${SDK_IDENTITY.version}`,
573
+ ...options.body !== void 0 ? { "content-type": "application/json", "content-length": String(Buffer.byteLength(options.body)) } : {},
574
+ ...options.headers
575
+ }
576
+ },
577
+ (res) => {
578
+ let body = "";
579
+ res.setEncoding("utf8");
580
+ res.on("data", (chunk) => {
581
+ if (body.length < 256 * 1024) body += chunk;
582
+ });
583
+ res.on("end", () => done({ status: _nullishCoalesce(res.statusCode, () => ( 0)), headers: res.headers, body }));
584
+ res.on("error", () => done(null));
585
+ }
586
+ );
587
+ if (options.unref !== false) req.on("socket", (socket) => socket.unref());
588
+ req.on("error", () => done(null));
589
+ req.end(options.body);
590
+ } catch (e) {
591
+ done(null);
592
+ }
593
+ });
594
+ }
595
+ async function fetchIngesterCompat(endpoint, options = {}) {
596
+ const base = endpoint.replace(/\/$/, "");
597
+ const res = await compatRequest("GET", `${base}/v1/compat`, options);
598
+ if (!res) return { info: null, report: null, error: `could not reach ${base}/v1/compat` };
599
+ if (res.status === 200) {
600
+ let report = null;
601
+ try {
602
+ report = JSON.parse(res.body);
603
+ } catch (e2) {
604
+ }
605
+ const info = _nullishCoalesce(ingesterInfoFromReport(report), () => ( headerInfo(res.headers)));
606
+ return info ? { info, report } : { info: null, report, error: "unrecognised /v1/compat response" };
607
+ }
608
+ if (res.status === 404) {
609
+ const fromHeader = headerInfo(res.headers);
610
+ if (fromHeader) return { info: fromHeader, report: null };
611
+ const routes = {};
612
+ for (const route of _nullishCoalesce(options.probeRoutes, () => ( []))) {
613
+ const exists = await probeRoute(base, route, options);
614
+ if (exists !== null) routes[route] = exists;
615
+ }
616
+ return { info: { version: null, legacy: true, routes }, report: null };
617
+ }
618
+ return { info: headerInfo(res.headers), report: null, error: `/v1/compat answered ${res.status}` };
619
+ }
620
+ async function probeRoute(base, route, options) {
621
+ const res = await compatRequest("POST", `${base}${route}`, { ...options, body: "{}" });
622
+ return res ? res.status !== 404 : null;
623
+ }
624
+ function headerInfo(headers) {
625
+ const raw = typeof headers.get === "function" ? headers.get(INGESTER_VERSION_HEADER) : headers[INGESTER_VERSION_HEADER];
626
+ const version = Array.isArray(raw) ? raw[0] : raw;
627
+ return version ? ingesterInfoFromReport({ ingester: { version } }) : null;
628
+ }
629
+ function envDisabled() {
630
+ const value = _optionalChain([process, 'access', _13 => _13.env, 'access', _14 => _14.AUTTER_COMPAT_CHECK, 'optionalAccess', _15 => _15.trim, 'call', _16 => _16(), 'access', _17 => _17.toLowerCase, 'call', _18 => _18()]);
631
+ return value === "0" || value === "false" || value === "off";
632
+ }
633
+ var defaultWarn = (message) => console.warn(`[autter-runtime] ${message}`);
634
+ var state = freshState(null, false);
635
+ function freshState(endpoint, enabled, sdk = SDK_IDENTITY, warn = defaultWarn) {
636
+ return {
637
+ endpoint,
638
+ enabled,
639
+ sdk,
640
+ features: /* @__PURE__ */ new Set(),
641
+ info: null,
642
+ started: false,
643
+ pendingProbes: /* @__PURE__ */ new Set(),
644
+ warned: /* @__PURE__ */ new Set(),
645
+ warn
646
+ };
647
+ }
648
+ function configureCompatCheck(options) {
649
+ state = freshState(
650
+ options.endpoint.replace(/\/$/, ""),
651
+ options.enabled !== false && !envDisabled(),
652
+ _nullishCoalesce(options.sdk, () => ( SDK_IDENTITY)),
653
+ _nullishCoalesce(options.warn, () => ( defaultWarn))
654
+ );
655
+ }
656
+ function ensureCompatConfigured(endpoint) {
657
+ if (state.endpoint === null && !envDisabled()) {
658
+ state = freshState(endpoint.replace(/\/$/, ""), true);
659
+ }
660
+ }
661
+ function noteCompatFeature(id) {
662
+ try {
663
+ if (!state.enabled || state.features.has(id) || !featureById(id)) return;
664
+ state.features.add(id);
665
+ if (state.info) {
666
+ evaluateAndWarn();
667
+ return;
668
+ }
669
+ if (state.started) return;
670
+ state.started = true;
671
+ const current = state;
672
+ setTimeout(() => void runCheck(current), 0).unref();
673
+ } catch (e3) {
674
+ }
675
+ }
676
+ async function runCheck(current) {
677
+ try {
678
+ if (!current.endpoint) return;
679
+ const { info, error } = await fetchIngesterCompat(current.endpoint, {
680
+ probeRoutes: routesFor(current.features)
681
+ });
682
+ if (state !== current) return;
683
+ if (!info) {
684
+ debugLog(`compat check skipped: ${_nullishCoalesce(error, () => ( "ingester version unknown"))}`);
685
+ return;
686
+ }
687
+ mergeInfo(info);
688
+ for (const route of routesFor(current.features)) current.pendingProbes.add(route);
689
+ debugLog(
690
+ `compat check: ingester ${_nullishCoalesce(info.version, () => ( "1.4.0 or older (no /v1/compat)"))}` + (info.schema ? `, schema ${info.schema.status}` : "") + `, sdk ${current.sdk.name}@${current.sdk.version}, features ${[...current.features].join(", ") || "none"}`
691
+ );
692
+ evaluateAndWarn();
693
+ } catch (err) {
694
+ debugLog("compat check failed", err);
695
+ }
696
+ }
697
+ function routesFor(features) {
698
+ const routes = [];
699
+ for (const id of features) {
700
+ const route = _optionalChain([featureById, 'call', _19 => _19(id), 'optionalAccess', _20 => _20.route]);
701
+ if (route && !routes.includes(route)) routes.push(route);
702
+ }
703
+ return routes;
704
+ }
705
+ function mergeInfo(next) {
706
+ const prev = state.info;
707
+ state.info = {
708
+ version: _nullishCoalesce(_nullishCoalesce(next.version, () => ( _optionalChain([prev, 'optionalAccess', _21 => _21.version]))), () => ( null)),
709
+ ...next.legacy || _optionalChain([prev, 'optionalAccess', _22 => _22.legacy]) ? { legacy: true } : {},
710
+ ..._nullishCoalesce(next.schema, () => ( _optionalChain([prev, 'optionalAccess', _23 => _23.schema]))) ? { schema: _nullishCoalesce(next.schema, () => ( _optionalChain([prev, 'optionalAccess', _24 => _24.schema]))) } : {},
711
+ routes: { ..._optionalChain([prev, 'optionalAccess', _25 => _25.routes]), ...next.routes }
712
+ };
713
+ }
714
+ function evaluateAndWarn() {
715
+ const info = state.info;
716
+ if (!info) return;
717
+ if (info.version === null && state.endpoint) {
718
+ const unprobed = routesFor(state.features).filter(
719
+ (route) => _optionalChain([info, 'access', _26 => _26.routes, 'optionalAccess', _27 => _27[route]]) === void 0 && !state.pendingProbes.has(route)
720
+ );
721
+ if (unprobed.length) {
722
+ const current = state;
723
+ for (const route of unprobed) current.pendingProbes.add(route);
724
+ void Promise.all(unprobed.map((route) => probeRoute(current.endpoint, route, {}).then((exists) => [route, exists]))).then((results) => {
725
+ if (state !== current) return;
726
+ const routes = {};
727
+ for (const [route, exists] of results) if (exists !== null) routes[route] = exists;
728
+ mergeInfo({ version: null, legacy: true, routes });
729
+ evaluateAndWarn();
730
+ }).catch(() => {
731
+ });
732
+ }
733
+ }
734
+ for (const issue of evaluateCompat({ features: state.features, ingester: info })) {
735
+ warnOnce(issue);
736
+ }
737
+ }
738
+ function warnOnce(issue) {
739
+ const key = `${issue.feature}:${issue.kind}`;
740
+ if (state.warned.has(key)) return;
741
+ state.warned.add(key);
742
+ try {
743
+ state.warn(issue.message);
744
+ } catch (e4) {
745
+ }
746
+ }
747
+ function observeIngesterResponse(response) {
748
+ try {
749
+ if (!state.enabled) return;
750
+ const fromHeader = headerInfo(response.headers);
751
+ if (fromHeader) {
752
+ if (_optionalChain([state, 'access', _28 => _28.info, 'optionalAccess', _29 => _29.version]) !== fromHeader.version) {
753
+ mergeInfo(fromHeader);
754
+ evaluateAndWarn();
755
+ }
756
+ return;
757
+ }
758
+ if (response.status === 404 && response.route && !_optionalChain([state, 'access', _30 => _30.info, 'optionalAccess', _31 => _31.version])) {
759
+ mergeInfo({ version: null, legacy: true, routes: { [response.route]: false } });
760
+ evaluateAndWarn();
761
+ }
762
+ } catch (e5) {
763
+ }
764
+ }
765
+ function noteRelayRejection(eventTypes, status, headers) {
766
+ try {
767
+ if (!state.enabled || status !== 400 || headerInfo(headers)) return [];
768
+ const named = [];
769
+ for (const id of featuresForBrowserEvents(eventTypes)) {
770
+ const feature = featureById(id);
771
+ const key = `${id}:relay_rejected`;
772
+ if (state.warned.has(key)) continue;
773
+ state.warned.add(key);
774
+ named.push(id);
775
+ try {
776
+ state.warn(
777
+ `The ingester rejected a browser batch containing ${feature.title.toLowerCase()} events. ${feature.title} needs ingester >= ${feature.ingester}; yours is 1.4.0 or older (it predates version reporting), so those batches are dropped. ${ingesterUpgradeHint(COMPAT_MANIFEST)}`
778
+ );
779
+ } catch (e6) {
780
+ }
781
+ }
782
+ return named;
783
+ } catch (e7) {
784
+ return [];
785
+ }
786
+ }
787
+
788
+
789
+
790
+
791
+
792
+
793
+
794
+
795
+
796
+
797
+
798
+
799
+
800
+
801
+
802
+
803
+
804
+
805
+
806
+
807
+
808
+
809
+
810
+ exports.COMPAT_MANIFEST = COMPAT_MANIFEST; exports.INGESTER_VERSION_HEADER = INGESTER_VERSION_HEADER; exports.versionAtLeast = versionAtLeast; exports.featuresForBrowserEvents = featuresForBrowserEvents; exports.evaluateCompat = evaluateCompat; exports.setDebugMode = setDebugMode; exports.isDebugEnabled = isDebugEnabled; exports.debugLog = debugLog; exports.telemetryStats = telemetryStats; exports.CountingExporter = CountingExporter; exports.registerFlushTarget = registerFlushTarget; exports.unregisterFlushTargets = unregisterFlushTargets; exports.installAutterAutoFlush = installAutterAutoFlush; exports.SDK_IDENTITY = SDK_IDENTITY; exports.compatRequest = compatRequest; exports.fetchIngesterCompat = fetchIngesterCompat; exports.configureCompatCheck = configureCompatCheck; exports.ensureCompatConfigured = ensureCompatConfigured; exports.noteCompatFeature = noteCompatFeature; exports.observeIngesterResponse = observeIngesterResponse; exports.noteRelayRejection = noteRelayRejection;