@ryanzeng/nest-observe 0.1.1 → 0.1.3

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.
package/dist/register.mjs CHANGED
@@ -91,15 +91,14 @@ function resolveObserveConfig(options = {}, env = process.env) {
91
91
  allowedHeaders,
92
92
  exportTimeoutMillis: positiveInteger(options.exportTimeoutMillis ?? env.OTEL_EXPORTER_OTLP_TIMEOUT, 1e4),
93
93
  metricExportIntervalMillis: positiveInteger(options.metricExportIntervalMillis ?? env.OTEL_METRIC_EXPORT_INTERVAL, 6e4),
94
- resourceAttributes
94
+ resourceAttributes,
95
+ diagnosticLogging: booleanValue(options.diagnosticLogging, true),
96
+ failFast: booleanValue(options.failFast, false)
95
97
  };
96
98
  if (options.exporters) result.exporters = options.exporters;
97
99
  return result;
98
100
  }
99
101
 
100
- // src/exceptions/process-exception-capture.ts
101
- import { trace, SpanStatusCode } from "@opentelemetry/api";
102
-
103
102
  // src/security/redaction.ts
104
103
  var REDACTED = "[REDACTED]";
105
104
  var SENSITIVE_KEY = /(?:authorization|proxy-authorization|cookie|set-cookie|pass(?:word|wd)?|secret|token|api[-_]?key|phone|mobile)/i;
@@ -133,7 +132,115 @@ function redact(value) {
133
132
  return visit(value, /* @__PURE__ */ new WeakSet());
134
133
  }
135
134
 
135
+ // src/diagnostics.ts
136
+ function toError(value) {
137
+ if (value instanceof Error) return value;
138
+ if (typeof value === "string") return new Error(value);
139
+ try {
140
+ return new Error(JSON.stringify(value));
141
+ } catch {
142
+ return new Error(String(value));
143
+ }
144
+ }
145
+ function sanitizeError(error) {
146
+ const sanitized = new Error(redactText(error.message));
147
+ sanitized.name = error.name;
148
+ if (error.stack) sanitized.stack = redactText(error.stack);
149
+ return sanitized;
150
+ }
151
+ var ObserveDiagnostics = class {
152
+ constructor(logging, onError) {
153
+ this.logging = logging;
154
+ this.onError = onError;
155
+ }
156
+ logging;
157
+ onError;
158
+ currentStatus = "starting";
159
+ currentError;
160
+ failedSignals = /* @__PURE__ */ new Set();
161
+ reported = /* @__PURE__ */ new Set();
162
+ get status() {
163
+ return this.currentStatus;
164
+ }
165
+ get lastError() {
166
+ return this.currentError;
167
+ }
168
+ activate() {
169
+ if (this.currentStatus === "starting") this.currentStatus = "active";
170
+ }
171
+ inactive() {
172
+ this.currentStatus = "inactive";
173
+ }
174
+ stop() {
175
+ this.currentStatus = "stopped";
176
+ }
177
+ success(signal) {
178
+ this.failedSignals.delete(signal);
179
+ for (const fingerprint of this.reported) {
180
+ if (fingerprint.startsWith(`${signal}\0`)) this.reported.delete(fingerprint);
181
+ }
182
+ if (this.currentStatus === "degraded" && this.failedSignals.size === 0) {
183
+ this.currentStatus = "active";
184
+ }
185
+ }
186
+ failure(signal, stage, value) {
187
+ const error = sanitizeError(toError(value));
188
+ const event = { signal, stage, error, timestamp: Date.now() };
189
+ this.currentError = event;
190
+ this.failedSignals.add(signal);
191
+ if (this.currentStatus !== "inactive" && this.currentStatus !== "stopped") {
192
+ this.currentStatus = "degraded";
193
+ }
194
+ const fingerprint = `${signal}\0${stage}\0${error.name}\0${error.message}`;
195
+ if (this.reported.has(fingerprint)) return;
196
+ this.reported.add(fingerprint);
197
+ try {
198
+ this.onError?.(event);
199
+ } catch {
200
+ }
201
+ if (!this.logging) return;
202
+ try {
203
+ process.stderr.write(
204
+ `[nest-observe] ${signal} ${stage} failed: ${redactText(error.message)}
205
+ `
206
+ );
207
+ } catch {
208
+ }
209
+ }
210
+ };
211
+ function withExporterDiagnostics(exporter, signal, diagnostics) {
212
+ const delegate = exporter;
213
+ return new Proxy(exporter, {
214
+ get(target, property) {
215
+ if (property === "export") {
216
+ return (items, callback) => {
217
+ try {
218
+ delegate.export.call(target, items, (result) => {
219
+ if (result.code === 0) {
220
+ diagnostics.success(signal);
221
+ } else {
222
+ diagnostics.failure(
223
+ signal,
224
+ "export",
225
+ result.error ?? new Error(`${signal} export failed`)
226
+ );
227
+ }
228
+ callback(result);
229
+ });
230
+ } catch (error) {
231
+ diagnostics.failure(signal, "export", error);
232
+ throw error;
233
+ }
234
+ };
235
+ }
236
+ const value = Reflect.get(target, property, target);
237
+ return typeof value === "function" ? value.bind(target) : value;
238
+ }
239
+ });
240
+ }
241
+
136
242
  // src/exceptions/process-exception-capture.ts
243
+ import { trace, SpanStatusCode } from "@opentelemetry/api";
137
244
  function asError(value) {
138
245
  return value instanceof Error ? value : new Error(String(value));
139
246
  }
@@ -289,7 +396,7 @@ var NestMethodInstrumenter = class {
289
396
  if (!descriptor || typeof original !== "function" || isTraceIgnored(original, type) || isTraceDecorated(original)) continue;
290
397
  const existing = Reflect.get(target, methodName);
291
398
  if (typeof existing === "function" && existing !== original && isTraceDecorated(existing)) continue;
292
- const name = componentName || type.name || "Anonymous";
399
+ const name = typeof componentName === "symbol" ? String(componentName) : typeof componentName === "string" && componentName ? componentName : type.name || "Anonymous";
293
400
  const attributes = {
294
401
  [`nestjs.${kind}`]: name,
295
402
  "nestjs.method": methodName
@@ -342,7 +449,7 @@ var CompatibleNestInstrumentation = class extends NestInstrumentation {
342
449
  const instance = await original.apply(this, args);
343
450
  try {
344
451
  const wrapper = args[1];
345
- if (!instance || typeof instance !== "object" || !wrapper?.metatype) return instance;
452
+ if (!instance || typeof instance !== "object" || !wrapper?.metatype || wrapper.inject !== void 0) return instance;
346
453
  if (["InternalCoreModule", "ObserveModule", "DiscoveryModule"].includes(wrapper.host?.name ?? "")) {
347
454
  return instance;
348
455
  }
@@ -386,7 +493,8 @@ var SEVERITY = {
386
493
  fatal: "FATAL"
387
494
  };
388
495
  function bodyValue(message) {
389
- if (message instanceof Error) return redactText(message.stack ?? `${message.name}: ${message.message}`);
496
+ if (message instanceof Error)
497
+ return redactText(message.stack ?? `${message.name}: ${message.message}`);
390
498
  return redact(message);
391
499
  }
392
500
  var NestLoggerInstrumentation = class {
@@ -431,7 +539,8 @@ var NestLoggerInstrumentation = class {
431
539
  disable() {
432
540
  if (!this.enabled) return;
433
541
  const prototype = ConsoleLogger.prototype;
434
- for (const [method, original] of this.originals) prototype[method] = original;
542
+ for (const [method, original] of this.originals)
543
+ prototype[method] = original;
435
544
  this.originals.clear();
436
545
  this.enabled = false;
437
546
  }
@@ -494,9 +603,14 @@ var HttpRequestMetrics = class {
494
603
  errorCount;
495
604
  startedAt = /* @__PURE__ */ new WeakMap();
496
605
  start(request) {
606
+ if (this.startedAt.has(request)) return false;
497
607
  this.startedAt.set(request, process.hrtime.bigint());
608
+ return true;
498
609
  }
499
610
  record(request, response) {
611
+ const started = this.startedAt.get(request);
612
+ if (started === void 0) return false;
613
+ this.startedAt.delete(request);
500
614
  const statusCode = Number.isFinite(response.statusCode) ? response.statusCode : 0;
501
615
  const attributes = {
502
616
  "service.name": this.serviceName,
@@ -505,12 +619,9 @@ var HttpRequestMetrics = class {
505
619
  "http.response.status_code": statusCode
506
620
  };
507
621
  this.requestCount.add(1, attributes);
508
- const started = this.startedAt.get(request);
509
- if (started !== void 0) {
510
- this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
511
- this.startedAt.delete(request);
512
- }
622
+ this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
513
623
  if (statusCode >= 500) this.errorCount.add(1, attributes);
624
+ return true;
514
625
  }
515
626
  };
516
627
 
@@ -601,8 +712,118 @@ import {
601
712
  resourceFromAttributes
602
713
  } from "@opentelemetry/resources";
603
714
  import { hostname } from "os";
604
- var SDK_NAME = "@ryanzeng/nest-observe";
605
- var SDK_VERSION = "0.1.0";
715
+
716
+ // package.json
717
+ var package_default = {
718
+ name: "@ryanzeng/nest-observe",
719
+ version: "0.1.3",
720
+ description: "Zero-config, vendor-neutral OpenTelemetry observability for NestJS",
721
+ keywords: [
722
+ "nestjs",
723
+ "opentelemetry",
724
+ "observability",
725
+ "otlp",
726
+ "tracing",
727
+ "metrics",
728
+ "logging"
729
+ ],
730
+ license: "MIT",
731
+ homepage: "https://github.com/ryanzen9/nest-observe#readme",
732
+ bugs: {
733
+ url: "https://github.com/ryanzen9/nest-observe/issues"
734
+ },
735
+ repository: {
736
+ type: "git",
737
+ url: "git+https://github.com/ryanzen9/nest-observe.git"
738
+ },
739
+ packageManager: "pnpm@10.13.1",
740
+ sideEffects: [
741
+ "./dist/register.js",
742
+ "./dist/register.mjs"
743
+ ],
744
+ main: "./dist/index.js",
745
+ module: "./dist/index.mjs",
746
+ types: "./dist/index.d.ts",
747
+ exports: {
748
+ ".": {
749
+ import: {
750
+ types: "./dist/index.d.mts",
751
+ default: "./dist/index.mjs"
752
+ },
753
+ require: {
754
+ types: "./dist/index.d.ts",
755
+ default: "./dist/index.js"
756
+ }
757
+ },
758
+ "./register": {
759
+ import: {
760
+ types: "./dist/register.d.mts",
761
+ default: "./dist/register.mjs"
762
+ },
763
+ require: {
764
+ types: "./dist/register.d.ts",
765
+ default: "./dist/register.js"
766
+ }
767
+ }
768
+ },
769
+ files: [
770
+ "dist",
771
+ "README.md",
772
+ "CHANGELOG.md",
773
+ "LICENSE"
774
+ ],
775
+ scripts: {
776
+ build: "tsup",
777
+ test: "vitest run",
778
+ "test:watch": "vitest",
779
+ typecheck: "tsc --noEmit",
780
+ check: "pnpm typecheck && pnpm test && pnpm build"
781
+ },
782
+ engines: {
783
+ node: ">=20"
784
+ },
785
+ peerDependencies: {
786
+ "@nestjs/common": ">=10 <13",
787
+ "@nestjs/core": ">=10 <13",
788
+ "reflect-metadata": ">=0.1.12 <1",
789
+ rxjs: "^7.1.0"
790
+ },
791
+ dependencies: {
792
+ "@opentelemetry/api": "^1.9.0",
793
+ "@opentelemetry/api-logs": "^0.221.0",
794
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.221.0",
795
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.221.0",
796
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.221.0",
797
+ "@opentelemetry/instrumentation": "^0.221.0",
798
+ "@opentelemetry/instrumentation-http": "^0.221.0",
799
+ "@opentelemetry/instrumentation-nestjs-core": "^0.67.0",
800
+ "@opentelemetry/resources": "^2.10.0",
801
+ "@opentelemetry/sdk-logs": "^0.221.0",
802
+ "@opentelemetry/sdk-metrics": "^2.10.0",
803
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
804
+ "@opentelemetry/sdk-trace-node": "^2.10.0",
805
+ "@prisma/instrumentation": "^7.10.0"
806
+ },
807
+ devDependencies: {
808
+ "@nestjs/common": "^12.0.1",
809
+ "@nestjs/core": "^12.0.1",
810
+ "@nestjs/testing": "^12.0.1",
811
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
812
+ "@types/node": "^24.0.0",
813
+ "reflect-metadata": "^0.2.2",
814
+ rxjs: "^7.8.2",
815
+ tsup: "^8.5.1",
816
+ typescript: "^5.9.3",
817
+ vitest: "^4.1.11"
818
+ },
819
+ publishConfig: {
820
+ access: "public"
821
+ }
822
+ };
823
+
824
+ // src/resource.ts
825
+ var SDK_NAME = package_default.name;
826
+ var SDK_VERSION = package_default.version;
606
827
  function createObserveResource(config) {
607
828
  return defaultResource().merge(
608
829
  resourceFromAttributes({
@@ -659,14 +880,23 @@ var SpanRedactionProcessor = class {
659
880
 
660
881
  // src/sdk.ts
661
882
  var InactiveObserveHandle = class {
662
- constructor(config) {
883
+ constructor(config, diagnostics) {
663
884
  this.config = config;
885
+ this.diagnostics = diagnostics;
664
886
  }
665
887
  config;
888
+ diagnostics;
666
889
  started = false;
667
890
  tracerProvider = void 0;
668
891
  meterProvider = void 0;
669
892
  loggerProvider = void 0;
893
+ httpRequestMetrics = void 0;
894
+ get status() {
895
+ return this.diagnostics.status;
896
+ }
897
+ get lastError() {
898
+ return this.diagnostics.lastError;
899
+ }
670
900
  forceFlush() {
671
901
  return Promise.resolve();
672
902
  }
@@ -675,52 +905,73 @@ var InactiveObserveHandle = class {
675
905
  }
676
906
  };
677
907
  var ActiveObserveHandle = class {
678
- constructor(config, tracerProvider, meterProvider, loggerProvider, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations) {
908
+ constructor(config, tracerProvider, meterProvider, loggerProvider, httpRequestMetrics, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations, diagnostics) {
679
909
  this.config = config;
680
910
  this.tracerProvider = tracerProvider;
681
911
  this.meterProvider = meterProvider;
682
912
  this.loggerProvider = loggerProvider;
913
+ this.httpRequestMetrics = httpRequestMetrics;
683
914
  this.runtimeMetrics = runtimeMetrics;
684
915
  this.loggerInstrumentation = loggerInstrumentation;
685
916
  this.exceptionCapture = exceptionCapture;
686
917
  this.instrumentations = instrumentations;
918
+ this.diagnostics = diagnostics;
687
919
  }
688
920
  config;
689
921
  tracerProvider;
690
922
  meterProvider;
691
923
  loggerProvider;
924
+ httpRequestMetrics;
692
925
  runtimeMetrics;
693
926
  loggerInstrumentation;
694
927
  exceptionCapture;
695
928
  instrumentations;
929
+ diagnostics;
696
930
  started = true;
697
931
  stopped = false;
932
+ get status() {
933
+ return this.diagnostics.status;
934
+ }
935
+ get lastError() {
936
+ return this.diagnostics.lastError;
937
+ }
698
938
  async forceFlush() {
699
- await Promise.allSettled([
700
- this.tracerProvider?.forceFlush(),
701
- this.meterProvider?.forceFlush(),
702
- this.loggerProvider?.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })
703
- ].filter((item) => Boolean(item)));
939
+ await this.runSafely("forceFlush", [
940
+ ...this.config.traces && this.tracerProvider ? [["traces", () => this.tracerProvider.forceFlush()]] : [],
941
+ ...this.config.metrics && this.meterProvider ? [["metrics", () => this.meterProvider.forceFlush()]] : [],
942
+ ...this.config.logs && this.loggerProvider ? [["logs", () => this.loggerProvider.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })]] : []
943
+ ]);
704
944
  }
705
945
  async shutdown() {
706
946
  if (this.stopped) return;
707
947
  this.stopped = true;
708
948
  this.loggerInstrumentation?.disable();
709
- this.runtimeMetrics?.stop();
710
949
  this.exceptionCapture.stop();
711
950
  for (const instrumentation of this.instrumentations) {
712
951
  try {
713
952
  instrumentation.disable();
714
- } catch {
953
+ } catch (error) {
954
+ this.diagnostics.failure("sdk", "shutdown", error);
715
955
  }
716
956
  }
717
957
  await this.forceFlush();
718
- await Promise.allSettled([
719
- this.tracerProvider?.shutdown(),
720
- this.meterProvider?.shutdown(),
721
- this.loggerProvider?.shutdown()
722
- ].filter((item) => Boolean(item)));
958
+ this.runtimeMetrics?.stop();
959
+ await this.runSafely("shutdown", [
960
+ ...this.tracerProvider ? [["traces", () => this.tracerProvider.shutdown()]] : [],
961
+ ...this.meterProvider ? [["metrics", () => this.meterProvider.shutdown()]] : [],
962
+ ...this.loggerProvider ? [["logs", () => this.loggerProvider.shutdown()]] : []
963
+ ]);
723
964
  if (activeRuntime === this) activeRuntime = void 0;
965
+ this.diagnostics.stop();
966
+ }
967
+ async runSafely(stage, operations) {
968
+ await Promise.all(operations.map(async ([signal, operation]) => {
969
+ try {
970
+ await operation();
971
+ } catch (error) {
972
+ this.diagnostics.failure(signal, stage, error);
973
+ }
974
+ }));
724
975
  }
725
976
  };
726
977
  var activeRuntime;
@@ -731,12 +982,16 @@ function exporterConfig(url, config) {
731
982
  timeoutMillis: config.exportTimeoutMillis
732
983
  };
733
984
  }
734
- function createTraceProvider(config, resource) {
985
+ function createTraceProvider(config, resource, diagnostics) {
735
986
  if (!config.traces && !config.metrics) return void 0;
736
987
  const spanProcessors = [new SpanRedactionProcessor()];
737
988
  if (config.traces) {
738
989
  const exporter = config.exporters?.span ?? new OTLPTraceExporter(exporterConfig(config.endpoints.traces, config));
739
- spanProcessors.push(new BatchSpanProcessor(exporter, {
990
+ spanProcessors.push(new BatchSpanProcessor(withExporterDiagnostics(
991
+ exporter,
992
+ "traces",
993
+ diagnostics
994
+ ), {
740
995
  exportTimeoutMillis: config.exportTimeoutMillis,
741
996
  maxQueueSize: 2048,
742
997
  maxExportBatchSize: 512
@@ -751,14 +1006,18 @@ function createTraceProvider(config, resource) {
751
1006
  provider.register();
752
1007
  return provider;
753
1008
  }
754
- function createMeterProvider(config, resource) {
1009
+ function createMeterProvider(config, resource, diagnostics) {
755
1010
  if (!config.metrics) return void 0;
756
1011
  let reader;
757
1012
  if (config.exporters?.metricReader) {
758
1013
  reader = config.exporters.metricReader;
759
1014
  } else {
760
1015
  reader = new PeriodicExportingMetricReader({
761
- exporter: new OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1016
+ exporter: withExporterDiagnostics(
1017
+ new OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1018
+ "metrics",
1019
+ diagnostics
1020
+ ),
762
1021
  exportIntervalMillis: config.metricExportIntervalMillis,
763
1022
  exportTimeoutMillis: Math.min(config.exportTimeoutMillis, config.metricExportIntervalMillis),
764
1023
  cardinalityLimits: { default: 2e3, histogram: 2e3 }
@@ -776,13 +1035,13 @@ function createMeterProvider(config, resource) {
776
1035
  metrics.setGlobalMeterProvider(provider);
777
1036
  return provider;
778
1037
  }
779
- function createLoggerProvider(config, resource) {
1038
+ function createLoggerProvider(config, resource, diagnostics) {
780
1039
  if (!config.logs) return void 0;
781
1040
  const exporter = config.exporters?.log ?? new OTLPLogExporter(exporterConfig(config.endpoints.logs, config));
782
1041
  const provider = new LoggerProvider({
783
1042
  resource,
784
1043
  processors: [new BatchLogRecordProcessor({
785
- exporter,
1044
+ exporter: withExporterDiagnostics(exporter, "logs", diagnostics),
786
1045
  exportTimeoutMillis: config.exportTimeoutMillis,
787
1046
  maxQueueSize: 2048,
788
1047
  maxExportBatchSize: 512
@@ -794,7 +1053,11 @@ function createLoggerProvider(config, resource) {
794
1053
  function observe(options = {}) {
795
1054
  if (activeRuntime?.started) return activeRuntime;
796
1055
  const config = resolveObserveConfig(options);
797
- if (!config.enabled) return new InactiveObserveHandle(config);
1056
+ const diagnostics = new ObserveDiagnostics(config.diagnosticLogging, options.onError);
1057
+ if (!config.enabled) {
1058
+ diagnostics.inactive();
1059
+ return new InactiveObserveHandle(config, diagnostics);
1060
+ }
798
1061
  let tracerProvider;
799
1062
  let meterProvider;
800
1063
  let loggerProvider;
@@ -804,10 +1067,11 @@ function observe(options = {}) {
804
1067
  const instrumentations = [];
805
1068
  try {
806
1069
  const resource = createObserveResource(config);
807
- meterProvider = createMeterProvider(config, resource);
1070
+ meterProvider = createMeterProvider(config, resource, diagnostics);
808
1071
  const meter = meterProvider?.getMeter(SDK_NAME, SDK_VERSION);
809
- tracerProvider = createTraceProvider(config, resource);
810
- loggerProvider = createLoggerProvider(config, resource);
1072
+ const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
1073
+ tracerProvider = createTraceProvider(config, resource, diagnostics);
1074
+ loggerProvider = createLoggerProvider(config, resource, diagnostics);
811
1075
  runtimeMetrics = meter ? new RuntimeMetrics(meter) : void 0;
812
1076
  runtimeMetrics?.start();
813
1077
  const otelLogger = loggerProvider?.getLogger(SDK_NAME, SDK_VERSION);
@@ -822,7 +1086,6 @@ function observe(options = {}) {
822
1086
  exceptionCapture.start();
823
1087
  if (config.traces || config.metrics) {
824
1088
  const safeHeaders = config.allowedHeaders.filter((header) => !isSensitiveKey(header));
825
- const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
826
1089
  instrumentations.push(new HttpInstrumentation({
827
1090
  requireParentforOutgoingSpans: false,
828
1091
  headersToSpanAttributes: {
@@ -866,14 +1129,18 @@ function observe(options = {}) {
866
1129
  tracerProvider,
867
1130
  meterProvider,
868
1131
  loggerProvider,
1132
+ httpRequestMetrics,
869
1133
  runtimeMetrics,
870
1134
  loggerInstrumentation,
871
1135
  exceptionCapture,
872
- instrumentations
1136
+ instrumentations,
1137
+ diagnostics
873
1138
  );
1139
+ diagnostics.activate();
874
1140
  activeRuntime = runtime;
875
1141
  return runtime;
876
- } catch {
1142
+ } catch (error) {
1143
+ diagnostics.failure("sdk", "initialization", error);
877
1144
  loggerInstrumentation?.disable();
878
1145
  runtimeMetrics?.stop();
879
1146
  exceptionCapture?.stop();
@@ -888,7 +1155,9 @@ function observe(options = {}) {
888
1155
  meterProvider?.shutdown(),
889
1156
  loggerProvider?.shutdown()
890
1157
  ].filter((item) => Boolean(item)));
891
- return new InactiveObserveHandle(config);
1158
+ diagnostics.inactive();
1159
+ if (config.failFast) throw toError(error);
1160
+ return new InactiveObserveHandle(config, diagnostics);
892
1161
  }
893
1162
  }
894
1163