@interfere/node 0.0.1-canary.0 → 0.0.1-canary.2

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 (69) hide show
  1. package/README.md +1 -1
  2. package/dist/capture.cjs +1 -0
  3. package/dist/capture.d.cts +34 -0
  4. package/dist/capture.d.mts +3 -3
  5. package/dist/capture.mjs +2 -85
  6. package/dist/index.cjs +1 -0
  7. package/dist/index.d.cts +4 -0
  8. package/dist/index.mjs +1 -3
  9. package/dist/init.cjs +1 -0
  10. package/dist/init.d.cts +62 -0
  11. package/dist/init.d.mts +7 -6
  12. package/dist/init.mjs +2 -87
  13. package/dist/internal/dedupe.cjs +1 -0
  14. package/dist/internal/dedupe.d.cts +6 -0
  15. package/dist/internal/dedupe.d.mts +2 -1
  16. package/dist/internal/dedupe.mjs +2 -11
  17. package/dist/internal/env.cjs +1 -0
  18. package/dist/internal/env.d.cts +21 -0
  19. package/dist/internal/env.d.mts +5 -4
  20. package/dist/internal/env.mjs +2 -25
  21. package/dist/internal/global-handlers.cjs +1 -0
  22. package/dist/internal/global-handlers.d.cts +26 -0
  23. package/dist/internal/global-handlers.d.mts +2 -1
  24. package/dist/internal/global-handlers.mjs +2 -68
  25. package/dist/internal/normalize-request.cjs +1 -0
  26. package/dist/internal/normalize-request.d.cts +16 -0
  27. package/dist/internal/normalize-request.d.mts +2 -1
  28. package/dist/internal/normalize-request.mjs +2 -58
  29. package/dist/internal/release-slug.cjs +1 -0
  30. package/dist/internal/release-slug.d.cts +9 -0
  31. package/dist/internal/release-slug.d.mts +2 -1
  32. package/dist/internal/release-slug.mjs +2 -32
  33. package/dist/internal/types.cjs +0 -0
  34. package/dist/internal/types.d.cts +17 -0
  35. package/dist/internal/types.d.mts +2 -1
  36. package/dist/internal/types.mjs +1 -1
  37. package/dist/internal/url.cjs +1 -0
  38. package/dist/internal/url.d.cts +5 -0
  39. package/dist/internal/url.d.mts +5 -0
  40. package/dist/internal/url.mjs +2 -0
  41. package/dist/internal/version.cjs +1 -0
  42. package/dist/internal/version.d.cts +5 -0
  43. package/dist/internal/version.d.mts +2 -1
  44. package/dist/internal/version.mjs +2 -5
  45. package/dist/package.cjs +1 -0
  46. package/dist/package.mjs +2 -5
  47. package/package.json +40 -16
  48. package/dist/capture.d.mts.map +0 -1
  49. package/dist/capture.mjs.map +0 -1
  50. package/dist/init.d.mts.map +0 -1
  51. package/dist/init.mjs.map +0 -1
  52. package/dist/internal/dedupe.d.mts.map +0 -1
  53. package/dist/internal/dedupe.mjs.map +0 -1
  54. package/dist/internal/env.d.mts.map +0 -1
  55. package/dist/internal/env.mjs.map +0 -1
  56. package/dist/internal/global-handlers.d.mts.map +0 -1
  57. package/dist/internal/global-handlers.mjs.map +0 -1
  58. package/dist/internal/normalize-request.d.mts.map +0 -1
  59. package/dist/internal/normalize-request.mjs.map +0 -1
  60. package/dist/internal/release-slug.d.mts.map +0 -1
  61. package/dist/internal/release-slug.mjs.map +0 -1
  62. package/dist/internal/remote-config.d.mts +0 -5
  63. package/dist/internal/remote-config.d.mts.map +0 -1
  64. package/dist/internal/remote-config.mjs +0 -29
  65. package/dist/internal/remote-config.mjs.map +0 -1
  66. package/dist/internal/types.d.mts.map +0 -1
  67. package/dist/internal/version.d.mts.map +0 -1
  68. package/dist/internal/version.mjs.map +0 -1
  69. package/dist/package.mjs.map +0 -1
package/README.md CHANGED
@@ -27,4 +27,4 @@ try {
27
27
  }
28
28
  ```
29
29
 
30
- Set `INTERFERE_API_KEY` in the environment. Full docs at <https://interfere.com/docs>.
30
+ Set `INTERFERE_PUBLIC_KEY` to your surface's `interfere_pk_*` public key. Full docs at <https://interfere.com/docs>.
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const require_internal_dedupe=require("./internal/dedupe.cjs"),require_internal_env=require("./internal/env.cjs"),require_internal_normalize_request=require("./internal/normalize-request.cjs");let _interfere_types_sdk_errors=require("@interfere/types/sdk/errors"),_opentelemetry_api=require("@opentelemetry/api");const TRACER_NAME=`@interfere/node`,DEFAULT_ERROR_MECHANISM={type:_interfere_types_sdk_errors.MECHANISM_TYPE.node.captureError,handled:!0};function captureError(error,request,errorContext){if(!require_internal_env.isEnabledInEnvironment()||require_internal_dedupe.isErrorCaptured(error))return;require_internal_dedupe.markErrorCaptured(error);let err=(0,_interfere_types_sdk_errors.toError)(error),normalizedRequest=require_internal_normalize_request.normalizeRequest(request),mechanism=errorContext?.mechanism??DEFAULT_ERROR_MECHANISM,span=_opentelemetry_api.trace.getTracer(TRACER_NAME).startSpan(`interfere.error.captured`,{kind:_opentelemetry_api.SpanKind.INTERNAL},_opentelemetry_api.context.active()),requestMethod=errorContext?.node?.requestMethod??normalizedRequest?.method??null;requestMethod&&span.setAttribute(`http.request.method`,requestMethod);let requestPath=errorContext?.node?.requestPath??normalizedRequest?.path??null;requestPath&&span.setAttribute(`url.path`,requestPath);let sessionId=normalizedRequest?.headers.get(`x-interfere-session`);sessionId&&span.setAttribute(`interfere.session.id`,sessionId);let eventAttrs={"exception.type":err.name,"exception.message":err.message,"interfere.exception.mechanism":mechanism.type,"interfere.exception.handled":mechanism.handled};err.stack&&(eventAttrs[`exception.stacktrace`]=err.stack);let digest=errorContext?.node?.errorDigest;digest&&(eventAttrs[`interfere.error.digest`]=digest),span.addEvent(`exception`,eventAttrs),span.setStatus({code:_opentelemetry_api.SpanStatusCode.ERROR}),span.end()}function captureMessage(message,request,errorContext){captureError(Error(message),request,errorContext)}async function withSpan(name,fn){let span=_opentelemetry_api.trace.getTracer(TRACER_NAME).startSpan(name);try{return await _opentelemetry_api.context.with(_opentelemetry_api.trace.setSpan(_opentelemetry_api.context.active(),span),fn)}catch(error){throw span.recordException(error),span.setStatus({code:_opentelemetry_api.SpanStatusCode.ERROR}),error}finally{span.end()}}exports.captureError=captureError,exports.captureMessage=captureMessage,exports.withSpan=withSpan;
@@ -0,0 +1,34 @@
1
+ import { CaptureErrorContext } from "./internal/types.cjs";
2
+
3
+ //#region src/capture.d.ts
4
+ /**
5
+ * Reports an error to Interfere. Safe to call from anywhere — no-op when the
6
+ * SDK is not configured (`INTERFERE_PUBLIC_KEY` unset).
7
+ *
8
+ * Internally opens a short OTel span and records the error as an `exception`
9
+ * event on it. The span ships through the same `OTLPTraceExporter` wired up
10
+ * in `init()`, so error capture inherits the OTel transport (batch, retry,
11
+ * traceparent propagation) without a separate fetch path. The collector's
12
+ * `mapTracesToSpanEvents` lifts the OTel-semconv `exception.*` attrs plus
13
+ * our `interfere.exception.*` taxonomy into the `span_events` datasource.
14
+ *
15
+ * Pass the request (a `Request`, a Node `IncomingMessage`-like object, or
16
+ * any object with `headers`/`method`/`url`) so the span carries request
17
+ * context. If there's an active OTel context (e.g. an HTTP handler span),
18
+ * the exception attaches to it as a child; otherwise it becomes a
19
+ * root span.
20
+ */
21
+ declare function captureError(error: unknown, request?: unknown, errorContext?: CaptureErrorContext): void;
22
+ /**
23
+ * Reports a string message as an error event.
24
+ */
25
+ declare function captureMessage(message: string, request?: unknown, errorContext?: CaptureErrorContext): void;
26
+ /**
27
+ * Wraps a function in an OTel span. Thrown errors are recorded on the span
28
+ * and rethrown. Without an active provider (i.e. before `init()`), still
29
+ * runs `fn` but produces no telemetry.
30
+ */
31
+ declare function withSpan<T>(name: string, fn: () => Promise<T> | T): Promise<T>;
32
+ //#endregion
33
+ export { type CaptureErrorContext, captureError, captureMessage, withSpan };
34
+ //# sourceMappingURL=capture.d.cts.map
@@ -3,8 +3,7 @@ import { CaptureErrorContext } from "./internal/types.mjs";
3
3
  //#region src/capture.d.ts
4
4
  /**
5
5
  * Reports an error to Interfere. Safe to call from anywhere — no-op when the
6
- * SDK is not configured (`INTERFERE_API_KEY` unset) or when the `errors`
7
- * plugin is disabled by remote config.
6
+ * SDK is not configured (`INTERFERE_PUBLIC_KEY` unset).
8
7
  *
9
8
  * Internally opens a short OTel span and records the error as an `exception`
10
9
  * event on it. The span ships through the same `OTLPTraceExporter` wired up
@@ -31,4 +30,5 @@ declare function captureMessage(message: string, request?: unknown, errorContext
31
30
  */
32
31
  declare function withSpan<T>(name: string, fn: () => Promise<T> | T): Promise<T>;
33
32
  //#endregion
34
- export { type CaptureErrorContext, captureError, captureMessage, withSpan };
33
+ export { type CaptureErrorContext, captureError, captureMessage, withSpan };
34
+ //# sourceMappingURL=capture.d.mts.map
package/dist/capture.mjs CHANGED
@@ -1,85 +1,2 @@
1
- import { isErrorCaptured, markErrorCaptured } from "./internal/dedupe.mjs";
2
- import { isEnabledInEnvironment } from "./internal/env.mjs";
3
- import { normalizeRequest } from "./internal/normalize-request.mjs";
4
- import { isPluginEnabled } from "./internal/remote-config.mjs";
5
- import { MECHANISM_TYPE, toError } from "@interfere/types/sdk/errors";
6
- import { SpanKind, SpanStatusCode, context, trace } from "@opentelemetry/api";
7
- //#region src/capture.ts
8
- const TRACER_NAME = "@interfere/node";
9
- const ERROR_SPAN_NAME = "interfere.error.captured";
10
- const SESSION_HEADER = "x-interfere-session";
11
- const DEFAULT_ERROR_MECHANISM = {
12
- type: MECHANISM_TYPE.node.captureError,
13
- handled: true
14
- };
15
- /**
16
- * Reports an error to Interfere. Safe to call from anywhere — no-op when the
17
- * SDK is not configured (`INTERFERE_API_KEY` unset) or when the `errors`
18
- * plugin is disabled by remote config.
19
- *
20
- * Internally opens a short OTel span and records the error as an `exception`
21
- * event on it. The span ships through the same `OTLPTraceExporter` wired up
22
- * in `init()`, so error capture inherits the OTel transport (batch, retry,
23
- * traceparent propagation) without a separate fetch path. The collector's
24
- * `mapTracesToSpanEvents` lifts the OTel-semconv `exception.*` attrs plus
25
- * our `interfere.exception.*` taxonomy into the `span_events` datasource.
26
- *
27
- * Pass the request (a `Request`, a Node `IncomingMessage`-like object, or
28
- * any object with `headers`/`method`/`url`) so the span carries request
29
- * context. If there's an active OTel context (e.g. an HTTP handler span),
30
- * the exception attaches to it as a child; otherwise it becomes a
31
- * root span.
32
- */
33
- function captureError(error, request, errorContext) {
34
- if (!isEnabledInEnvironment()) return;
35
- if (!isPluginEnabled("errors")) return;
36
- if (isErrorCaptured(error)) return;
37
- markErrorCaptured(error);
38
- const err = toError(error);
39
- const normalizedRequest = normalizeRequest(request);
40
- const mechanism = errorContext?.mechanism ?? DEFAULT_ERROR_MECHANISM;
41
- const span = trace.getTracer(TRACER_NAME).startSpan(ERROR_SPAN_NAME, { kind: SpanKind.INTERNAL }, context.active());
42
- const requestMethod = errorContext?.node?.requestMethod ?? normalizedRequest?.method ?? null;
43
- if (requestMethod) span.setAttribute("http.request.method", requestMethod);
44
- const requestPath = errorContext?.node?.requestPath ?? normalizedRequest?.path ?? null;
45
- if (requestPath) span.setAttribute("url.path", requestPath);
46
- const sessionId = normalizedRequest?.headers.get(SESSION_HEADER);
47
- if (sessionId) span.setAttribute("interfere.session.id", sessionId);
48
- const eventAttrs = {
49
- "exception.type": err.name,
50
- "exception.message": err.message,
51
- "interfere.exception.mechanism": mechanism.type,
52
- "interfere.exception.handled": mechanism.handled
53
- };
54
- if (err.stack) eventAttrs["exception.stacktrace"] = err.stack;
55
- const digest = errorContext?.node?.errorDigest;
56
- if (digest) eventAttrs["interfere.error.digest"] = digest;
57
- span.addEvent("exception", eventAttrs);
58
- span.setStatus({ code: SpanStatusCode.ERROR });
59
- span.end();
60
- }
61
- /**
62
- * Reports a string message as an error event.
63
- */
64
- function captureMessage(message, request, errorContext) {
65
- captureError(new Error(message), request, errorContext);
66
- }
67
- /**
68
- * Wraps a function in an OTel span. Thrown errors are recorded on the span
69
- * and rethrown. Without an active provider (i.e. before `init()`), still
70
- * runs `fn` but produces no telemetry.
71
- */
72
- async function withSpan(name, fn) {
73
- const span = trace.getTracer(TRACER_NAME).startSpan(name);
74
- try {
75
- return await context.with(trace.setSpan(context.active(), span), fn);
76
- } catch (error) {
77
- span.recordException(error);
78
- span.setStatus({ code: SpanStatusCode.ERROR });
79
- throw error;
80
- } finally {
81
- span.end();
82
- }
83
- }
84
- //#endregion
85
- export { captureError, captureMessage, withSpan };
1
+ import{isErrorCaptured,markErrorCaptured}from"./internal/dedupe.mjs";import{isEnabledInEnvironment}from"./internal/env.mjs";import{normalizeRequest}from"./internal/normalize-request.mjs";import{MECHANISM_TYPE,toError}from"@interfere/types/sdk/errors";import{SpanKind,SpanStatusCode,context,trace}from"@opentelemetry/api";const TRACER_NAME=`@interfere/node`,DEFAULT_ERROR_MECHANISM={type:MECHANISM_TYPE.node.captureError,handled:!0};function captureError(error,request,errorContext){if(!isEnabledInEnvironment()||isErrorCaptured(error))return;markErrorCaptured(error);let err=toError(error),normalizedRequest=normalizeRequest(request),mechanism=errorContext?.mechanism??DEFAULT_ERROR_MECHANISM,span=trace.getTracer(TRACER_NAME).startSpan(`interfere.error.captured`,{kind:SpanKind.INTERNAL},context.active()),requestMethod=errorContext?.node?.requestMethod??normalizedRequest?.method??null;requestMethod&&span.setAttribute(`http.request.method`,requestMethod);let requestPath=errorContext?.node?.requestPath??normalizedRequest?.path??null;requestPath&&span.setAttribute(`url.path`,requestPath);let sessionId=normalizedRequest?.headers.get(`x-interfere-session`);sessionId&&span.setAttribute(`interfere.session.id`,sessionId);let eventAttrs={"exception.type":err.name,"exception.message":err.message,"interfere.exception.mechanism":mechanism.type,"interfere.exception.handled":mechanism.handled};err.stack&&(eventAttrs[`exception.stacktrace`]=err.stack);let digest=errorContext?.node?.errorDigest;digest&&(eventAttrs[`interfere.error.digest`]=digest),span.addEvent(`exception`,eventAttrs),span.setStatus({code:SpanStatusCode.ERROR}),span.end()}function captureMessage(message,request,errorContext){captureError(Error(message),request,errorContext)}async function withSpan(name,fn){let span=trace.getTracer(TRACER_NAME).startSpan(name);try{return await context.with(trace.setSpan(context.active(),span),fn)}catch(error){throw span.recordException(error),span.setStatus({code:SpanStatusCode.ERROR}),error}finally{span.end()}}export{captureError,captureMessage,withSpan};
2
+ //# sourceMappingURL=capture.mjs.map
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const require_capture=require("./capture.cjs"),require_init=require("./init.cjs");exports.captureError=require_capture.captureError,exports.captureMessage=require_capture.captureMessage,exports.close=require_init.close,exports.flush=require_init.flush,exports.init=require_init.init,exports.withSpan=require_capture.withSpan;
@@ -0,0 +1,4 @@
1
+ import { CaptureErrorContext } from "./internal/types.cjs";
2
+ import { captureError, captureMessage, withSpan } from "./capture.cjs";
3
+ import { InitOptions, close, flush, init } from "./init.cjs";
4
+ export { type CaptureErrorContext, type InitOptions, captureError, captureMessage, close, flush, init, withSpan };
package/dist/index.mjs CHANGED
@@ -1,3 +1 @@
1
- import { captureError, captureMessage, withSpan } from "./capture.mjs";
2
- import { close, flush, init } from "./init.mjs";
3
- export { captureError, captureMessage, close, flush, init, withSpan };
1
+ import{captureError,captureMessage,withSpan}from"./capture.mjs";import{close,flush,init}from"./init.mjs";export{captureError,captureMessage,close,flush,init,withSpan};
package/dist/init.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const require_internal_env=require("./internal/env.cjs"),require_internal_global_handlers=require("./internal/global-handlers.cjs"),require_internal_release_slug=require("./internal/release-slug.cjs"),require_internal_url=require("./internal/url.cjs"),require_internal_version=require("./internal/version.cjs");let _interfere_constants_api=require("@interfere/constants/api"),_interfere_types_releases_slug=require("@interfere/types/releases/slug"),_opentelemetry_context_async_hooks=require("@opentelemetry/context-async-hooks"),_opentelemetry_exporter_trace_otlp_http=require("@opentelemetry/exporter-trace-otlp-http"),_opentelemetry_resources=require("@opentelemetry/resources"),_opentelemetry_sdk_trace_base=require("@opentelemetry/sdk-trace-base"),_opentelemetry_sdk_trace_node=require("@opentelemetry/sdk-trace-node"),provider=null;function init(options={}){if(provider)return;let env=require_internal_env.readInterfereEnv();if(!env.publicKey)return;let releaseSlug=env.forceEnable?_interfere_types_releases_slug.LOCAL_DEV_RELEASE_SLUG:require_internal_release_slug.resolveReleaseSlug().slug;releaseSlug||console.warn("[interfere] No commit SHA available; server spans will ship without `release.slug`. Set `INTERFERE_SOURCE_ID` (or any of `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) on the runtime env."),provider=new _opentelemetry_sdk_trace_node.NodeTracerProvider({resource:(0,_opentelemetry_resources.resourceFromAttributes)({"service.name":options.serviceName??process.env.OTEL_SERVICE_NAME??`node-app`,...options.serviceNamespace?{"service.namespace":options.serviceNamespace}:{},"deployment.environment.name":env.nodeEnvironment??`unknown`,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/node`,"interfere.sdk.version":require_internal_version.PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}}),spanProcessors:[new _opentelemetry_sdk_trace_base.BatchSpanProcessor(new _opentelemetry_exporter_trace_otlp_http.OTLPTraceExporter({url:require_internal_url.withPublicKey(`${env.apiUrl}${_interfere_constants_api.API_PATHS.SINK}`,env.publicKey),headers:{"user-agent":require_internal_version.PRODUCER_VERSION,"x-interfere-producer-version":require_internal_version.PRODUCER_VERSION,...env.forceEnable?{"x-interfere-force-enable":`1`}:{}}}))]}),provider.register({contextManager:new _opentelemetry_context_async_hooks.AsyncLocalStorageContextManager().enable()}),require_internal_global_handlers.installGlobalHandlers({captureUncaughtException:options.captureUncaughtException??!0,captureUnhandledRejection:options.captureUnhandledRejection??!0,flush})}async function close(){provider&&=(require_internal_global_handlers.uninstallGlobalHandlers(),await provider.shutdown(),null)}async function flush(){provider&&await provider.forceFlush()}exports.close=close,exports.flush=flush,exports.init=init;
@@ -0,0 +1,62 @@
1
+ //#region src/init.d.ts
2
+ interface InitOptions {
3
+ /**
4
+ * Install a `process.on("uncaughtException")` listener that captures the
5
+ * error, flushes spans, and exits the process. Default `true`. Disable to
6
+ * use your own handler.
7
+ */
8
+ readonly captureUncaughtException?: boolean;
9
+ /**
10
+ * Install a `process.on("unhandledRejection")` listener that captures the
11
+ * rejection and flushes spans. Default `true`. Disable to use your own
12
+ * handler. Whether the process exits is governed by Node's
13
+ * `--unhandled-rejections` flag, not the SDK.
14
+ */
15
+ readonly captureUnhandledRejection?: boolean;
16
+ /**
17
+ * Sets the OTel `service.name` resource attribute — the identity spans
18
+ * are grouped by in the dashboard. Defaults to `OTEL_SERVICE_NAME` env
19
+ * var, then `"node-app"`. Use this to distinguish workers, crons, and
20
+ * other processes that share an `INTERFERE_PUBLIC_KEY`.
21
+ */
22
+ readonly serviceName?: string;
23
+ /**
24
+ * Sets the OTel `service.namespace` resource attribute. Optional; useful
25
+ * for grouping multiple services under one logical product surface
26
+ * (e.g. `"checkout"` across an API and worker fleet).
27
+ */
28
+ readonly serviceNamespace?: string;
29
+ }
30
+ /**
31
+ * Bootstraps the Interfere Node SDK: wires a private `NodeTracerProvider`
32
+ * whose spans export to the Interfere collector via OTLP/HTTP, registers an
33
+ * `AsyncLocalStorageContextManager` so user spans propagate across async
34
+ * boundaries.
35
+ *
36
+ * Idempotent — safe to call from multiple entry points; only the first call
37
+ * wires up.
38
+ *
39
+ * Bails silently when `INTERFERE_PUBLIC_KEY` is unset so dev runs without the
40
+ * SDK configured don't crash.
41
+ *
42
+ * The `release.slug` resource attribute is derived deterministically from
43
+ * the commit SHA (auto-detected from `INTERFERE_SOURCE_ID`,
44
+ * `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`, or `git rev-parse HEAD`) plus the
45
+ * public key — same algorithm used by `@interfere/next` and the browser SDKs,
46
+ * so spans from cohabiting frontends agree on release identity.
47
+ */
48
+ declare function init(options?: InitOptions): void;
49
+ /**
50
+ * Flushes any buffered spans and shuts down the tracer provider. Call from
51
+ * a server shutdown hook (`SIGTERM`, `process.on("beforeExit")`, framework
52
+ * lifecycle close) so in-flight error spans aren't lost on exit.
53
+ */
54
+ declare function close(): Promise<void>;
55
+ /**
56
+ * Forces buffered spans to be exported immediately. Useful in serverless
57
+ * runtimes where the process is suspended between requests.
58
+ */
59
+ declare function flush(): Promise<void>;
60
+ //#endregion
61
+ export { InitOptions, close, flush, init };
62
+ //# sourceMappingURL=init.d.cts.map
package/dist/init.d.mts CHANGED
@@ -17,7 +17,7 @@ interface InitOptions {
17
17
  * Sets the OTel `service.name` resource attribute — the identity spans
18
18
  * are grouped by in the dashboard. Defaults to `OTEL_SERVICE_NAME` env
19
19
  * var, then `"node-app"`. Use this to distinguish workers, crons, and
20
- * other processes that share an `INTERFERE_API_KEY`.
20
+ * other processes that share an `INTERFERE_PUBLIC_KEY`.
21
21
  */
22
22
  readonly serviceName?: string;
23
23
  /**
@@ -31,21 +31,21 @@ interface InitOptions {
31
31
  * Bootstraps the Interfere Node SDK: wires a private `NodeTracerProvider`
32
32
  * whose spans export to the Interfere collector via OTLP/HTTP, registers an
33
33
  * `AsyncLocalStorageContextManager` so user spans propagate across async
34
- * boundaries, and fetches the remote-config gate.
34
+ * boundaries.
35
35
  *
36
36
  * Idempotent — safe to call from multiple entry points; only the first call
37
37
  * wires up.
38
38
  *
39
- * Bails silently when `INTERFERE_API_KEY` is unset so dev runs without the
39
+ * Bails silently when `INTERFERE_PUBLIC_KEY` is unset so dev runs without the
40
40
  * SDK configured don't crash.
41
41
  *
42
42
  * The `release.slug` resource attribute is derived deterministically from
43
43
  * the commit SHA (auto-detected from `INTERFERE_SOURCE_ID`,
44
44
  * `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`, or `git rev-parse HEAD`) plus the
45
- * API key — same algorithm used by `@interfere/next` and the browser SDKs,
45
+ * public key — same algorithm used by `@interfere/next` and the browser SDKs,
46
46
  * so spans from cohabiting frontends agree on release identity.
47
47
  */
48
- declare function init(options?: InitOptions): Promise<void>;
48
+ declare function init(options?: InitOptions): void;
49
49
  /**
50
50
  * Flushes any buffered spans and shuts down the tracer provider. Call from
51
51
  * a server shutdown hook (`SIGTERM`, `process.on("beforeExit")`, framework
@@ -58,4 +58,5 @@ declare function close(): Promise<void>;
58
58
  */
59
59
  declare function flush(): Promise<void>;
60
60
  //#endregion
61
- export { InitOptions, close, flush, init };
61
+ export { InitOptions, close, flush, init };
62
+ //# sourceMappingURL=init.d.mts.map
package/dist/init.mjs CHANGED
@@ -1,87 +1,2 @@
1
- import { readInterfereEnv } from "./internal/env.mjs";
2
- import { fetchAndCacheRemoteConfig } from "./internal/remote-config.mjs";
3
- import { installGlobalHandlers, uninstallGlobalHandlers } from "./internal/global-handlers.mjs";
4
- import { resolveReleaseSlug } from "./internal/release-slug.mjs";
5
- import { PRODUCER_VERSION } from "./internal/version.mjs";
6
- import { API_PATHS } from "@interfere/constants/api";
7
- import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
8
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
9
- import { resourceFromAttributes } from "@opentelemetry/resources";
10
- import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
11
- import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
12
- //#region src/init.ts
13
- const DEFAULT_SERVICE_NAME = "node-app";
14
- let provider = null;
15
- /**
16
- * Bootstraps the Interfere Node SDK: wires a private `NodeTracerProvider`
17
- * whose spans export to the Interfere collector via OTLP/HTTP, registers an
18
- * `AsyncLocalStorageContextManager` so user spans propagate across async
19
- * boundaries, and fetches the remote-config gate.
20
- *
21
- * Idempotent — safe to call from multiple entry points; only the first call
22
- * wires up.
23
- *
24
- * Bails silently when `INTERFERE_API_KEY` is unset so dev runs without the
25
- * SDK configured don't crash.
26
- *
27
- * The `release.slug` resource attribute is derived deterministically from
28
- * the commit SHA (auto-detected from `INTERFERE_SOURCE_ID`,
29
- * `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`, or `git rev-parse HEAD`) plus the
30
- * API key — same algorithm used by `@interfere/next` and the browser SDKs,
31
- * so spans from cohabiting frontends agree on release identity.
32
- */
33
- async function init(options = {}) {
34
- if (provider) return;
35
- const env = readInterfereEnv();
36
- if (!env.apiKey) return;
37
- const releaseSlug = env.forceEnable ? "rel_0000000000000000" : resolveReleaseSlug().slug;
38
- if (!releaseSlug) console.warn("[interfere] No commit SHA available; server spans will ship without `release.slug`. Set `INTERFERE_SOURCE_ID` (or any of `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) on the runtime env.");
39
- provider = new NodeTracerProvider({
40
- resource: resourceFromAttributes({
41
- "service.name": options.serviceName ?? process.env["OTEL_SERVICE_NAME"] ?? DEFAULT_SERVICE_NAME,
42
- ...options.serviceNamespace ? { "service.namespace": options.serviceNamespace } : {},
43
- "deployment.environment.name": env.nodeEnvironment ?? "unknown",
44
- "telemetry.sdk.language": "nodejs",
45
- "interfere.sdk.name": "@interfere/node",
46
- "interfere.sdk.version": PRODUCER_VERSION,
47
- ...releaseSlug ? { "release.slug": releaseSlug } : {}
48
- }),
49
- spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter({
50
- url: `${env.apiUrl}${API_PATHS.SINK}`,
51
- headers: {
52
- "user-agent": PRODUCER_VERSION,
53
- "x-api-key": env.apiKey,
54
- "x-interfere-producer-version": PRODUCER_VERSION,
55
- ...env.forceEnable ? { "x-interfere-force-enable": "1" } : {}
56
- }
57
- }))]
58
- });
59
- provider.register({ contextManager: new AsyncLocalStorageContextManager().enable() });
60
- installGlobalHandlers({
61
- captureUncaughtException: options.captureUncaughtException ?? true,
62
- captureUnhandledRejection: options.captureUnhandledRejection ?? true,
63
- flush
64
- });
65
- await fetchAndCacheRemoteConfig();
66
- }
67
- /**
68
- * Flushes any buffered spans and shuts down the tracer provider. Call from
69
- * a server shutdown hook (`SIGTERM`, `process.on("beforeExit")`, framework
70
- * lifecycle close) so in-flight error spans aren't lost on exit.
71
- */
72
- async function close() {
73
- if (!provider) return;
74
- uninstallGlobalHandlers();
75
- await provider.shutdown();
76
- provider = null;
77
- }
78
- /**
79
- * Forces buffered spans to be exported immediately. Useful in serverless
80
- * runtimes where the process is suspended between requests.
81
- */
82
- async function flush() {
83
- if (!provider) return;
84
- await provider.forceFlush();
85
- }
86
- //#endregion
87
- export { close, flush, init };
1
+ import{readInterfereEnv}from"./internal/env.mjs";import{installGlobalHandlers,uninstallGlobalHandlers}from"./internal/global-handlers.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{withPublicKey}from"./internal/url.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{API_PATHS}from"@interfere/constants/api";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";let provider=null;function init(options={}){if(provider)return;let env=readInterfereEnv();if(!env.publicKey)return;let releaseSlug=env.forceEnable?LOCAL_DEV_RELEASE_SLUG:resolveReleaseSlug().slug;releaseSlug||console.warn("[interfere] No commit SHA available; server spans will ship without `release.slug`. Set `INTERFERE_SOURCE_ID` (or any of `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) on the runtime env."),provider=new NodeTracerProvider({resource:resourceFromAttributes({"service.name":options.serviceName??process.env.OTEL_SERVICE_NAME??`node-app`,...options.serviceNamespace?{"service.namespace":options.serviceNamespace}:{},"deployment.environment.name":env.nodeEnvironment??`unknown`,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/node`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}}),spanProcessors:[new BatchSpanProcessor(new OTLPTraceExporter({url:withPublicKey(`${env.apiUrl}${API_PATHS.SINK}`,env.publicKey),headers:{"user-agent":PRODUCER_VERSION,"x-interfere-producer-version":PRODUCER_VERSION,...env.forceEnable?{"x-interfere-force-enable":`1`}:{}}}))]}),provider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),installGlobalHandlers({captureUncaughtException:options.captureUncaughtException??!0,captureUnhandledRejection:options.captureUnhandledRejection??!0,flush})}async function close(){provider&&=(uninstallGlobalHandlers(),await provider.shutdown(),null)}async function flush(){provider&&await provider.forceFlush()}export{close,flush,init};
2
+ //# sourceMappingURL=init.mjs.map
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const seenErrors=new WeakSet;function isErrorCaptured(error){return error instanceof Error&&seenErrors.has(error)}function markErrorCaptured(error){error instanceof Error&&seenErrors.add(error)}exports.isErrorCaptured=isErrorCaptured,exports.markErrorCaptured=markErrorCaptured;
@@ -0,0 +1,6 @@
1
+ //#region src/internal/dedupe.d.ts
2
+ declare function isErrorCaptured(error: unknown): boolean;
3
+ declare function markErrorCaptured(error: unknown): void;
4
+ //#endregion
5
+ export { isErrorCaptured, markErrorCaptured };
6
+ //# sourceMappingURL=dedupe.d.cts.map
@@ -2,4 +2,5 @@
2
2
  declare function isErrorCaptured(error: unknown): boolean;
3
3
  declare function markErrorCaptured(error: unknown): void;
4
4
  //#endregion
5
- export { isErrorCaptured, markErrorCaptured };
5
+ export { isErrorCaptured, markErrorCaptured };
6
+ //# sourceMappingURL=dedupe.d.mts.map
@@ -1,11 +1,2 @@
1
- //#region src/internal/dedupe.ts
2
- const seenErrors = /* @__PURE__ */ new WeakSet();
3
- function isErrorCaptured(error) {
4
- return error instanceof Error && seenErrors.has(error);
5
- }
6
- function markErrorCaptured(error) {
7
- if (!(error instanceof Error)) return;
8
- seenErrors.add(error);
9
- }
10
- //#endregion
11
- export { isErrorCaptured, markErrorCaptured };
1
+ const seenErrors=new WeakSet;function isErrorCaptured(error){return error instanceof Error&&seenErrors.has(error)}function markErrorCaptured(error){error instanceof Error&&seenErrors.add(error)}export{isErrorCaptured,markErrorCaptured};
2
+ //# sourceMappingURL=dedupe.mjs.map
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let _interfere_constants_api=require("@interfere/constants/api"),_interfere_types_sdk_env=require("@interfere/types/sdk/env"),_interfere_types_sdk_runtime=require("@interfere/types/sdk/runtime");function isEnabledInEnvironment(){return!0}function readInterfereEnv(){let nodeEnvironment=(0,_interfere_types_sdk_runtime.normalizeEnv)(process.env.NODE_ENV);return{apiUrl:(0,_interfere_types_sdk_env.parseEnvValue)(process.env.INTERFERE_API_URL)??_interfere_constants_api.API_URL,forceEnable:process.env.INTERFERE_FORCE_ENABLE===`1`,nodeEnvironment,publicKey:(0,_interfere_types_sdk_env.parseEnvValue)(process.env.INTERFERE_PUBLIC_KEY)}}exports.isEnabledInEnvironment=isEnabledInEnvironment,exports.readInterfereEnv=readInterfereEnv;
@@ -0,0 +1,21 @@
1
+ import { Env } from "@interfere/types/sdk/runtime";
2
+
3
+ //#region src/internal/env.d.ts
4
+ interface InterfereEnv {
5
+ readonly apiUrl: string;
6
+ readonly forceEnable: boolean;
7
+ readonly nodeEnvironment: Env;
8
+ readonly publicKey: string | null;
9
+ }
10
+ /**
11
+ * Server SDKs always run end-to-end. There's no `NEXT_PUBLIC_*` force-enable
12
+ * escape hatch here — if `INTERFERE_PUBLIC_KEY` is set, the SDK is on. The Next
13
+ * SDK guards on `NODE_ENV=production || NEXT_PUBLIC_INTERFERE_FORCE_ENABLE`
14
+ * because `next dev` runs the SDK during development without a real release
15
+ * slug; that asymmetry doesn't apply to plain Node servers.
16
+ */
17
+ declare function isEnabledInEnvironment(): boolean;
18
+ declare function readInterfereEnv(): InterfereEnv;
19
+ //#endregion
20
+ export { InterfereEnv, isEnabledInEnvironment, readInterfereEnv };
21
+ //# sourceMappingURL=env.d.cts.map
@@ -2,14 +2,14 @@ import { Env } from "@interfere/types/sdk/runtime";
2
2
 
3
3
  //#region src/internal/env.d.ts
4
4
  interface InterfereEnv {
5
- readonly apiKey: string | null;
6
5
  readonly apiUrl: string;
7
6
  readonly forceEnable: boolean;
8
- readonly nodeEnvironment: Exclude<Env, null>;
7
+ readonly nodeEnvironment: Env;
8
+ readonly publicKey: string | null;
9
9
  }
10
10
  /**
11
11
  * Server SDKs always run end-to-end. There's no `NEXT_PUBLIC_*` force-enable
12
- * escape hatch here — if `INTERFERE_API_KEY` is set, the SDK is on. The Next
12
+ * escape hatch here — if `INTERFERE_PUBLIC_KEY` is set, the SDK is on. The Next
13
13
  * SDK guards on `NODE_ENV=production || NEXT_PUBLIC_INTERFERE_FORCE_ENABLE`
14
14
  * because `next dev` runs the SDK during development without a real release
15
15
  * slug; that asymmetry doesn't apply to plain Node servers.
@@ -17,4 +17,5 @@ interface InterfereEnv {
17
17
  declare function isEnabledInEnvironment(): boolean;
18
18
  declare function readInterfereEnv(): InterfereEnv;
19
19
  //#endregion
20
- export { InterfereEnv, isEnabledInEnvironment, readInterfereEnv };
20
+ export { InterfereEnv, isEnabledInEnvironment, readInterfereEnv };
21
+ //# sourceMappingURL=env.d.mts.map
@@ -1,25 +1,2 @@
1
- import { API_URL } from "@interfere/constants/api";
2
- import { parseEnvValue } from "@interfere/types/sdk/env";
3
- import { normalizeEnv } from "@interfere/types/sdk/runtime";
4
- //#region src/internal/env.ts
5
- /**
6
- * Server SDKs always run end-to-end. There's no `NEXT_PUBLIC_*` force-enable
7
- * escape hatch here — if `INTERFERE_API_KEY` is set, the SDK is on. The Next
8
- * SDK guards on `NODE_ENV=production || NEXT_PUBLIC_INTERFERE_FORCE_ENABLE`
9
- * because `next dev` runs the SDK during development without a real release
10
- * slug; that asymmetry doesn't apply to plain Node servers.
11
- */
12
- function isEnabledInEnvironment() {
13
- return true;
14
- }
15
- function readInterfereEnv() {
16
- const nodeEnvironment = normalizeEnv(process.env["NODE_ENV"]) ?? "production";
17
- return {
18
- apiKey: parseEnvValue(process.env["INTERFERE_API_KEY"]),
19
- apiUrl: parseEnvValue(process.env["INTERFERE_API_URL"]) ?? API_URL,
20
- forceEnable: process.env["INTERFERE_FORCE_ENABLE"] === "1",
21
- nodeEnvironment
22
- };
23
- }
24
- //#endregion
25
- export { isEnabledInEnvironment, readInterfereEnv };
1
+ import{API_URL}from"@interfere/constants/api";import{parseEnvValue}from"@interfere/types/sdk/env";import{normalizeEnv}from"@interfere/types/sdk/runtime";function isEnabledInEnvironment(){return!0}function readInterfereEnv(){let nodeEnvironment=normalizeEnv(process.env.NODE_ENV);return{apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,forceEnable:process.env.INTERFERE_FORCE_ENABLE===`1`,nodeEnvironment,publicKey:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY)}}export{isEnabledInEnvironment,readInterfereEnv};
2
+ //# sourceMappingURL=env.mjs.map
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const require_capture=require("../capture.cjs");let _interfere_types_sdk_errors=require("@interfere/types/sdk/errors"),uncaughtHandler=null,unhandledHandler=null;function installGlobalHandlers(options){options.captureUncaughtException&&uncaughtHandler===null&&(uncaughtHandler=error=>{handleUncaughtException(error,options.flush).catch(()=>void 0)},process.on(`uncaughtException`,uncaughtHandler)),options.captureUnhandledRejection&&unhandledHandler===null&&(unhandledHandler=reason=>{handleUnhandledRejection(reason,options.flush).catch(()=>void 0)},process.on(`unhandledRejection`,unhandledHandler))}function uninstallGlobalHandlers(){uncaughtHandler!==null&&(process.off(`uncaughtException`,uncaughtHandler),uncaughtHandler=null),unhandledHandler!==null&&(process.off(`unhandledRejection`,unhandledHandler),unhandledHandler=null)}async function handleUncaughtException(error,flush){await require_capture.captureError(error,void 0,{mechanism:{type:_interfere_types_sdk_errors.MECHANISM_TYPE.node.uncaughtException,handled:!1}}),await flushWithTimeout(flush),process.exit(1)}async function handleUnhandledRejection(reason,flush){await require_capture.captureError(reason,void 0,{mechanism:{type:_interfere_types_sdk_errors.MECHANISM_TYPE.node.unhandledRejection,handled:!1}}),await flushWithTimeout(flush)}function flushWithTimeout(flush){return Promise.race([flush(),new Promise(resolve=>{setTimeout(resolve,2e3)})])}exports.installGlobalHandlers=installGlobalHandlers,exports.uninstallGlobalHandlers=uninstallGlobalHandlers;
@@ -0,0 +1,26 @@
1
+ //#region src/internal/global-handlers.d.ts
2
+ /**
3
+ * Installs `process.on("uncaughtException")` and
4
+ * `process.on("unhandledRejection")` listeners that report to Interfere.
5
+ *
6
+ * Uncaught exceptions: captured, spans flushed (best-effort, 2s timeout),
7
+ * then `process.exit(1)`. Once an `uncaughtException` listener is
8
+ * registered, Node will not exit on its own — leaving the process alive
9
+ * after an uncaught error usually means a corrupted state, so we own the
10
+ * exit. Customers who need different exit semantics should opt out and
11
+ * register their own handler.
12
+ *
13
+ * Unhandled rejections: captured and flushed, but the SDK does not call
14
+ * `process.exit`. Whether unhandled rejections are fatal is governed by
15
+ * Node's `--unhandled-rejections` flag (default `throw` since v15) and we
16
+ * don't override that.
17
+ */
18
+ declare function installGlobalHandlers(options: {
19
+ readonly captureUncaughtException: boolean;
20
+ readonly captureUnhandledRejection: boolean;
21
+ readonly flush: () => Promise<void>;
22
+ }): void;
23
+ declare function uninstallGlobalHandlers(): void;
24
+ //#endregion
25
+ export { installGlobalHandlers, uninstallGlobalHandlers };
26
+ //# sourceMappingURL=global-handlers.d.cts.map
@@ -22,4 +22,5 @@ declare function installGlobalHandlers(options: {
22
22
  }): void;
23
23
  declare function uninstallGlobalHandlers(): void;
24
24
  //#endregion
25
- export { installGlobalHandlers, uninstallGlobalHandlers };
25
+ export { installGlobalHandlers, uninstallGlobalHandlers };
26
+ //# sourceMappingURL=global-handlers.d.mts.map
@@ -1,68 +1,2 @@
1
- import { captureError } from "../capture.mjs";
2
- import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
3
- //#region src/internal/global-handlers.ts
4
- const FLUSH_TIMEOUT_MS = 2e3;
5
- let uncaughtHandler = null;
6
- let unhandledHandler = null;
7
- /**
8
- * Installs `process.on("uncaughtException")` and
9
- * `process.on("unhandledRejection")` listeners that report to Interfere.
10
- *
11
- * Uncaught exceptions: captured, spans flushed (best-effort, 2s timeout),
12
- * then `process.exit(1)`. Once an `uncaughtException` listener is
13
- * registered, Node will not exit on its own — leaving the process alive
14
- * after an uncaught error usually means a corrupted state, so we own the
15
- * exit. Customers who need different exit semantics should opt out and
16
- * register their own handler.
17
- *
18
- * Unhandled rejections: captured and flushed, but the SDK does not call
19
- * `process.exit`. Whether unhandled rejections are fatal is governed by
20
- * Node's `--unhandled-rejections` flag (default `throw` since v15) and we
21
- * don't override that.
22
- */
23
- function installGlobalHandlers(options) {
24
- if (options.captureUncaughtException && uncaughtHandler === null) {
25
- uncaughtHandler = (error) => {
26
- handleUncaughtException(error, options.flush).catch(() => void 0);
27
- };
28
- process.on("uncaughtException", uncaughtHandler);
29
- }
30
- if (options.captureUnhandledRejection && unhandledHandler === null) {
31
- unhandledHandler = (reason) => {
32
- handleUnhandledRejection(reason, options.flush).catch(() => void 0);
33
- };
34
- process.on("unhandledRejection", unhandledHandler);
35
- }
36
- }
37
- function uninstallGlobalHandlers() {
38
- if (uncaughtHandler !== null) {
39
- process.off("uncaughtException", uncaughtHandler);
40
- uncaughtHandler = null;
41
- }
42
- if (unhandledHandler !== null) {
43
- process.off("unhandledRejection", unhandledHandler);
44
- unhandledHandler = null;
45
- }
46
- }
47
- async function handleUncaughtException(error, flush) {
48
- await captureError(error, void 0, { mechanism: {
49
- type: MECHANISM_TYPE.node.uncaughtException,
50
- handled: false
51
- } });
52
- await flushWithTimeout(flush);
53
- process.exit(1);
54
- }
55
- async function handleUnhandledRejection(reason, flush) {
56
- await captureError(reason, void 0, { mechanism: {
57
- type: MECHANISM_TYPE.node.unhandledRejection,
58
- handled: false
59
- } });
60
- await flushWithTimeout(flush);
61
- }
62
- function flushWithTimeout(flush) {
63
- return Promise.race([flush(), new Promise((resolve) => {
64
- setTimeout(resolve, FLUSH_TIMEOUT_MS);
65
- })]);
66
- }
67
- //#endregion
68
- export { installGlobalHandlers, uninstallGlobalHandlers };
1
+ import{captureError}from"../capture.mjs";import{MECHANISM_TYPE}from"@interfere/types/sdk/errors";let uncaughtHandler=null,unhandledHandler=null;function installGlobalHandlers(options){options.captureUncaughtException&&uncaughtHandler===null&&(uncaughtHandler=error=>{handleUncaughtException(error,options.flush).catch(()=>void 0)},process.on(`uncaughtException`,uncaughtHandler)),options.captureUnhandledRejection&&unhandledHandler===null&&(unhandledHandler=reason=>{handleUnhandledRejection(reason,options.flush).catch(()=>void 0)},process.on(`unhandledRejection`,unhandledHandler))}function uninstallGlobalHandlers(){uncaughtHandler!==null&&(process.off(`uncaughtException`,uncaughtHandler),uncaughtHandler=null),unhandledHandler!==null&&(process.off(`unhandledRejection`,unhandledHandler),unhandledHandler=null)}async function handleUncaughtException(error,flush){await captureError(error,void 0,{mechanism:{type:MECHANISM_TYPE.node.uncaughtException,handled:!1}}),await flushWithTimeout(flush),process.exit(1)}async function handleUnhandledRejection(reason,flush){await captureError(reason,void 0,{mechanism:{type:MECHANISM_TYPE.node.unhandledRejection,handled:!1}}),await flushWithTimeout(flush)}function flushWithTimeout(flush){return Promise.race([flush(),new Promise(resolve=>{setTimeout(resolve,2e3)})])}export{installGlobalHandlers,uninstallGlobalHandlers};
2
+ //# sourceMappingURL=global-handlers.mjs.map
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function normalizeRequest(request){if(request instanceof Request)return{method:normalizeMethod(request.method),path:normalizePath(request.url),headers:request.headers};if(typeof request!=`object`||!request)return null;let value=request;return{method:normalizeMethod(value.method),path:normalizePath(typeof value.path==`string`?value.path:toStringOrDefault(value.url)),headers:normalizeHeaders(value.headers)}}function normalizeMethod(value){if(typeof value!=`string`)return`GET`;let method=value.trim();return method.length>0?method.toUpperCase():`GET`}function normalizePath(value){if(value.length===0)return`/`;try{return new URL(value,`http://localhost`).pathname||`/`}catch{return value.startsWith(`/`)?value:`/${value}`}}function toStringOrDefault(value,fallback=`/`){if(typeof value!=`string`)return fallback;let trimmed=value.trim();return trimmed.length>0?trimmed:fallback}function normalizeHeaders(value){if(value instanceof Headers)return new Headers(value);if(typeof value!=`object`||!value)return new Headers;let headers=new Headers;for(let[key,headerValue]of Object.entries(value))if(headerValue!==void 0){if(Array.isArray(headerValue)){headers.set(key,headerValue.join(`, `));continue}headers.set(key,String(headerValue))}return headers}exports.TRACEPARENT_HEADER=`traceparent`,exports.normalizeRequest=normalizeRequest;
@@ -0,0 +1,16 @@
1
+ import { NormalizedRequest } from "./types.cjs";
2
+
3
+ //#region src/internal/normalize-request.d.ts
4
+ declare const TRACEPARENT_HEADER = "traceparent";
5
+ /**
6
+ * Coerces a request-shaped value into the headers/method/path triple used
7
+ * by envelope construction. Handles WHATWG `Request`, Node
8
+ * `IncomingMessage`-like objects, and plain object literals.
9
+ *
10
+ * Returns `null` only for non-objects; otherwise produces a best-effort
11
+ * `NormalizedRequest` with sensible defaults.
12
+ */
13
+ declare function normalizeRequest(request: unknown): NormalizedRequest | null;
14
+ //#endregion
15
+ export { TRACEPARENT_HEADER, normalizeRequest };
16
+ //# sourceMappingURL=normalize-request.d.cts.map
@@ -12,4 +12,5 @@ declare const TRACEPARENT_HEADER = "traceparent";
12
12
  */
13
13
  declare function normalizeRequest(request: unknown): NormalizedRequest | null;
14
14
  //#endregion
15
- export { TRACEPARENT_HEADER, normalizeRequest };
15
+ export { TRACEPARENT_HEADER, normalizeRequest };
16
+ //# sourceMappingURL=normalize-request.d.mts.map
@@ -1,58 +1,2 @@
1
- //#region src/internal/normalize-request.ts
2
- const TRACEPARENT_HEADER = "traceparent";
3
- /**
4
- * Coerces a request-shaped value into the headers/method/path triple used
5
- * by envelope construction. Handles WHATWG `Request`, Node
6
- * `IncomingMessage`-like objects, and plain object literals.
7
- *
8
- * Returns `null` only for non-objects; otherwise produces a best-effort
9
- * `NormalizedRequest` with sensible defaults.
10
- */
11
- function normalizeRequest(request) {
12
- if (request instanceof Request) return {
13
- method: normalizeMethod(request.method),
14
- path: normalizePath(request.url),
15
- headers: request.headers
16
- };
17
- if (typeof request !== "object" || request === null) return null;
18
- const value = request;
19
- return {
20
- method: normalizeMethod(value.method),
21
- path: normalizePath(typeof value.path === "string" ? value.path : toStringOrDefault(value.url)),
22
- headers: normalizeHeaders(value.headers)
23
- };
24
- }
25
- function normalizeMethod(value) {
26
- if (typeof value !== "string") return "GET";
27
- const method = value.trim();
28
- return method.length > 0 ? method.toUpperCase() : "GET";
29
- }
30
- function normalizePath(value) {
31
- if (value.length === 0) return "/";
32
- try {
33
- return new URL(value, "http://localhost").pathname || "/";
34
- } catch {
35
- return value.startsWith("/") ? value : `/${value}`;
36
- }
37
- }
38
- function toStringOrDefault(value, fallback = "/") {
39
- if (typeof value !== "string") return fallback;
40
- const trimmed = value.trim();
41
- return trimmed.length > 0 ? trimmed : fallback;
42
- }
43
- function normalizeHeaders(value) {
44
- if (value instanceof Headers) return new Headers(value);
45
- if (typeof value !== "object" || value === null) return new Headers();
46
- const headers = new Headers();
47
- for (const [key, headerValue] of Object.entries(value)) {
48
- if (headerValue === void 0) continue;
49
- if (Array.isArray(headerValue)) {
50
- headers.set(key, headerValue.join(", "));
51
- continue;
52
- }
53
- headers.set(key, String(headerValue));
54
- }
55
- return headers;
56
- }
57
- //#endregion
58
- export { TRACEPARENT_HEADER, normalizeRequest };
1
+ const TRACEPARENT_HEADER=`traceparent`;function normalizeRequest(request){if(request instanceof Request)return{method:normalizeMethod(request.method),path:normalizePath(request.url),headers:request.headers};if(typeof request!=`object`||!request)return null;let value=request;return{method:normalizeMethod(value.method),path:normalizePath(typeof value.path==`string`?value.path:toStringOrDefault(value.url)),headers:normalizeHeaders(value.headers)}}function normalizeMethod(value){if(typeof value!=`string`)return`GET`;let method=value.trim();return method.length>0?method.toUpperCase():`GET`}function normalizePath(value){if(value.length===0)return`/`;try{return new URL(value,`http://localhost`).pathname||`/`}catch{return value.startsWith(`/`)?value:`/${value}`}}function toStringOrDefault(value,fallback=`/`){if(typeof value!=`string`)return fallback;let trimmed=value.trim();return trimmed.length>0?trimmed:fallback}function normalizeHeaders(value){if(value instanceof Headers)return new Headers(value);if(typeof value!=`object`||!value)return new Headers;let headers=new Headers;for(let[key,headerValue]of Object.entries(value))if(headerValue!==void 0){if(Array.isArray(headerValue)){headers.set(key,headerValue.join(`, `));continue}headers.set(key,String(headerValue))}return headers}export{TRACEPARENT_HEADER,normalizeRequest};
2
+ //# sourceMappingURL=normalize-request.mjs.map
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let _interfere_types_sdk_env=require("@interfere/types/sdk/env"),_interfere_types_releases_slug=require("@interfere/types/releases/slug"),_interfere_types_integrations=require("@interfere/types/integrations"),node_child_process=require("node:child_process");function runGitCommand(command){try{let output=(0,node_child_process.execSync)(command,{encoding:`utf8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim();return output.length>0?output:null}catch{return null}}function resolveCommitSha(){return(0,_interfere_types_sdk_env.readFirstEnvValue)(process.env,_interfere_types_integrations.releaseSourceIdEnvKeys)??runGitCommand(`git rev-parse HEAD`)}function resolveReleaseSlug(){let commitSha=resolveCommitSha();return{commitSha,slug:commitSha?(0,_interfere_types_releases_slug.deriveReleaseSlug)(commitSha):null}}exports.resolveCommitSha=resolveCommitSha,exports.resolveReleaseSlug=resolveReleaseSlug;
@@ -0,0 +1,9 @@
1
+ //#region src/internal/release-slug.d.ts
2
+ declare function resolveCommitSha(): string | null;
3
+ declare function resolveReleaseSlug(): {
4
+ commitSha: string | null;
5
+ slug: string | null;
6
+ };
7
+ //#endregion
8
+ export { resolveCommitSha, resolveReleaseSlug };
9
+ //# sourceMappingURL=release-slug.d.cts.map
@@ -5,4 +5,5 @@ declare function resolveReleaseSlug(): {
5
5
  slug: string | null;
6
6
  };
7
7
  //#endregion
8
- export { resolveCommitSha, resolveReleaseSlug };
8
+ export { resolveCommitSha, resolveReleaseSlug };
9
+ //# sourceMappingURL=release-slug.d.mts.map
@@ -1,32 +1,2 @@
1
- import { readFirstEnvValue } from "@interfere/types/sdk/env";
2
- import { releaseSourceIdEnvKeys } from "@interfere/types/integrations";
3
- import { deriveReleaseSlug } from "@interfere/types/releases/slug";
4
- import { execSync } from "node:child_process";
5
- //#region src/internal/release-slug.ts
6
- function runGitCommand(command) {
7
- try {
8
- const output = execSync(command, {
9
- encoding: "utf8",
10
- stdio: [
11
- "ignore",
12
- "pipe",
13
- "ignore"
14
- ]
15
- }).trim();
16
- return output.length > 0 ? output : null;
17
- } catch {
18
- return null;
19
- }
20
- }
21
- function resolveCommitSha() {
22
- return readFirstEnvValue(process.env, releaseSourceIdEnvKeys) ?? runGitCommand("git rev-parse HEAD");
23
- }
24
- function resolveReleaseSlug() {
25
- const commitSha = resolveCommitSha();
26
- return {
27
- commitSha,
28
- slug: commitSha ? deriveReleaseSlug(commitSha) : null
29
- };
30
- }
31
- //#endregion
32
- export { resolveCommitSha, resolveReleaseSlug };
1
+ import{readFirstEnvValue}from"@interfere/types/sdk/env";import{deriveReleaseSlug}from"@interfere/types/releases/slug";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";import{execSync}from"node:child_process";function runGitCommand(command){try{let output=execSync(command,{encoding:`utf8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim();return output.length>0?output:null}catch{return null}}function resolveCommitSha(){return readFirstEnvValue(process.env,releaseSourceIdEnvKeys)??runGitCommand(`git rev-parse HEAD`)}function resolveReleaseSlug(){let commitSha=resolveCommitSha();return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveCommitSha,resolveReleaseSlug};
2
+ //# sourceMappingURL=release-slug.mjs.map
File without changes
@@ -0,0 +1,17 @@
1
+ import { NodeContext } from "@interfere/types/sdk/plugins/context/node";
2
+ import { ErrorMechanism } from "@interfere/types/sdk/plugins/payload/errors";
3
+
4
+ //#region src/internal/types.d.ts
5
+ interface CaptureErrorContext {
6
+ readonly mechanism?: ErrorMechanism;
7
+ readonly node?: Omit<NodeContext, "runtime">;
8
+ readonly traceparent?: string;
9
+ }
10
+ interface NormalizedRequest {
11
+ readonly headers: Headers;
12
+ readonly method: string;
13
+ readonly path: string;
14
+ }
15
+ //#endregion
16
+ export { CaptureErrorContext, NormalizedRequest };
17
+ //# sourceMappingURL=types.d.cts.map
@@ -13,4 +13,5 @@ interface NormalizedRequest {
13
13
  readonly path: string;
14
14
  }
15
15
  //#endregion
16
- export { CaptureErrorContext, NormalizedRequest };
16
+ export { CaptureErrorContext, NormalizedRequest };
17
+ //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- export {};
1
+ export{};
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function withPublicKey(url,publicKey){let next=new URL(url);return next.searchParams.set(`pk`,publicKey),next.toString()}exports.withPublicKey=withPublicKey;
@@ -0,0 +1,5 @@
1
+ //#region src/internal/url.d.ts
2
+ declare function withPublicKey(url: string, publicKey: string): string;
3
+ //#endregion
4
+ export { withPublicKey };
5
+ //# sourceMappingURL=url.d.cts.map
@@ -0,0 +1,5 @@
1
+ //#region src/internal/url.d.ts
2
+ declare function withPublicKey(url: string, publicKey: string): string;
3
+ //#endregion
4
+ export { withPublicKey };
5
+ //# sourceMappingURL=url.d.mts.map
@@ -0,0 +1,2 @@
1
+ function withPublicKey(url,publicKey){let next=new URL(url);return next.searchParams.set(`pk`,publicKey),next.toString()}export{withPublicKey};
2
+ //# sourceMappingURL=url.mjs.map
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const require_package=require("../package.cjs"),PRODUCER_VERSION=`${require_package.name}@${require_package.version}`;exports.PRODUCER_VERSION=PRODUCER_VERSION;
@@ -0,0 +1,5 @@
1
+ //#region src/internal/version.d.ts
2
+ declare const PRODUCER_VERSION: string;
3
+ //#endregion
4
+ export { PRODUCER_VERSION };
5
+ //# sourceMappingURL=version.d.cts.map
@@ -1,4 +1,5 @@
1
1
  //#region src/internal/version.d.ts
2
2
  declare const PRODUCER_VERSION: string;
3
3
  //#endregion
4
- export { PRODUCER_VERSION };
4
+ export { PRODUCER_VERSION };
5
+ //# sourceMappingURL=version.d.mts.map
@@ -1,5 +1,2 @@
1
- import { name, version } from "../package.mjs";
2
- //#region src/internal/version.ts
3
- const PRODUCER_VERSION = `${name}@${version}`;
4
- //#endregion
5
- export { PRODUCER_VERSION };
1
+ import{name,version}from"../package.mjs";const PRODUCER_VERSION=`${name}@${version}`;export{PRODUCER_VERSION};
2
+ //# sourceMappingURL=version.mjs.map
@@ -0,0 +1 @@
1
+ var name=`@interfere/node`,version=`0.0.1-canary.2`;Object.defineProperty(exports,"name",{enumerable:!0,get:function(){return name}}),Object.defineProperty(exports,"version",{enumerable:!0,get:function(){return version}});
package/dist/package.mjs CHANGED
@@ -1,5 +1,2 @@
1
- //#region package.json
2
- var name = "@interfere/node";
3
- var version = "0.0.1-canary.0";
4
- //#endregion
5
- export { name, version };
1
+ var name=`@interfere/node`,version=`0.0.1-canary.2`;export{name,version};
2
+ //# sourceMappingURL=package.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/node",
3
- "version": "0.0.1-canary.0",
3
+ "version": "0.0.1-canary.2",
4
4
  "license": "MIT",
5
5
  "description": "Node.js SDK for Interfere. Error tracking and OTel instrumentation for any Node server.",
6
6
  "keywords": [
@@ -21,30 +21,54 @@
21
21
  "directory": "src/packages/public/node"
22
22
  },
23
23
  "files": [
24
- "dist"
24
+ "dist/**/*.cjs",
25
+ "dist/**/*.mjs",
26
+ "dist/**/*.d.cts",
27
+ "dist/**/*.d.mts",
28
+ "README.md"
25
29
  ],
26
30
  "type": "module",
31
+ "main": "./dist/index.cjs",
32
+ "module": "./dist/index.mjs",
33
+ "types": "./dist/index.d.cts",
27
34
  "exports": {
28
35
  ".": {
29
- "@source": "./src/index.ts",
30
- "types": "./dist/index.d.mts",
31
- "default": "./dist/index.mjs"
36
+ "monorepo": "./src/index.ts",
37
+ "import": {
38
+ "types": "./dist/index.d.mts",
39
+ "default": "./dist/index.mjs"
40
+ },
41
+ "require": {
42
+ "types": "./dist/index.d.cts",
43
+ "default": "./dist/index.cjs"
44
+ }
32
45
  },
33
46
  "./init": {
34
- "@source": "./src/init.ts",
35
- "types": "./dist/init.d.mts",
36
- "default": "./dist/init.mjs"
47
+ "monorepo": "./src/init.ts",
48
+ "import": {
49
+ "types": "./dist/init.d.mts",
50
+ "default": "./dist/init.mjs"
51
+ },
52
+ "require": {
53
+ "types": "./dist/init.d.cts",
54
+ "default": "./dist/init.cjs"
55
+ }
37
56
  },
38
57
  "./capture": {
39
- "@source": "./src/capture.ts",
40
- "types": "./dist/capture.d.mts",
41
- "default": "./dist/capture.mjs"
58
+ "monorepo": "./src/capture.ts",
59
+ "import": {
60
+ "types": "./dist/capture.d.mts",
61
+ "default": "./dist/capture.mjs"
62
+ },
63
+ "require": {
64
+ "types": "./dist/capture.d.cts",
65
+ "default": "./dist/capture.cjs"
66
+ }
42
67
  }
43
68
  },
44
69
  "sideEffects": false,
45
70
  "publishConfig": {
46
- "access": "public",
47
- "tag": "canary"
71
+ "access": "public"
48
72
  },
49
73
  "scripts": {
50
74
  "build": "tsdown",
@@ -52,11 +76,11 @@
52
76
  "typecheck": "tsc --noEmit --incremental"
53
77
  },
54
78
  "dependencies": {
55
- "@interfere/constants": "^9.0.1",
56
- "@interfere/types": "^9.0.0",
79
+ "@interfere/constants": "^9.0.2-canary.0",
80
+ "@interfere/types": "^9.0.3-canary.0",
57
81
  "@opentelemetry/api": "^1.9.1",
58
82
  "@opentelemetry/context-async-hooks": "^2.7.1",
59
- "@opentelemetry/exporter-trace-otlp-http": "^0.217.0",
83
+ "@opentelemetry/exporter-trace-otlp-http": "^0.218.0",
60
84
  "@opentelemetry/resources": "^2.7.0",
61
85
  "@opentelemetry/sdk-trace-base": "^2.7.0",
62
86
  "@opentelemetry/sdk-trace-node": "^2.7.1",
@@ -1 +0,0 @@
1
- {"version":3,"file":"capture.d.mts","names":[],"sources":["../src/capture.ts"],"mappings":";;;;AAwCA;;;;;;;;;;AA8EA;;;;;;;iBA9EgB,YAAA,CACd,KAAA,WACA,OAAA,YACA,YAAA,GAAe,mBAAA;;;AAwFjB;iBAbgB,cAAA,CACd,OAAA,UACA,OAAA,YACA,YAAA,GAAe,mBAAA;;;;;;iBAUK,QAAA,GAAA,CACpB,IAAA,UACA,EAAA,QAAU,OAAA,CAAQ,CAAA,IAAK,CAAA,GACtB,OAAA,CAAQ,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"capture.mjs","names":[],"sources":["../src/capture.ts"],"sourcesContent":["import { MECHANISM_TYPE, toError } from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { context, SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\n\nimport { isErrorCaptured, markErrorCaptured } from \"./internal/dedupe.js\";\nimport { isEnabledInEnvironment } from \"./internal/env.js\";\nimport { normalizeRequest } from \"./internal/normalize-request.js\";\nimport { isPluginEnabled } from \"./internal/remote-config.js\";\nimport type { CaptureErrorContext } from \"./internal/types.js\";\n\nexport type { CaptureErrorContext } from \"./internal/types.js\";\n\nconst TRACER_NAME = \"@interfere/node\";\nconst ERROR_SPAN_NAME = \"interfere.error.captured\";\nconst SESSION_HEADER = \"x-interfere-session\";\n\nconst DEFAULT_ERROR_MECHANISM: ErrorMechanism = {\n type: MECHANISM_TYPE.node.captureError,\n handled: true,\n};\n\n/**\n * Reports an error to Interfere. Safe to call from anywhere — no-op when the\n * SDK is not configured (`INTERFERE_API_KEY` unset) or when the `errors`\n * plugin is disabled by remote config.\n *\n * Internally opens a short OTel span and records the error as an `exception`\n * event on it. The span ships through the same `OTLPTraceExporter` wired up\n * in `init()`, so error capture inherits the OTel transport (batch, retry,\n * traceparent propagation) without a separate fetch path. The collector's\n * `mapTracesToSpanEvents` lifts the OTel-semconv `exception.*` attrs plus\n * our `interfere.exception.*` taxonomy into the `span_events` datasource.\n *\n * Pass the request (a `Request`, a Node `IncomingMessage`-like object, or\n * any object with `headers`/`method`/`url`) so the span carries request\n * context. If there's an active OTel context (e.g. an HTTP handler span),\n * the exception attaches to it as a child; otherwise it becomes a\n * root span.\n */\nexport function captureError(\n error: unknown,\n request?: unknown,\n errorContext?: CaptureErrorContext\n): void {\n if (!isEnabledInEnvironment()) {\n return;\n }\n\n if (!isPluginEnabled(\"errors\")) {\n return;\n }\n\n if (isErrorCaptured(error)) {\n return;\n }\n markErrorCaptured(error);\n\n const err = toError(error);\n const normalizedRequest = normalizeRequest(request);\n const mechanism = errorContext?.mechanism ?? DEFAULT_ERROR_MECHANISM;\n\n const tracer = trace.getTracer(TRACER_NAME);\n const span = tracer.startSpan(\n ERROR_SPAN_NAME,\n { kind: SpanKind.INTERNAL },\n context.active()\n );\n\n // Request context as span attributes (OTel HTTP semconv). Prefer an\n // explicit `errorContext.node` payload over the normalized request when\n // both are present — callers passing `node` are typically frameworks\n // (Next.js error handlers) that already have the canonical values.\n const requestMethod =\n errorContext?.node?.requestMethod ?? normalizedRequest?.method ?? null;\n if (requestMethod) {\n span.setAttribute(\"http.request.method\", requestMethod);\n }\n const requestPath =\n errorContext?.node?.requestPath ?? normalizedRequest?.path ?? null;\n if (requestPath) {\n span.setAttribute(\"url.path\", requestPath);\n }\n\n // Session header is the legacy bridge: browser/Next forwards\n // `x-interfere-session` so the dashboard can stitch server errors to a\n // user session. Lift it as a span attribute so the mapper can fold it in.\n const sessionId = normalizedRequest?.headers.get(SESSION_HEADER);\n if (sessionId) {\n span.setAttribute(\"interfere.session.id\", sessionId);\n }\n\n // Exception event — OTel semconv naming (`exception.{type,message,\n // stacktrace}`) plus our taxonomy (`interfere.exception.{mechanism,\n // handled}`, `interfere.error.digest`). `mapTracesToSpanEvents` in the\n // collector lifts both sets into typed columns on the `span_events` row.\n const eventAttrs: Record<string, string | boolean> = {\n \"exception.type\": err.name,\n \"exception.message\": err.message,\n \"interfere.exception.mechanism\": mechanism.type,\n \"interfere.exception.handled\": mechanism.handled,\n };\n if (err.stack) {\n eventAttrs[\"exception.stacktrace\"] = err.stack;\n }\n const digest = errorContext?.node?.errorDigest;\n if (digest) {\n eventAttrs[\"interfere.error.digest\"] = digest;\n }\n\n span.addEvent(\"exception\", eventAttrs);\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.end();\n}\n\n/**\n * Reports a string message as an error event.\n */\nexport function captureMessage(\n message: string,\n request?: unknown,\n errorContext?: CaptureErrorContext\n): void {\n captureError(new Error(message), request, errorContext);\n}\n\n/**\n * Wraps a function in an OTel span. Thrown errors are recorded on the span\n * and rethrown. Without an active provider (i.e. before `init()`), still\n * runs `fn` but produces no telemetry.\n */\nexport async function withSpan<T>(\n name: string,\n fn: () => Promise<T> | T\n): Promise<T> {\n const tracer = trace.getTracer(TRACER_NAME);\n const span = tracer.startSpan(name);\n try {\n return await context.with(trace.setSpan(context.active(), span), fn);\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n } finally {\n span.end();\n }\n}\n"],"mappings":";;;;;;;AAaA,MAAM,cAAc;AACpB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AAEvB,MAAM,0BAA0C;CAC9C,MAAM,eAAe,KAAK;CAC1B,SAAS;CACV;;;;;;;;;;;;;;;;;;;AAoBD,SAAgB,aACd,OACA,SACA,cACM;CACN,IAAI,CAAC,wBAAwB,EAC3B;CAGF,IAAI,CAAC,gBAAgB,SAAS,EAC5B;CAGF,IAAI,gBAAgB,MAAM,EACxB;CAEF,kBAAkB,MAAM;CAExB,MAAM,MAAM,QAAQ,MAAM;CAC1B,MAAM,oBAAoB,iBAAiB,QAAQ;CACnD,MAAM,YAAY,cAAc,aAAa;CAG7C,MAAM,OADS,MAAM,UAAU,YACZ,CAAC,UAClB,iBACA,EAAE,MAAM,SAAS,UAAU,EAC3B,QAAQ,QAAQ,CACjB;CAMD,MAAM,gBACJ,cAAc,MAAM,iBAAiB,mBAAmB,UAAU;CACpE,IAAI,eACF,KAAK,aAAa,uBAAuB,cAAc;CAEzD,MAAM,cACJ,cAAc,MAAM,eAAe,mBAAmB,QAAQ;CAChE,IAAI,aACF,KAAK,aAAa,YAAY,YAAY;CAM5C,MAAM,YAAY,mBAAmB,QAAQ,IAAI,eAAe;CAChE,IAAI,WACF,KAAK,aAAa,wBAAwB,UAAU;CAOtD,MAAM,aAA+C;EACnD,kBAAkB,IAAI;EACtB,qBAAqB,IAAI;EACzB,iCAAiC,UAAU;EAC3C,+BAA+B,UAAU;EAC1C;CACD,IAAI,IAAI,OACN,WAAW,0BAA0B,IAAI;CAE3C,MAAM,SAAS,cAAc,MAAM;CACnC,IAAI,QACF,WAAW,4BAA4B;CAGzC,KAAK,SAAS,aAAa,WAAW;CACtC,KAAK,UAAU,EAAE,MAAM,eAAe,OAAO,CAAC;CAC9C,KAAK,KAAK;;;;;AAMZ,SAAgB,eACd,SACA,SACA,cACM;CACN,aAAa,IAAI,MAAM,QAAQ,EAAE,SAAS,aAAa;;;;;;;AAQzD,eAAsB,SACpB,MACA,IACY;CAEZ,MAAM,OADS,MAAM,UAAU,YACZ,CAAC,UAAU,KAAK;CACnC,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,MAAM,QAAQ,QAAQ,QAAQ,EAAE,KAAK,EAAE,GAAG;UAC7D,OAAO;EACd,KAAK,gBAAgB,MAAe;EACpC,KAAK,UAAU,EAAE,MAAM,eAAe,OAAO,CAAC;EAC9C,MAAM;WACE;EACR,KAAK,KAAK"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"init.d.mts","names":[],"sources":["../src/init.ts"],"mappings":";UAqBiB,WAAA;EAAA;;;;;EAAA,SAMN,wBAAA;EAcA;;;;AA2BX;;EA3BW,SAPA,yBAAA;EAkCmD;;;;;;EAAA,SA3BnD,WAAA;EA4GgB;;;;AAa3B;EAb2B,SAtGhB,gBAAA;AAAA;;;;;;;;;;;;;;;;;;;iBAqBW,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,OAAA;;;;;;iBAiFjC,KAAA,CAAA,GAAS,OAAA;;;;;iBAaT,KAAA,CAAA,GAAS,OAAA"}
package/dist/init.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"init.mjs","names":[],"sources":["../src/init.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\n\nimport { AsyncLocalStorageContextManager } from \"@opentelemetry/context-async-hooks\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { BatchSpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\n\nimport { readInterfereEnv } from \"./internal/env.js\";\nimport {\n installGlobalHandlers,\n uninstallGlobalHandlers,\n} from \"./internal/global-handlers.js\";\nimport { resolveReleaseSlug } from \"./internal/release-slug.js\";\nimport { fetchAndCacheRemoteConfig } from \"./internal/remote-config.js\";\nimport { PRODUCER_VERSION } from \"./internal/version.js\";\n\nconst DEFAULT_SERVICE_NAME = \"node-app\";\n\nlet provider: NodeTracerProvider | null = null;\n\nexport interface InitOptions {\n /**\n * Install a `process.on(\"uncaughtException\")` listener that captures the\n * error, flushes spans, and exits the process. Default `true`. Disable to\n * use your own handler.\n */\n readonly captureUncaughtException?: boolean;\n /**\n * Install a `process.on(\"unhandledRejection\")` listener that captures the\n * rejection and flushes spans. Default `true`. Disable to use your own\n * handler. Whether the process exits is governed by Node's\n * `--unhandled-rejections` flag, not the SDK.\n */\n readonly captureUnhandledRejection?: boolean;\n /**\n * Sets the OTel `service.name` resource attribute — the identity spans\n * are grouped by in the dashboard. Defaults to `OTEL_SERVICE_NAME` env\n * var, then `\"node-app\"`. Use this to distinguish workers, crons, and\n * other processes that share an `INTERFERE_API_KEY`.\n */\n readonly serviceName?: string;\n /**\n * Sets the OTel `service.namespace` resource attribute. Optional; useful\n * for grouping multiple services under one logical product surface\n * (e.g. `\"checkout\"` across an API and worker fleet).\n */\n readonly serviceNamespace?: string;\n}\n\n/**\n * Bootstraps the Interfere Node SDK: wires a private `NodeTracerProvider`\n * whose spans export to the Interfere collector via OTLP/HTTP, registers an\n * `AsyncLocalStorageContextManager` so user spans propagate across async\n * boundaries, and fetches the remote-config gate.\n *\n * Idempotent — safe to call from multiple entry points; only the first call\n * wires up.\n *\n * Bails silently when `INTERFERE_API_KEY` is unset so dev runs without the\n * SDK configured don't crash.\n *\n * The `release.slug` resource attribute is derived deterministically from\n * the commit SHA (auto-detected from `INTERFERE_SOURCE_ID`,\n * `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`, or `git rev-parse HEAD`) plus the\n * API key — same algorithm used by `@interfere/next` and the browser SDKs,\n * so spans from cohabiting frontends agree on release identity.\n */\nexport async function init(options: InitOptions = {}): Promise<void> {\n if (provider) {\n return;\n }\n\n const env = readInterfereEnv();\n if (!env.apiKey) {\n return;\n }\n\n // Force-enable swaps in the seeded `rel_local` slug so dev runs pass the\n // collector's preflight gate without uploading source maps. Prod\n // collectors drop force-enabled batches with a 2xx, so this header is\n // safe to ship in dev images that escape to prod by mistake. Same\n // pattern as `@interfere/next/config.ts` for `next dev`.\n const releaseSlug = env.forceEnable\n ? \"rel_0000000000000000\"\n : resolveReleaseSlug().slug;\n\n if (!releaseSlug) {\n console.warn(\n \"[interfere] No commit SHA available; server spans will ship without `release.slug`. Set `INTERFERE_SOURCE_ID` (or any of `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) on the runtime env.\"\n );\n }\n\n const serviceName =\n options.serviceName ??\n process.env[\"OTEL_SERVICE_NAME\"] ??\n DEFAULT_SERVICE_NAME;\n\n const resource = resourceFromAttributes({\n \"service.name\": serviceName,\n ...(options.serviceNamespace\n ? { \"service.namespace\": options.serviceNamespace }\n : {}),\n \"deployment.environment.name\": env.nodeEnvironment ?? \"unknown\",\n \"telemetry.sdk.language\": \"nodejs\",\n \"interfere.sdk.name\": \"@interfere/node\",\n \"interfere.sdk.version\": PRODUCER_VERSION,\n ...(releaseSlug ? { \"release.slug\": releaseSlug } : {}),\n });\n\n const exporter = new OTLPTraceExporter({\n // Single opaque sink endpoint shared with browser/Next exporters; see\n // `@interfere/react/internal/otel/exporter` for the rationale.\n url: `${env.apiUrl}${API_PATHS.SINK}`,\n headers: {\n // The collector's bot filter (`v2/index.ts`) silently 202-drops\n // requests whose UA matches `isBot()`. Bun/Node's default UA gets\n // flagged, so SDKs self-identify; the bypass regex\n // `^@interfere/[a-z][a-z-]*@` lives in `collector/lib/bot-filter.ts`.\n \"user-agent\": PRODUCER_VERSION,\n \"x-api-key\": env.apiKey,\n \"x-interfere-producer-version\": PRODUCER_VERSION,\n ...(env.forceEnable ? { \"x-interfere-force-enable\": \"1\" } : {}),\n },\n });\n\n provider = new NodeTracerProvider({\n resource,\n spanProcessors: [new BatchSpanProcessor(exporter)],\n });\n\n provider.register({\n contextManager: new AsyncLocalStorageContextManager().enable(),\n });\n\n installGlobalHandlers({\n captureUncaughtException: options.captureUncaughtException ?? true,\n captureUnhandledRejection: options.captureUnhandledRejection ?? true,\n flush,\n });\n\n await fetchAndCacheRemoteConfig();\n}\n\n/**\n * Flushes any buffered spans and shuts down the tracer provider. Call from\n * a server shutdown hook (`SIGTERM`, `process.on(\"beforeExit\")`, framework\n * lifecycle close) so in-flight error spans aren't lost on exit.\n */\nexport async function close(): Promise<void> {\n if (!provider) {\n return;\n }\n uninstallGlobalHandlers();\n await provider.shutdown();\n provider = null;\n}\n\n/**\n * Forces buffered spans to be exported immediately. Useful in serverless\n * runtimes where the process is suspended between requests.\n */\nexport async function flush(): Promise<void> {\n if (!provider) {\n return;\n }\n await provider.forceFlush();\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,MAAM,uBAAuB;AAE7B,IAAI,WAAsC;;;;;;;;;;;;;;;;;;;AAiD1C,eAAsB,KAAK,UAAuB,EAAE,EAAiB;CACnE,IAAI,UACF;CAGF,MAAM,MAAM,kBAAkB;CAC9B,IAAI,CAAC,IAAI,QACP;CAQF,MAAM,cAAc,IAAI,cACpB,yBACA,oBAAoB,CAAC;CAEzB,IAAI,CAAC,aACH,QAAQ,KACN,sLACD;CAoCH,WAAW,IAAI,mBAAmB;EAChC,UA7Be,uBAAuB;GACtC,gBALA,QAAQ,eACR,QAAQ,IAAI,wBACZ;GAIA,GAAI,QAAQ,mBACR,EAAE,qBAAqB,QAAQ,kBAAkB,GACjD,EAAE;GACN,+BAA+B,IAAI,mBAAmB;GACtD,0BAA0B;GAC1B,sBAAsB;GACtB,yBAAyB;GACzB,GAAI,cAAc,EAAE,gBAAgB,aAAa,GAAG,EAAE;GACvD,CAmBS;EACR,gBAAgB,CAAC,IAAI,mBAAmB,IAlBrB,kBAAkB;GAGrC,KAAK,GAAG,IAAI,SAAS,UAAU;GAC/B,SAAS;IAKP,cAAc;IACd,aAAa,IAAI;IACjB,gCAAgC;IAChC,GAAI,IAAI,cAAc,EAAE,4BAA4B,KAAK,GAAG,EAAE;IAC/D;GACF,CAIiD,CAAC,CAAC;EACnD,CAAC;CAEF,SAAS,SAAS,EAChB,gBAAgB,IAAI,iCAAiC,CAAC,QAAQ,EAC/D,CAAC;CAEF,sBAAsB;EACpB,0BAA0B,QAAQ,4BAA4B;EAC9D,2BAA2B,QAAQ,6BAA6B;EAChE;EACD,CAAC;CAEF,MAAM,2BAA2B;;;;;;;AAQnC,eAAsB,QAAuB;CAC3C,IAAI,CAAC,UACH;CAEF,yBAAyB;CACzB,MAAM,SAAS,UAAU;CACzB,WAAW;;;;;;AAOb,eAAsB,QAAuB;CAC3C,IAAI,CAAC,UACH;CAEF,MAAM,SAAS,YAAY"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"dedupe.d.mts","names":[],"sources":["../../src/internal/dedupe.ts"],"mappings":";iBAEgB,eAAA,CAAgB,KAAA;AAAA,iBAIhB,iBAAA,CAAkB,KAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"dedupe.mjs","names":[],"sources":["../../src/internal/dedupe.ts"],"sourcesContent":["const seenErrors = new WeakSet<Error>();\n\nexport function isErrorCaptured(error: unknown): boolean {\n return error instanceof Error && seenErrors.has(error);\n}\n\nexport function markErrorCaptured(error: unknown): void {\n if (!(error instanceof Error)) {\n return;\n }\n\n seenErrors.add(error);\n}\n"],"mappings":";AAAA,MAAM,6BAAa,IAAI,SAAgB;AAEvC,SAAgB,gBAAgB,OAAyB;CACvD,OAAO,iBAAiB,SAAS,WAAW,IAAI,MAAM;;AAGxD,SAAgB,kBAAkB,OAAsB;CACtD,IAAI,EAAE,iBAAiB,QACrB;CAGF,WAAW,IAAI,MAAM"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"env.d.mts","names":[],"sources":["../../src/internal/env.ts"],"mappings":";;;UAKiB,YAAA;EAAA,SACN,MAAA;EAAA,SACA,MAAA;EAAA,SACA,WAAA;EAAA,SACA,eAAA,EAAiB,OAAA,CAAQ,GAAA;AAAA;;;;;;;;iBAUpB,sBAAA,CAAA;AAAA,iBAIA,gBAAA,CAAA,GAAoB,YAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"env.mjs","names":[],"sources":["../../src/internal/env.ts"],"sourcesContent":["import { API_URL } from \"@interfere/constants/api\";\nimport { parseEnvValue } from \"@interfere/types/sdk/env\";\nimport type { Env } from \"@interfere/types/sdk/runtime\";\nimport { normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nexport interface InterfereEnv {\n readonly apiKey: string | null;\n readonly apiUrl: string;\n readonly forceEnable: boolean;\n readonly nodeEnvironment: Exclude<Env, null>;\n}\n\n/**\n * Server SDKs always run end-to-end. There's no `NEXT_PUBLIC_*` force-enable\n * escape hatch here — if `INTERFERE_API_KEY` is set, the SDK is on. The Next\n * SDK guards on `NODE_ENV=production || NEXT_PUBLIC_INTERFERE_FORCE_ENABLE`\n * because `next dev` runs the SDK during development without a real release\n * slug; that asymmetry doesn't apply to plain Node servers.\n */\nexport function isEnabledInEnvironment(): boolean {\n return true;\n}\n\nexport function readInterfereEnv(): InterfereEnv {\n const nodeEnvironment = normalizeEnv(process.env[\"NODE_ENV\"]) ?? \"production\";\n\n return {\n apiKey: parseEnvValue(process.env[\"INTERFERE_API_KEY\"]),\n apiUrl: parseEnvValue(process.env[\"INTERFERE_API_URL\"]) ?? API_URL,\n forceEnable: process.env[\"INTERFERE_FORCE_ENABLE\"] === \"1\",\n nodeEnvironment,\n };\n}\n"],"mappings":";;;;;;;;;;;AAmBA,SAAgB,yBAAkC;CAChD,OAAO;;AAGT,SAAgB,mBAAiC;CAC/C,MAAM,kBAAkB,aAAa,QAAQ,IAAI,YAAY,IAAI;CAEjE,OAAO;EACL,QAAQ,cAAc,QAAQ,IAAI,qBAAqB;EACvD,QAAQ,cAAc,QAAQ,IAAI,qBAAqB,IAAI;EAC3D,aAAa,QAAQ,IAAI,8BAA8B;EACvD;EACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"global-handlers.d.mts","names":[],"sources":["../../src/internal/global-handlers.ts"],"mappings":";;AAyBA;;;;;;;;;;;AAoBA;;;;iBApBgB,qBAAA,CAAsB,OAAA;EAAA,SAC3B,wBAAA;EAAA,SACA,yBAAA;EAAA,SACA,KAAA,QAAa,OAAA;AAAA;AAAA,iBAiBR,uBAAA,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"global-handlers.mjs","names":[],"sources":["../../src/internal/global-handlers.ts"],"sourcesContent":["import { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { captureError } from \"../capture.js\";\n\nconst FLUSH_TIMEOUT_MS = 2000;\n\nlet uncaughtHandler: NodeJS.UncaughtExceptionListener | null = null;\nlet unhandledHandler: NodeJS.UnhandledRejectionListener | null = null;\n\n/**\n * Installs `process.on(\"uncaughtException\")` and\n * `process.on(\"unhandledRejection\")` listeners that report to Interfere.\n *\n * Uncaught exceptions: captured, spans flushed (best-effort, 2s timeout),\n * then `process.exit(1)`. Once an `uncaughtException` listener is\n * registered, Node will not exit on its own — leaving the process alive\n * after an uncaught error usually means a corrupted state, so we own the\n * exit. Customers who need different exit semantics should opt out and\n * register their own handler.\n *\n * Unhandled rejections: captured and flushed, but the SDK does not call\n * `process.exit`. Whether unhandled rejections are fatal is governed by\n * Node's `--unhandled-rejections` flag (default `throw` since v15) and we\n * don't override that.\n */\nexport function installGlobalHandlers(options: {\n readonly captureUncaughtException: boolean;\n readonly captureUnhandledRejection: boolean;\n readonly flush: () => Promise<void>;\n}): void {\n if (options.captureUncaughtException && uncaughtHandler === null) {\n uncaughtHandler = (error) => {\n handleUncaughtException(error, options.flush).catch(() => undefined);\n };\n process.on(\"uncaughtException\", uncaughtHandler);\n }\n\n if (options.captureUnhandledRejection && unhandledHandler === null) {\n unhandledHandler = (reason) => {\n handleUnhandledRejection(reason, options.flush).catch(() => undefined);\n };\n process.on(\"unhandledRejection\", unhandledHandler);\n }\n}\n\nexport function uninstallGlobalHandlers(): void {\n if (uncaughtHandler !== null) {\n process.off(\"uncaughtException\", uncaughtHandler);\n uncaughtHandler = null;\n }\n if (unhandledHandler !== null) {\n process.off(\"unhandledRejection\", unhandledHandler);\n unhandledHandler = null;\n }\n}\n\nasync function handleUncaughtException(\n error: Error,\n flush: () => Promise<void>\n): Promise<void> {\n await captureError(error, undefined, {\n mechanism: {\n type: MECHANISM_TYPE.node.uncaughtException,\n handled: false,\n },\n });\n await flushWithTimeout(flush);\n process.exit(1);\n}\n\nasync function handleUnhandledRejection(\n reason: unknown,\n flush: () => Promise<void>\n): Promise<void> {\n await captureError(reason, undefined, {\n mechanism: {\n type: MECHANISM_TYPE.node.unhandledRejection,\n handled: false,\n },\n });\n await flushWithTimeout(flush);\n}\n\nfunction flushWithTimeout(flush: () => Promise<void>): Promise<void> {\n return Promise.race([\n flush(),\n new Promise<void>((resolve) => {\n setTimeout(resolve, FLUSH_TIMEOUT_MS);\n }),\n ]);\n}\n"],"mappings":";;;AAIA,MAAM,mBAAmB;AAEzB,IAAI,kBAA2D;AAC/D,IAAI,mBAA6D;;;;;;;;;;;;;;;;;AAkBjE,SAAgB,sBAAsB,SAI7B;CACP,IAAI,QAAQ,4BAA4B,oBAAoB,MAAM;EAChE,mBAAmB,UAAU;GAC3B,wBAAwB,OAAO,QAAQ,MAAM,CAAC,YAAY,KAAA,EAAU;;EAEtE,QAAQ,GAAG,qBAAqB,gBAAgB;;CAGlD,IAAI,QAAQ,6BAA6B,qBAAqB,MAAM;EAClE,oBAAoB,WAAW;GAC7B,yBAAyB,QAAQ,QAAQ,MAAM,CAAC,YAAY,KAAA,EAAU;;EAExE,QAAQ,GAAG,sBAAsB,iBAAiB;;;AAItD,SAAgB,0BAAgC;CAC9C,IAAI,oBAAoB,MAAM;EAC5B,QAAQ,IAAI,qBAAqB,gBAAgB;EACjD,kBAAkB;;CAEpB,IAAI,qBAAqB,MAAM;EAC7B,QAAQ,IAAI,sBAAsB,iBAAiB;EACnD,mBAAmB;;;AAIvB,eAAe,wBACb,OACA,OACe;CACf,MAAM,aAAa,OAAO,KAAA,GAAW,EACnC,WAAW;EACT,MAAM,eAAe,KAAK;EAC1B,SAAS;EACV,EACF,CAAC;CACF,MAAM,iBAAiB,MAAM;CAC7B,QAAQ,KAAK,EAAE;;AAGjB,eAAe,yBACb,QACA,OACe;CACf,MAAM,aAAa,QAAQ,KAAA,GAAW,EACpC,WAAW;EACT,MAAM,eAAe,KAAK;EAC1B,SAAS;EACV,EACF,CAAC;CACF,MAAM,iBAAiB,MAAM;;AAG/B,SAAS,iBAAiB,OAA2C;CACnE,OAAO,QAAQ,KAAK,CAClB,OAAO,EACP,IAAI,SAAe,YAAY;EAC7B,WAAW,SAAS,iBAAiB;GACrC,CACH,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalize-request.d.mts","names":[],"sources":["../../src/internal/normalize-request.ts"],"mappings":";;;cAEa,kBAAA;;AAAb;;;;;AAUA;;iBAAgB,gBAAA,CAAiB,OAAA,YAAmB,iBAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalize-request.mjs","names":[],"sources":["../../src/internal/normalize-request.ts"],"sourcesContent":["import type { NormalizedRequest } from \"./types.js\";\n\nexport const TRACEPARENT_HEADER = \"traceparent\";\n\n/**\n * Coerces a request-shaped value into the headers/method/path triple used\n * by envelope construction. Handles WHATWG `Request`, Node\n * `IncomingMessage`-like objects, and plain object literals.\n *\n * Returns `null` only for non-objects; otherwise produces a best-effort\n * `NormalizedRequest` with sensible defaults.\n */\nexport function normalizeRequest(request: unknown): NormalizedRequest | null {\n if (request instanceof Request) {\n return {\n method: normalizeMethod(request.method),\n path: normalizePath(request.url),\n headers: request.headers,\n };\n }\n\n if (typeof request !== \"object\" || request === null) {\n return null;\n }\n\n const value = request as {\n headers?: unknown;\n method?: unknown;\n path?: unknown;\n url?: unknown;\n };\n\n return {\n method: normalizeMethod(value.method),\n path: normalizePath(\n typeof value.path === \"string\" ? value.path : toStringOrDefault(value.url)\n ),\n headers: normalizeHeaders(value.headers),\n };\n}\n\nfunction normalizeMethod(value: unknown): string {\n if (typeof value !== \"string\") {\n return \"GET\";\n }\n\n const method = value.trim();\n return method.length > 0 ? method.toUpperCase() : \"GET\";\n}\n\nfunction normalizePath(value: string): string {\n if (value.length === 0) {\n return \"/\";\n }\n\n try {\n const parsed = new URL(value, \"http://localhost\");\n return parsed.pathname || \"/\";\n } catch {\n return value.startsWith(\"/\") ? value : `/${value}`;\n }\n}\n\nfunction toStringOrDefault(value: unknown, fallback = \"/\"): string {\n if (typeof value !== \"string\") {\n return fallback;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : fallback;\n}\n\nfunction normalizeHeaders(value: unknown): Headers {\n if (value instanceof Headers) {\n return new Headers(value);\n }\n\n if (typeof value !== \"object\" || value === null) {\n return new Headers();\n }\n\n const headers = new Headers();\n for (const [key, headerValue] of Object.entries(value)) {\n if (headerValue === undefined) {\n continue;\n }\n\n if (Array.isArray(headerValue)) {\n headers.set(key, headerValue.join(\", \"));\n continue;\n }\n\n headers.set(key, String(headerValue));\n }\n\n return headers;\n}\n"],"mappings":";AAEA,MAAa,qBAAqB;;;;;;;;;AAUlC,SAAgB,iBAAiB,SAA4C;CAC3E,IAAI,mBAAmB,SACrB,OAAO;EACL,QAAQ,gBAAgB,QAAQ,OAAO;EACvC,MAAM,cAAc,QAAQ,IAAI;EAChC,SAAS,QAAQ;EAClB;CAGH,IAAI,OAAO,YAAY,YAAY,YAAY,MAC7C,OAAO;CAGT,MAAM,QAAQ;CAOd,OAAO;EACL,QAAQ,gBAAgB,MAAM,OAAO;EACrC,MAAM,cACJ,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,kBAAkB,MAAM,IAAI,CAC3E;EACD,SAAS,iBAAiB,MAAM,QAAQ;EACzC;;AAGH,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,MAAM,SAAS,MAAM,MAAM;CAC3B,OAAO,OAAO,SAAS,IAAI,OAAO,aAAa,GAAG;;AAGpD,SAAS,cAAc,OAAuB;CAC5C,IAAI,MAAM,WAAW,GACnB,OAAO;CAGT,IAAI;EAEF,OAAO,IADY,IAAI,OAAO,mBACjB,CAAC,YAAY;SACpB;EACN,OAAO,MAAM,WAAW,IAAI,GAAG,QAAQ,IAAI;;;AAI/C,SAAS,kBAAkB,OAAgB,WAAW,KAAa;CACjE,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,MAAM,UAAU,MAAM,MAAM;CAC5B,OAAO,QAAQ,SAAS,IAAI,UAAU;;AAGxC,SAAS,iBAAiB,OAAyB;CACjD,IAAI,iBAAiB,SACnB,OAAO,IAAI,QAAQ,MAAM;CAG3B,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO,IAAI,SAAS;CAGtB,MAAM,UAAU,IAAI,SAAS;CAC7B,KAAK,MAAM,CAAC,KAAK,gBAAgB,OAAO,QAAQ,MAAM,EAAE;EACtD,IAAI,gBAAgB,KAAA,GAClB;EAGF,IAAI,MAAM,QAAQ,YAAY,EAAE;GAC9B,QAAQ,IAAI,KAAK,YAAY,KAAK,KAAK,CAAC;GACxC;;EAGF,QAAQ,IAAI,KAAK,OAAO,YAAY,CAAC;;CAGvC,OAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-slug.d.mts","names":[],"sources":["../../src/internal/release-slug.ts"],"mappings":";iBAkBgB,gBAAA,CAAA;AAAA,iBAOA,kBAAA,CAAA;EACd,SAAA;EACA,IAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-slug.mjs","names":[],"sources":["../../src/internal/release-slug.ts"],"sourcesContent":["import { releaseSourceIdEnvKeys } from \"@interfere/types/integrations\";\nimport { deriveReleaseSlug } from \"@interfere/types/releases/slug\";\nimport { readFirstEnvValue } from \"@interfere/types/sdk/env\";\n\nimport { execSync } from \"node:child_process\";\n\nfunction runGitCommand(command: string): string | null {\n try {\n const output = execSync(command, {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n return output.length > 0 ? output : null;\n } catch {\n return null;\n }\n}\n\nexport function resolveCommitSha(): string | null {\n return (\n readFirstEnvValue(process.env, releaseSourceIdEnvKeys) ??\n runGitCommand(\"git rev-parse HEAD\")\n );\n}\n\nexport function resolveReleaseSlug(): {\n commitSha: string | null;\n slug: string | null;\n} {\n const commitSha = resolveCommitSha();\n return {\n commitSha,\n slug: commitSha ? deriveReleaseSlug(commitSha) : null,\n };\n}\n"],"mappings":";;;;;AAMA,SAAS,cAAc,SAAgC;CACrD,IAAI;EACF,MAAM,SAAS,SAAS,SAAS;GAC/B,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACpC,CAAC,CAAC,MAAM;EACT,OAAO,OAAO,SAAS,IAAI,SAAS;SAC9B;EACN,OAAO;;;AAIX,SAAgB,mBAAkC;CAChD,OACE,kBAAkB,QAAQ,KAAK,uBAAuB,IACtD,cAAc,qBAAqB;;AAIvC,SAAgB,qBAGd;CACA,MAAM,YAAY,kBAAkB;CACpC,OAAO;EACL;EACA,MAAM,YAAY,kBAAkB,UAAU,GAAG;EAClD"}
@@ -1,5 +0,0 @@
1
- //#region src/internal/remote-config.d.ts
2
- declare function fetchAndCacheRemoteConfig(): Promise<void>;
3
- declare function isPluginEnabled(plugin: string): boolean;
4
- //#endregion
5
- export { fetchAndCacheRemoteConfig, isPluginEnabled };
@@ -1 +0,0 @@
1
- {"version":3,"file":"remote-config.d.mts","names":[],"sources":["../../src/internal/remote-config.ts"],"mappings":";iBAUsB,yBAAA,CAAA,GAA6B,OAAA;AAAA,iBAkCnC,eAAA,CAAgB,MAAA"}
@@ -1,29 +0,0 @@
1
- import { isEnabledInEnvironment, readInterfereEnv } from "./env.mjs";
2
- import { API_PATHS } from "@interfere/constants/api";
3
- //#region src/internal/remote-config.ts
4
- let cachedConfig = null;
5
- async function fetchAndCacheRemoteConfig() {
6
- if (!isEnabledInEnvironment()) return;
7
- const env = readInterfereEnv();
8
- if (env.apiKey === null) return;
9
- try {
10
- const url = `${env.apiUrl}${API_PATHS.CONFIG}`;
11
- const response = await fetch(url, {
12
- method: "GET",
13
- headers: {
14
- "content-type": "application/json",
15
- "x-api-key": env.apiKey
16
- },
17
- signal: AbortSignal.timeout(1e4)
18
- });
19
- if (!response.ok) return;
20
- const config = await response.json();
21
- if (config?.plugins) cachedConfig = config.plugins;
22
- } catch {}
23
- }
24
- function isPluginEnabled(plugin) {
25
- if (!cachedConfig) return true;
26
- return cachedConfig[plugin] !== false;
27
- }
28
- //#endregion
29
- export { fetchAndCacheRemoteConfig, isPluginEnabled };
@@ -1 +0,0 @@
1
- {"version":3,"file":"remote-config.mjs","names":[],"sources":["../../src/internal/remote-config.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport type {\n RemoteConfig,\n RemotePluginConfig,\n} from \"@interfere/types/sdk/remote-config\";\n\nimport { isEnabledInEnvironment, readInterfereEnv } from \"./env.js\";\n\nlet cachedConfig: RemotePluginConfig | null = null;\n\nexport async function fetchAndCacheRemoteConfig(): Promise<void> {\n if (!isEnabledInEnvironment()) {\n return;\n }\n\n const env = readInterfereEnv();\n if (env.apiKey === null) {\n return;\n }\n\n try {\n const url = `${env.apiUrl}${API_PATHS.CONFIG}`;\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-api-key\": env.apiKey,\n },\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!response.ok) {\n return;\n }\n\n const config = (await response.json()) as RemoteConfig;\n if (config?.plugins) {\n cachedConfig = config.plugins;\n }\n } catch {\n // Fail silently — all plugins remain enabled\n }\n}\n\nexport function isPluginEnabled(plugin: string): boolean {\n if (!cachedConfig) {\n return true;\n }\n return cachedConfig[plugin as keyof RemotePluginConfig] !== false;\n}\n"],"mappings":";;;AAQA,IAAI,eAA0C;AAE9C,eAAsB,4BAA2C;CAC/D,IAAI,CAAC,wBAAwB,EAC3B;CAGF,MAAM,MAAM,kBAAkB;CAC9B,IAAI,IAAI,WAAW,MACjB;CAGF,IAAI;EACF,MAAM,MAAM,GAAG,IAAI,SAAS,UAAU;EACtC,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,aAAa,IAAI;IAClB;GACD,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC;EAEF,IAAI,CAAC,SAAS,IACZ;EAGF,MAAM,SAAU,MAAM,SAAS,MAAM;EACrC,IAAI,QAAQ,SACV,eAAe,OAAO;SAElB;;AAKV,SAAgB,gBAAgB,QAAyB;CACvD,IAAI,CAAC,cACH,OAAO;CAET,OAAO,aAAa,YAAwC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/internal/types.ts"],"mappings":";;;;UAGiB,mBAAA;EAAA,SACN,SAAA,GAAY,cAAA;EAAA,SACZ,IAAA,GAAO,IAAA,CAAK,WAAA;EAAA,SACZ,WAAA;AAAA;AAAA,UAGM,iBAAA;EAAA,SACN,OAAA,EAAS,OAAA;EAAA,SACT,MAAA;EAAA,SACA,IAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"version.d.mts","names":[],"sources":["../../src/internal/version.ts"],"mappings":";cAEa,gBAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"version.mjs","names":["pkg.name","pkg.version"],"sources":["../../src/internal/version.ts"],"sourcesContent":["import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const PRODUCER_VERSION = `${pkg.name}@${pkg.version}`;\n"],"mappings":";;AAEA,MAAa,mBAAmB,GAAGA,KAAS,GAAGC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"package.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}