@interfere/next 11.0.0-canary.2 → 11.0.0-canary.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.
Files changed (120) hide show
  1. package/README.md +3 -2
  2. package/dist/config.mjs +1 -1
  3. package/dist/instrument-client.d.mts +1 -0
  4. package/dist/instrumentation.d.mts +4 -2
  5. package/dist/instrumentation.edge.d.mts +16 -23
  6. package/dist/instrumentation.edge.mjs +1 -1
  7. package/dist/instrumentation.mjs +1 -1
  8. package/dist/internal/build/configure-build.mjs +1 -1
  9. package/dist/internal/build/pipeline.d.mts +1 -14
  10. package/dist/internal/build/release/destinations/cli.d.mts +7 -0
  11. package/dist/internal/build/release/destinations/cli.mjs +1 -0
  12. package/dist/internal/build/release/destinations/cloudflare.d.mts +6 -0
  13. package/dist/internal/build/release/destinations/cloudflare.mjs +1 -0
  14. package/dist/internal/build/release/destinations/index.d.mts +5 -4
  15. package/dist/internal/build/release/destinations/index.mjs +1 -1
  16. package/dist/internal/build/release/index.mjs +1 -1
  17. package/dist/internal/build/source-maps/discover-webpack.d.mts +1 -7
  18. package/dist/internal/build/source-maps/discover-webpack.mjs +1 -1
  19. package/dist/internal/env.mjs +1 -1
  20. package/dist/internal/release-slug.d.mts +1 -17
  21. package/dist/internal/release-slug.mjs +1 -1
  22. package/dist/internal/route/proxy.d.mts +1 -21
  23. package/dist/internal/route/proxy.mjs +1 -1
  24. package/dist/internal/server/capture.d.mts +6 -1
  25. package/dist/internal/server/capture.mjs +1 -1
  26. package/dist/internal/server/edge-exporter.d.mts +14 -0
  27. package/dist/internal/server/edge-exporter.mjs +1 -0
  28. package/dist/internal/server/global-handlers.d.mts +11 -0
  29. package/dist/internal/server/global-handlers.mjs +1 -0
  30. package/dist/internal/server/instrumentation-options.d.mts +15 -1
  31. package/dist/internal/server/server-action-marker.d.mts +9 -0
  32. package/dist/internal/server/server-action-marker.mjs +1 -0
  33. package/dist/internal/server/trace-meta.d.mts +1 -1
  34. package/dist/internal/server/types.d.mts +5 -0
  35. package/dist/internal/server/wrap-proxy.d.mts +22 -0
  36. package/dist/internal/server/wrap-proxy.mjs +1 -0
  37. package/dist/internal/server/wrap-server-action.d.mts +34 -0
  38. package/dist/internal/server/wrap-server-action.mjs +1 -0
  39. package/dist/package.mjs +1 -1
  40. package/dist/server.d.mts +3 -1
  41. package/dist/server.mjs +1 -1
  42. package/package.json +22 -23
  43. package/dist/config.d.mts.map +0 -1
  44. package/dist/config.mjs.map +0 -1
  45. package/dist/instrument-client.d.mts.map +0 -1
  46. package/dist/instrument-client.mjs.map +0 -1
  47. package/dist/instrumentation-client.mjs.map +0 -1
  48. package/dist/instrumentation.d.mts.map +0 -1
  49. package/dist/instrumentation.edge.d.mts.map +0 -1
  50. package/dist/instrumentation.edge.mjs.map +0 -1
  51. package/dist/instrumentation.mjs.map +0 -1
  52. package/dist/internal/build/configure-build.d.mts.map +0 -1
  53. package/dist/internal/build/configure-build.mjs.map +0 -1
  54. package/dist/internal/build/detect-bundler.d.mts.map +0 -1
  55. package/dist/internal/build/detect-bundler.mjs.map +0 -1
  56. package/dist/internal/build/pipeline.d.mts.map +0 -1
  57. package/dist/internal/build/pipeline.mjs.map +0 -1
  58. package/dist/internal/build/release/destinations/index.d.mts.map +0 -1
  59. package/dist/internal/build/release/destinations/index.mjs.map +0 -1
  60. package/dist/internal/build/release/destinations/vercel.d.mts.map +0 -1
  61. package/dist/internal/build/release/destinations/vercel.mjs.map +0 -1
  62. package/dist/internal/build/release/git.d.mts.map +0 -1
  63. package/dist/internal/build/release/git.mjs.map +0 -1
  64. package/dist/internal/build/release/index.d.mts.map +0 -1
  65. package/dist/internal/build/release/index.mjs.map +0 -1
  66. package/dist/internal/build/release/sources/github.d.mts.map +0 -1
  67. package/dist/internal/build/release/sources/github.mjs.map +0 -1
  68. package/dist/internal/build/release/sources/index.d.mts.map +0 -1
  69. package/dist/internal/build/release/sources/index.mjs.map +0 -1
  70. package/dist/internal/build/source-maps/discover-turbopack.d.mts.map +0 -1
  71. package/dist/internal/build/source-maps/discover-turbopack.mjs.map +0 -1
  72. package/dist/internal/build/source-maps/discover-webpack.d.mts.map +0 -1
  73. package/dist/internal/build/source-maps/discover-webpack.mjs.map +0 -1
  74. package/dist/internal/build/source-maps/discover.d.mts.map +0 -1
  75. package/dist/internal/build/source-maps/discover.mjs.map +0 -1
  76. package/dist/internal/build/source-maps/index.d.mts.map +0 -1
  77. package/dist/internal/build/source-maps/index.mjs.map +0 -1
  78. package/dist/internal/build/source-maps/paths.d.mts.map +0 -1
  79. package/dist/internal/build/source-maps/paths.mjs.map +0 -1
  80. package/dist/internal/build/source-maps/upload.d.mts.map +0 -1
  81. package/dist/internal/build/source-maps/upload.mjs.map +0 -1
  82. package/dist/internal/build/value-injection-loader.d.mts.map +0 -1
  83. package/dist/internal/build/value-injection-loader.mjs.map +0 -1
  84. package/dist/internal/env.d.mts.map +0 -1
  85. package/dist/internal/env.mjs.map +0 -1
  86. package/dist/internal/logger.d.mts.map +0 -1
  87. package/dist/internal/logger.mjs.map +0 -1
  88. package/dist/internal/release-slug.d.mts.map +0 -1
  89. package/dist/internal/release-slug.mjs.map +0 -1
  90. package/dist/internal/route/handle-get.d.mts.map +0 -1
  91. package/dist/internal/route/handle-get.mjs.map +0 -1
  92. package/dist/internal/route/handle-post.d.mts.map +0 -1
  93. package/dist/internal/route/handle-post.mjs.map +0 -1
  94. package/dist/internal/route/proxy.d.mts.map +0 -1
  95. package/dist/internal/route/proxy.mjs.map +0 -1
  96. package/dist/internal/server/capture.d.mts.map +0 -1
  97. package/dist/internal/server/capture.mjs.map +0 -1
  98. package/dist/internal/server/console-bridge.d.mts.map +0 -1
  99. package/dist/internal/server/console-bridge.mjs.map +0 -1
  100. package/dist/internal/server/id-generator.d.mts.map +0 -1
  101. package/dist/internal/server/id-generator.mjs.map +0 -1
  102. package/dist/internal/server/instrumentation-options.d.mts.map +0 -1
  103. package/dist/internal/server/remote-config.d.mts.map +0 -1
  104. package/dist/internal/server/remote-config.mjs.map +0 -1
  105. package/dist/internal/server/trace-meta.d.mts.map +0 -1
  106. package/dist/internal/server/trace-meta.mjs.map +0 -1
  107. package/dist/internal/server/traceparent.d.mts.map +0 -1
  108. package/dist/internal/server/traceparent.mjs.map +0 -1
  109. package/dist/internal/server/types.d.mts.map +0 -1
  110. package/dist/internal/setup-warnings.d.mts.map +0 -1
  111. package/dist/internal/setup-warnings.mjs.map +0 -1
  112. package/dist/internal/url.d.mts.map +0 -1
  113. package/dist/internal/url.mjs.map +0 -1
  114. package/dist/internal/version.d.mts.map +0 -1
  115. package/dist/internal/version.mjs.map +0 -1
  116. package/dist/package.mjs.map +0 -1
  117. package/dist/provider.d.mts.map +0 -1
  118. package/dist/provider.mjs.map +0 -1
  119. package/dist/route-handler.d.mts.map +0 -1
  120. package/dist/route-handler.mjs.map +0 -1
package/README.md CHANGED
@@ -62,7 +62,7 @@ export default withInterfere(config);
62
62
 
63
63
  ```ts
64
64
  // instrumentation.ts
65
- export { onRequestError } from "@interfere/next/server";
65
+ export { onRequestError, register } from "@interfere/next/instrumentation";
66
66
  ```
67
67
 
68
68
  **3. Mount the ingest route**
@@ -178,9 +178,10 @@ function useInterfereIdentity() {
178
178
  | Method | Description |
179
179
  | --- | --- |
180
180
  | `identity.set(params)` | Link the current session to a user. Deduplicated per session — only the first call sends a request. |
181
+ | `identity.clear()` | Clears the linked identity and rotates the session. Call on logout. |
181
182
  | `identity.get()` | Returns the current `IdentifyParams`, or `null` if no identity has been set. |
182
183
 
183
- Identity is automatically cleared when the SDK is closed or the session rotates.
184
+ Identity is automatically cleared when the SDK is closed. Call `identity.clear()` on logout to start a fresh anonymous session.
184
185
 
185
186
  ## Consent Management
186
187
 
package/dist/config.mjs CHANGED
@@ -1 +1 @@
1
- import{configureBuild}from"./internal/build/configure-build.mjs";import{readInterfereEnv}from"./internal/env.mjs";import{FatalError,log}from"./internal/logger.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{readNextMajorVersion,warnIfServerInstrumentationMissing}from"./internal/setup-warnings.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{readFirstEnvValue}from"@interfere/types/sdk/env";function withInterfere(nextConfig={}){let{interfere,env:userEnv,webpack,turbopack,compiler,productionBrowserSourceMaps,...rest}=nextConfig;if(interfere&&`buildId`in interfere&&interfere.buildId!==void 0)return log.fatal(`Removed: interfere.buildId`,["`interfere.buildId` was removed in SDK 10. It only worked at build time, so server-side spans drifted from the bundle's slug.","Override the commit SHA via the `INTERFERE_SOURCE_ID` env var on both build and runtime envs instead."]);let projectDir=process.cwd(),config=resolveBuildConfig(),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,projectDir,values:{__INTERFERE_RELEASE_SLUG__:forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug,...config.publicKey&&{__INTERFERE_PROXY_MODE__:!0,__INTERFERE_PUBLIC_KEY__:config.publicKey},...config.environment&&{__INTERFERE_ENVIRONMENT__:config.environment},...forceEnable&&{__INTERFERE_FORCE_ENABLE__:!0}}});if(config.publicKey!==null&&!build.webpack&&!build.turbopack)return log.fatal(`Missing instrumentation client`,[`INTERFERE_PUBLIC_KEY is set but no instrumentation-client file was found.`,`Create an instrumentation-client.ts file in your project root or src/ directory.`]);config.publicKey!==null&&warnIfServerInstrumentationMissing(projectDir);let nextMajor=readNextMajorVersion(projectDir),needsInstrumentationHookFlag=nextMajor===null||nextMajor<=14,existingHook=compiler?.runAfterProductionCompile,browserSourceMaps=config.apiKey===null?productionBrowserSourceMaps:!0;return config.apiKey!==null&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),{...rest,...userEnv?{env:userEnv}:{},compiler:{...omitUndefined(compiler??{}),async runAfterProductionCompile(context){existingHook&&await existingHook(context);try{await runBuildReleasePipeline(context,config)}catch(error){if(error instanceof FatalError)throw error;let message=error instanceof Error?error.message:String(error);log.warn(`Skipping release pipeline`,[`Interfere encountered an unexpected error.`,`Build will continue, but this release's data will be ingested, but ignored as evidence when generating insights.`,`Error: ${message}`])}}},...omitUndefined({productionBrowserSourceMaps:browserSourceMaps,turbopack:build.turbopack,webpack:build.webpack}),...needsInstrumentationHookFlag?{experimental:{...rest.experimental??{},instrumentationHook:!0}}:{}}}function resolveBuildConfig(){let{apiKey,apiUrl,publicKey}=readInterfereEnv(),{commitSha,slug}=resolveReleaseSlug();return{apiKey:apiKey??null,apiUrl,buildId:commitSha,environment:readFirstEnvValue(process.env,[`INTERFERE_ENVIRONMENT`,`VERCEL_ENV`]),publicKey:publicKey??null,releaseSlug:slug}}async function runBuildReleasePipeline(context,config){let{apiKey,buildId,releaseSlug}=config;if(!apiKey)return log.fatal(`API key not set`,[`withInterfere() requires an API key to publish release metadata during production builds.`,`Set the INTERFERE_API_KEY environment variable, or remove withInterfere() from your Next.js config.`,`See: https://support.interfere.com/docs/guides/next-js`]);if(!(buildId&&releaseSlug))return log.fatal(`Commit SHA missing`,[`Could not resolve a commit SHA from environment variables or git.`,"Set `INTERFERE_SOURCE_ID`, ensure your CI exposes its git SHA env var (e.g. `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`), or run inside a git checkout."]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey,buildId,releaseSlug});if(!result.ready){log.warn(`Skipping`,[`No source maps found`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config?.org.slug}/surfaces/${result.config?.surface.slug}`,`Release: ${result.release?.destination.slug??`Unknown`}`,`Build: ${result.buildId}`,`Artifacts: ${result.fileCount} source maps`])}catch(error){let message=error instanceof Error?error.message:String(error);log.warn(`Skipping release pipeline`,[`Interfere failed to process this release.`,`Events from this release will be ingested, but ignored as evidence when generating insights.`,`Please check https://status.interfere.com for any outages, or contact support@interfere.com if the problem persists.`,`Error: ${message}`]);return}}export{withInterfere};
1
+ import{configureBuild}from"./internal/build/configure-build.mjs";import{readInterfereEnv}from"./internal/env.mjs";import{FatalError,log}from"./internal/logger.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{readNextMajorVersion,warnIfServerInstrumentationMissing}from"./internal/setup-warnings.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";function withInterfere(nextConfig={}){let{interfere,env:userEnv,webpack,turbopack,compiler,productionBrowserSourceMaps,...rest}=nextConfig;if(interfere&&`buildId`in interfere&&interfere.buildId!==void 0)return log.fatal(`Removed: interfere.buildId`,["`interfere.buildId` was removed in SDK 10. It only worked at build time, so server-side spans drifted from the bundle's slug.","Override the commit SHA via the `INTERFERE_SOURCE_ID` env var on both build and runtime envs instead."]);let projectDir=process.cwd(),config=resolveBuildConfig(),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,projectDir,values:{__INTERFERE_RELEASE_SLUG__:forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug,...config.publicKey&&{__INTERFERE_PROXY_MODE__:!0,__INTERFERE_PUBLIC_KEY__:config.publicKey},...config.environment&&{__INTERFERE_ENVIRONMENT__:config.environment},...forceEnable&&{__INTERFERE_FORCE_ENABLE__:!0}}});if(config.publicKey!==null&&!build.webpack&&!build.turbopack)return log.fatal(`Missing instrumentation client`,[`INTERFERE_PUBLIC_KEY is set but no instrumentation-client file was found.`,`Create an instrumentation-client.ts file in your project root or src/ directory.`]);config.publicKey!==null&&warnIfServerInstrumentationMissing(projectDir);let nextMajor=readNextMajorVersion(projectDir),needsInstrumentationHookFlag=nextMajor===null||nextMajor<=14,existingHook=compiler?.runAfterProductionCompile,browserSourceMaps=config.apiKey===null?productionBrowserSourceMaps:!0;return config.apiKey!==null&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),{...rest,...userEnv?{env:userEnv}:{},compiler:{...omitUndefined(compiler??{}),async runAfterProductionCompile(context){existingHook&&await existingHook(context);try{await runBuildReleasePipeline(context,config)}catch(error){if(error instanceof FatalError)throw error;let message=error instanceof Error?error.message:String(error);log.warn(`Skipping release pipeline`,[`Interfere encountered an unexpected error.`,`Build will continue, but this release's data will be ingested, but ignored as evidence when generating insights.`,`Error: ${message}`])}}},...omitUndefined({productionBrowserSourceMaps:browserSourceMaps,turbopack:build.turbopack,webpack:build.webpack}),...needsInstrumentationHookFlag?{experimental:{...rest.experimental??{},instrumentationHook:!0}}:{}}}function resolveBuildConfig(){let{apiKey,apiUrl,publicKey}=readInterfereEnv(),{commitSha,slug}=resolveReleaseSlug(),environment=process.env.NEXT_PHASE===PHASE_PRODUCTION_BUILD?resolveDeployEnvironment(process.env):null;return{apiKey:apiKey??null,apiUrl,buildId:commitSha,environment,publicKey:publicKey??null,releaseSlug:slug}}async function runBuildReleasePipeline(context,config){let{apiKey,buildId,releaseSlug}=config;if(!apiKey)return log.fatal(`API key not set`,[`withInterfere() requires an API key to publish release metadata during production builds.`,`Set the INTERFERE_API_KEY environment variable, or remove withInterfere() from your Next.js config.`,`See: https://support.interfere.com/docs/guides/next-js`]);if(!(buildId&&releaseSlug))return log.fatal(`Commit SHA missing`,[`Could not resolve a commit SHA from environment variables or git.`,"Set `INTERFERE_SOURCE_ID`, ensure your CI exposes its git SHA env var (e.g. `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`), or run inside a git checkout."]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey,buildId,releaseSlug});if(!result.ready){log.warn(`Skipping`,[`No source maps found`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config?.org.slug}/surfaces/${result.config?.surface.slug}`,`Release: ${result.release?.destination.slug??`Unknown`}`,`Build: ${result.buildId}`,`Artifacts: ${result.fileCount} source maps`])}catch(error){let message=error instanceof Error?error.message:String(error);log.warn(`Skipping release pipeline`,[`Interfere failed to process this release.`,`Events from this release will be ingested, but ignored as evidence when generating insights.`,`Please check https://status.interfere.com for any outages, or contact support@interfere.com if the problem persists.`,`Error: ${message}`]);return}}export{withInterfere};
@@ -7,6 +7,7 @@ declare const close: () => Promise<void>, consent: {
7
7
  get(): import("@interfere/types/sdk/plugins/manifest").ConsentState | null;
8
8
  set(value?: import("@interfere/types/sdk/plugins/manifest").ConsentState): void;
9
9
  }, getKernel: () => import("@interfere/react/internal/kernel").Kernel, getKernelOrNull: () => import("@interfere/react/internal/kernel").Kernel | null, identity: {
10
+ clear(): void;
10
11
  get(): import("@interfere/types/sdk/identify").IdentifyParams | null;
11
12
  set(params: import("@interfere/types/sdk/identify").IdentifyParams): Promise<void>;
12
13
  }, init: (opts?: import("@interfere/react/internal/kernel").KernelOptions) => Promise<import("@interfere/react/internal/kernel").Kernel | null>, subscribeToKernel: (listener: () => void) => () => void;
@@ -1,12 +1,14 @@
1
1
  import { ServerInstrumentationOptions } from "./internal/server/instrumentation-options.mjs";
2
+ import { onRequestError } from "./internal/server/capture.mjs";
2
3
  import { PrerenderSafeIdGenerator } from "./internal/server/id-generator.mjs";
3
4
  import { AttributeValue, TextMapPropagator } from "@opentelemetry/api";
5
+ import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
4
6
  import { Instrumentation } from "@opentelemetry/instrumentation";
5
7
  import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
6
8
  import { MetricReader } from "@opentelemetry/sdk-metrics";
7
- import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
8
9
 
9
10
  //#region src/instrumentation.d.ts
11
+ declare function flush(): Promise<void>;
10
12
  /**
11
13
  * Composable building blocks the Interfere SDK contributes to OTel —
12
14
  * span / log / metric processors, baggage propagator, undici
@@ -131,4 +133,4 @@ declare function buildInterfereOtelKit(opts?: ServerInstrumentationOptions): Int
131
133
  */
132
134
  declare function register(opts?: ServerInstrumentationOptions): Promise<void>;
133
135
  //#endregion
134
- export { InterfereOtelKit, PrerenderSafeIdGenerator, type ServerInstrumentationOptions, buildInterfereOtelKit, register };
136
+ export { InterfereOtelKit, PrerenderSafeIdGenerator, type ServerInstrumentationOptions, buildInterfereOtelKit, flush, onRequestError, register };
@@ -1,35 +1,28 @@
1
1
  import { ServerInstrumentationOptions } from "./internal/server/instrumentation-options.mjs";
2
+ import { onRequestError } from "./internal/server/capture.mjs";
2
3
  import { InterfereOtelKit } from "./instrumentation.mjs";
3
4
 
4
5
  //#region src/instrumentation.edge.d.ts
5
6
  /**
6
- * Edge-runtime entry for `@interfere/next/instrumentation`. Selected by
7
- * `package.json`'s `edge-light` / `workerd` / `worker` export conditions
8
- * Next.js + Vercel pick this bundle when compiling
9
- * `instrumentation.ts` for the Edge runtime.
7
+ * Edge-runtime bootstrap for `@interfere/next/instrumentation`. Selected by
8
+ * `package.json`'s `edge-light` / `workerd` / `worker` export conditions when
9
+ * Next.js compiles `instrumentation.ts` for the Edge runtime.
10
10
  *
11
- * The Node entry (`instrumentation.ts`) is built around
12
- * `NodeTracerProvider`, `AsyncLocalStorageContextManager`, undici
13
- * instrumentation, and a console→OTel bridge none of which work in
14
- * the Edge runtime. Pulling that import graph into an Edge bundle would
15
- * (a) trip Next's `node:*`-in-edge warnings (release-slug derivation
16
- * uses `node:child_process` + `node:crypto`) and (b) crash on module
17
- * load if the Edge bundle were ever actually invoked.
11
+ * The Node entry's pipeline (`NodeTracerProvider`, `AsyncLocalStorage`, undici,
12
+ * console bridge, `node:child_process`/`node:crypto` release-slug derivation)
13
+ * can't run on edge, so this builds an edge-safe pipeline instead: a
14
+ * `BasicTracerProvider` + `SimpleSpanProcessor` exporting via {@link FetchTraceExporter}.
15
+ * `onRequestError` then records middleware / edge route-handler errors onto it.
18
16
  *
19
- * Both `register()` and `buildInterfereOtelKit()` are exported as
20
- * no-op stubs so customer TypeScript code compiles unchanged
21
- * regardless of which runtime the file ends up in. Edge-runtime
22
- * tracing (middleware spans, edge route handlers) needs an
23
- * `@opentelemetry/sdk-trace-base`-based pipeline with a fetch-based
24
- * exporter — tracked separately.
17
+ * Release slug is `LOCAL_DEV_RELEASE_SLUG` under force-enable (dev); a
18
+ * build-baked slug for production edge is a follow-up.
25
19
  */
26
- declare function register(_opts?: ServerInstrumentationOptions): Promise<void>;
20
+ declare function register(opts?: ServerInstrumentationOptions): Promise<void>;
27
21
  /**
28
- * Edge stub for the kit factory. Returns `null` so callers spreading
29
- * `kit?.spanProcessors ?? []` into a host bootstrap behave as if the
30
- * SDK is unconfigured same shape as the Node entry returns when
31
- * `INTERFERE_PUBLIC_KEY` is unset.
22
+ * Edge stub for the kit factory. Composition into a host OTel bootstrap isn't
23
+ * supported on edge yet; returns `null` so callers spreading
24
+ * `kit?.spanProcessors ?? []` behave as if the SDK is unconfigured.
32
25
  */
33
26
  declare function buildInterfereOtelKit(_opts?: ServerInstrumentationOptions): null;
34
27
  //#endregion
35
- export { type InterfereOtelKit, type ServerInstrumentationOptions, buildInterfereOtelKit, register };
28
+ export { type InterfereOtelKit, type ServerInstrumentationOptions, buildInterfereOtelKit, onRequestError, register };
@@ -1 +1 @@
1
- function register(_opts={}){return Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,register};
1
+ import{readInterfereEnv}from"./internal/env.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{FetchTraceExporter}from"./internal/server/edge-exporter.mjs";import{withPublicKey}from"./internal/url.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{trace}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;function register(opts={}){if(registered)return Promise.resolve();let env=readInterfereEnv();if(!env.publicKey)return Promise.resolve();let releaseSlug=process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE?LOCAL_DEV_RELEASE_SLUG:null;releaseSlug||console.warn(`[interfere] edge runtime has no build-baked release.slug; the collector will reject edge spans until one is wired (production follow-up).`);let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),provider=new BasicTracerProvider({resource:resourceFromAttributes({"service.name":opts.serviceName??`interfere-sdk-next-edge`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}}),spanProcessors:[new SimpleSpanProcessor(new FetchTraceExporter(sinkUrl,{"x-interfere-producer-version":PRODUCER_VERSION}))]});return trace.setGlobalTracerProvider(provider),registered=!0,Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,onRequestError,register};
@@ -1 +1 @@
1
- import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{withPublicKey}from"./internal/url.mjs";import{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{metrics,propagation}from"@opentelemetry/api";import{BaggageSpanProcessor}from"@opentelemetry/baggage-span-processor";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{OTLPLogExporter}from"@opentelemetry/exporter-logs-otlp-http";import{AggregationTemporalityPreference,OTLPMetricExporter}from"@opentelemetry/exporter-metrics-otlp-http";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{UndiciInstrumentation}from"@opentelemetry/instrumentation-undici";import{resourceFromAttributes}from"@opentelemetry/resources";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";let registered=!1;function matchesAny(url,patterns){for(let pattern of patterns)if(pattern instanceof RegExp?pattern.test(url):url.includes(pattern))return!0;return!1}function buildInterfereOtelKit(opts={}){let env=readInterfereEnv();if(!env.publicKey)return null;let{slug:releaseSlug}=resolveReleaseSlug();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.");let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),ignoreUrls=[sinkUrl,...opts.ignoreUrls??[]],propagateContextUrls=opts.propagateContextUrls??[],exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION};return{resourceAttributes:{"service.name":opts.serviceName??`interfere-sdk-next-server`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}},spanProcessors:[new BaggageSpanProcessor(()=>!0),new BatchSpanProcessor(new OTLPTraceExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalSpanProcessors??[]],logRecordProcessors:[new BatchLogRecordProcessor(new OTLPLogExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalLogRecordProcessors??[]],metricReaders:[new PeriodicExportingMetricReader({exporter:new OTLPMetricExporter({url:sinkUrl,headers:exporterHeaders,temporalityPreference:AggregationTemporalityPreference.DELTA}),exportIntervalMillis:3e4}),...opts._internalAdditionalMetricReaders??[]],propagators:[new W3CBaggagePropagator],instrumentations:[new UndiciInstrumentation({ignoreRequestHook:req=>matchesAny(`${req.origin}${req.path}`,ignoreUrls),requestHook:(span,req)=>{matchesAny(`${req.origin}${req.path}`,propagateContextUrls)&&span.setAttribute(`interfere.propagated`,!0)}})],enableConsoleBridge:()=>bridgeConsoleToOtel(),fetchRemoteConfig:()=>fetchAndCacheRemoteConfig()}}async function register(opts={}){if(registered)return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes),tracerProvider=new NodeTracerProvider({resource,spanProcessors:kit.spanProcessors,idGenerator:new PrerenderSafeIdGenerator});propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,...kit.propagators]})),tracerProvider.register({contextManager:new AsyncLocalStorageContextManager().enable()});let loggerProvider=new LoggerProvider({resource,processors:kit.logRecordProcessors}),{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider);let meterProvider=new MeterProvider({resource,readers:kit.metricReaders});metrics.setGlobalMeterProvider(meterProvider),registerInstrumentations({tracerProvider,meterProvider,instrumentations:kit.instrumentations}),opts.consoleBridge!==!1&&kit.enableConsoleBridge(),registered=!0,await kit.fetchRemoteConfig()}export{PrerenderSafeIdGenerator,buildInterfereOtelKit,register};
1
+ import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{withPublicKey}from"./internal/url.mjs";import{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{installGlobalHandlers}from"./internal/server/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{metrics,propagation}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{BaggageSpanProcessor}from"@opentelemetry/baggage-span-processor";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{OTLPLogExporter}from"@opentelemetry/exporter-logs-otlp-http";import{AggregationTemporalityPreference,OTLPMetricExporter}from"@opentelemetry/exporter-metrics-otlp-http";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{UndiciInstrumentation}from"@opentelemetry/instrumentation-undici";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";let registered=!1,tracerProvider=null,loggerProvider=null,meterProvider=null;async function flush(){await Promise.all([tracerProvider?.forceFlush(),loggerProvider?.forceFlush(),meterProvider?.forceFlush()])}function matchesAny(url,patterns){for(let pattern of patterns)if(pattern instanceof RegExp?pattern.test(url):url.includes(pattern))return!0;return!1}function buildInterfereOtelKit(opts={}){let env=readInterfereEnv();if(!env.publicKey)return null;let{slug:releaseSlug}=resolveReleaseSlug();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.");let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),ignoreUrls=[sinkUrl,...opts.ignoreUrls??[]],propagateContextUrls=opts.propagateContextUrls??[],exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION};return{resourceAttributes:{"service.name":opts.serviceName??`interfere-sdk-next-server`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}},spanProcessors:[new BaggageSpanProcessor(()=>!0),new BatchSpanProcessor(new OTLPTraceExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalSpanProcessors??[]],logRecordProcessors:[new BatchLogRecordProcessor(new OTLPLogExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalLogRecordProcessors??[]],metricReaders:[new PeriodicExportingMetricReader({exporter:new OTLPMetricExporter({url:sinkUrl,headers:exporterHeaders,temporalityPreference:AggregationTemporalityPreference.DELTA}),exportIntervalMillis:3e4}),...opts._internalAdditionalMetricReaders??[]],propagators:[new W3CBaggagePropagator],instrumentations:[new UndiciInstrumentation({ignoreRequestHook:req=>matchesAny(`${req.origin}${req.path}`,ignoreUrls),requestHook:(span,req)=>{matchesAny(`${req.origin}${req.path}`,propagateContextUrls)&&span.setAttribute(`interfere.propagated`,!0)}})],enableConsoleBridge:()=>bridgeConsoleToOtel(),fetchRemoteConfig:()=>fetchAndCacheRemoteConfig()}}async function register(opts={}){if(registered)return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes);tracerProvider=new NodeTracerProvider({resource,spanProcessors:kit.spanProcessors,idGenerator:new PrerenderSafeIdGenerator}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,...kit.propagators]})),tracerProvider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),loggerProvider=new LoggerProvider({resource,processors:kit.logRecordProcessors});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),meterProvider=new MeterProvider({resource,readers:kit.metricReaders}),metrics.setGlobalMeterProvider(meterProvider),registerInstrumentations({tracerProvider,meterProvider,instrumentations:kit.instrumentations}),opts.consoleBridge!==!1&&kit.enableConsoleBridge(),installGlobalHandlers({captureUncaughtException:opts.captureUncaughtException??!0,captureUnhandledRejection:opts.captureUnhandledRejection??!0,exitOnUncaughtException:process.env.NODE_ENV===`production`,flush}),registered=!0,await kit.fetchRemoteConfig()}export{PrerenderSafeIdGenerator,buildInterfereOtelKit,flush,onRequestError,register};
@@ -1 +1 @@
1
- import{existsSync}from"node:fs";import{basename,dirname,join,normalize,resolve}from"node:path";import{fileURLToPath}from"node:url";const INSTRUMENTATION_CLIENT_FILES=[`instrumentation-client.ts`,`instrumentation-client.tsx`,`instrumentation-client.js`,`instrumentation-client.jsx`,`src/instrumentation-client.ts`,`src/instrumentation-client.tsx`,`src/instrumentation-client.js`,`src/instrumentation-client.jsx`],LOADER_FILE_CANDIDATES=[`value-injection-loader.ts`,`value-injection-loader.js`,`value-injection-loader.mjs`,`value-injection-loader.cjs`];function configureBuild({existingWebpack,existingTurbopack,projectDir,values}){let instrumentationClientPath=resolveInstrumentationClientPath(projectDir);return{webpack:buildWebpackHook(existingWebpack,instrumentationClientPath,values),turbopack:buildTurbopackConfig(existingTurbopack,instrumentationClientPath,values)}}function hasInjectedMetadata(metadata){return metadata.__INTERFERE_RELEASE_SLUG__!==null||metadata.__INTERFERE_PUBLIC_KEY__!==void 0}function buildWebpackHook(existingWebpack,instrumentationClientPath,values){if(!(instrumentationClientPath&&hasInjectedMetadata(values)))return existingWebpack;let normalizedPath=normalize(instrumentationClientPath),loaderPath=resolveLoaderPath();return(webpackConfig,options)=>{let outputConfig=existingWebpack?existingWebpack(webpackConfig,options):webpackConfig,mutableConfig=outputConfig,injectionRule={test:resource=>!!resource&&normalize(resource)===normalizedPath,use:[{loader:loaderPath,options:{values}}]};return mutableConfig.module??={},mutableConfig.module.rules??=[],mutableConfig.module.rules.push(injectionRule),outputConfig}}function buildTurbopackConfig(existingTurbopack,instrumentationClientPath,values){let baseConfig=existingTurbopack??{},debugIds=baseConfig.debugIds??!0;if(!(instrumentationClientPath&&hasInjectedMetadata(values)))return existingTurbopack===void 0&&debugIds===baseConfig.debugIds?existingTurbopack:{...baseConfig,debugIds};let ruleKey=`**/${basename(instrumentationClientPath)}`,existingRule=baseConfig.rules?.[ruleKey]??{},existingLoaders=Array.isArray(existingRule.loaders)?existingRule.loaders:[];return{...baseConfig,debugIds,rules:{...baseConfig.rules,[ruleKey]:{...existingRule,loaders:[...existingLoaders,{loader:resolveLoaderPath(),options:{serializedValues:JSON.stringify(values)}}]}}}}function resolveInstrumentationClientPath(projectDir){for(let candidate of INSTRUMENTATION_CLIENT_FILES){let filePath=resolve(projectDir,candidate);if(existsSync(filePath))return filePath}return null}function resolveLoaderPath(){let directory=resolve(dirname(fileURLToPath(import.meta.url)));for(let candidate of LOADER_FILE_CANDIDATES){let filePath=join(directory,candidate);if(existsSync(filePath))return filePath}return join(directory,`value-injection-loader.js`)}export{configureBuild};
1
+ import{existsSync}from"node:fs";import{basename,dirname,join,normalize,resolve}from"node:path";import{fileURLToPath}from"node:url";const INSTRUMENTATION_CLIENT_FILES=[`instrumentation-client.ts`,`instrumentation-client.tsx`,`instrumentation-client.js`,`instrumentation-client.jsx`,`src/instrumentation-client.ts`,`src/instrumentation-client.tsx`,`src/instrumentation-client.js`,`src/instrumentation-client.jsx`],INSTRUMENTATION_SERVER_FILES=[`instrumentation.ts`,`instrumentation.tsx`,`instrumentation.js`,`instrumentation.jsx`,`src/instrumentation.ts`,`src/instrumentation.tsx`,`src/instrumentation.js`,`src/instrumentation.jsx`],LOADER_FILE_CANDIDATES=[`value-injection-loader.ts`,`value-injection-loader.js`,`value-injection-loader.mjs`,`value-injection-loader.cjs`];function configureBuild({existingWebpack,existingTurbopack,projectDir,values}){let clientPath=resolveFirstExisting(projectDir,INSTRUMENTATION_CLIENT_FILES),targets=[clientPath,resolveFirstExisting(projectDir,INSTRUMENTATION_SERVER_FILES)].filter(path=>path!==null);return{webpack:buildWebpackHook(existingWebpack,clientPath,targets,values),turbopack:buildTurbopackConfig(existingTurbopack,clientPath,targets,values)}}function hasInjectedMetadata(metadata){return metadata.__INTERFERE_RELEASE_SLUG__!==null||metadata.__INTERFERE_PUBLIC_KEY__!==void 0}function buildWebpackHook(existingWebpack,clientPath,targets,values){if(!(clientPath&&hasInjectedMetadata(values)))return existingWebpack;let normalizedTargets=new Set(targets.map(path=>normalize(path))),loaderPath=resolveLoaderPath();return(webpackConfig,options)=>{let outputConfig=existingWebpack?existingWebpack(webpackConfig,options):webpackConfig,mutableConfig=outputConfig,injectionRule={test:resource=>!!resource&&normalizedTargets.has(normalize(resource)),use:[{loader:loaderPath,options:{values}}]};return mutableConfig.module??={},mutableConfig.module.rules??=[],mutableConfig.module.rules.push(injectionRule),outputConfig}}function buildTurbopackConfig(existingTurbopack,clientPath,targets,values){let baseConfig=existingTurbopack??{},debugIds=baseConfig.debugIds??!0;if(!(clientPath&&hasInjectedMetadata(values)))return existingTurbopack===void 0&&debugIds===baseConfig.debugIds?existingTurbopack:{...baseConfig,debugIds};let loaderPath=resolveLoaderPath(),rules={...baseConfig.rules};for(let target of targets){let ruleKey=`**/${basename(target)}`,existingRule=rules[ruleKey]??{},existingLoaders=Array.isArray(existingRule.loaders)?existingRule.loaders:[];rules[ruleKey]={...existingRule,loaders:[...existingLoaders,{loader:loaderPath,options:{serializedValues:JSON.stringify(values)}}]}}return{...baseConfig,debugIds,rules}}function resolveFirstExisting(projectDir,candidates){for(let candidate of candidates){let filePath=resolve(projectDir,candidate);if(existsSync(filePath))return filePath}return null}function resolveLoaderPath(){let directory=resolve(dirname(fileURLToPath(import.meta.url)));for(let candidate of LOADER_FILE_CANDIDATES){let filePath=join(directory,candidate);if(existsSync(filePath))return filePath}return join(directory,`value-injection-loader.js`)}export{configureBuild};
@@ -1,7 +1,6 @@
1
1
  import { ProductionCompileContext, ResolvedBuildConfig } from "../../config.mjs";
2
2
  import { ReleaseSlug } from "@interfere/types/releases/slug";
3
3
  import { ReleasesConfigResponse } from "@interfere/sdk/models/releases-config-response.js";
4
- import { CreateReleaseResponse } from "@interfere/types/releases/definition";
5
4
 
6
5
  //#region src/internal/build/pipeline.d.ts
7
6
  interface BuildTiming {
@@ -14,18 +13,6 @@ interface BuildTiming {
14
13
  totalBytes: number;
15
14
  upload: number;
16
15
  }
17
- type PipelineResult = {
18
- ready: false;
19
- reason: "no_source_maps";
20
- fileCount: 0;
21
- } | {
22
- ready: true;
23
- fileCount: number;
24
- release: CreateReleaseResponse;
25
- config: ReleasesConfigResponse;
26
- buildId: string;
27
- timing: BuildTiming;
28
- };
29
16
  declare function runBuildPipeline(context: ProductionCompileContext, metadata: ResolvedBuildConfig & {
30
17
  apiKey: string;
31
18
  buildId: string;
@@ -58,4 +45,4 @@ declare function runBuildPipeline(context: ProductionCompileContext, metadata: R
58
45
  timing: BuildTiming;
59
46
  }>;
60
47
  //#endregion
61
- export { BuildTiming, PipelineResult, runBuildPipeline };
48
+ export { runBuildPipeline };
@@ -0,0 +1,7 @@
1
+ import { ReleaseSlug } from "@interfere/types/releases/slug";
2
+ import { ReleaseDestinationMetadata } from "@interfere/types/integrations";
3
+
4
+ //#region src/internal/build/release/destinations/cli.d.ts
5
+ declare function resolve(releaseSlug: ReleaseSlug): ReleaseDestinationMetadata;
6
+ //#endregion
7
+ export { resolve };
@@ -0,0 +1 @@
1
+ import{resolveCliReleaseDestination}from"@interfere/types/sdk/cli-destination";function resolve(releaseSlug){return resolveCliReleaseDestination(process.env,releaseSlug)}export{resolve};
@@ -0,0 +1,6 @@
1
+ import { ReleaseDestinationMetadata } from "@interfere/types/integrations";
2
+
3
+ //#region src/internal/build/release/destinations/cloudflare.d.ts
4
+ declare function resolve(): ReleaseDestinationMetadata;
5
+ //#endregion
6
+ export { resolve };
@@ -0,0 +1 @@
1
+ import{parseEnvValue}from"@interfere/types/sdk/env";function parseVersionMetadata(){let raw=parseEnvValue(process.env.CF_VERSION_METADATA);if(!raw)return null;try{return JSON.parse(raw)}catch{return null}}function resolveDeploymentUrl(){let candidate=parseEnvValue(process.env.CF_PAGES_URL)??parseEnvValue(process.env.WORKERS_CI_DEPLOY_URL);return candidate===null?null:candidate.startsWith(`https://`)||candidate.startsWith(`http://`)?candidate:`https://${candidate}`}function resolve(){let versionMetadata=parseVersionMetadata(),versionId=parseEnvValue(process.env.CF_VERSION_ID)??versionMetadata?.id??parseEnvValue(process.env.WORKERS_CI_BUILD_UUID)??null,scriptName=parseEnvValue(process.env.CF_WORKER_NAME)??parseEnvValue(process.env.WORKERS_CI_SCRIPT_NAME)??null,accountId=parseEnvValue(process.env.CF_ACCOUNT_ID)??parseEnvValue(process.env.CLOUDFLARE_ACCOUNT_ID)??null,environment=parseEnvValue(process.env.WORKERS_CI_BRANCH)??versionMetadata?.tag??null;return{provider:`cloudflare`,destinationReleaseId:versionId,environment,versionId,scriptName,accountId,deploymentUrl:resolveDeploymentUrl(),environmentName:environment,environmentTarget:environment}}export{resolve};
@@ -1,4 +1,5 @@
1
- import { DestinationProvider, ReleaseDestinationMetadata } from "@interfere/types/integrations";
1
+ import { ReleaseSlug } from "@interfere/types/releases/slug";
2
+ import { ReleaseDestinationMetadata, ReleaseDestinationProvider } from "@interfere/types/integrations";
2
3
 
3
4
  //#region src/internal/build/release/destinations/index.d.ts
4
5
  /**
@@ -6,9 +7,9 @@ import { DestinationProvider, ReleaseDestinationMetadata } from "@interfere/type
6
7
  * each entry knows how to read its platform's deployment env vars and
7
8
  * returns a `ReleaseDestinationMetadata` blob.
8
9
  *
9
- * Adding a new destination (cloudflare-pages, fly, render, …) is one
10
- * new file under this directory + one entry here.
10
+ * Adding a new destination (fly, render, …) is one new file under this
11
+ * directory + one entry here.
11
12
  */
12
- declare const destinations: Record<DestinationProvider, () => ReleaseDestinationMetadata>;
13
+ declare const destinations: Record<ReleaseDestinationProvider, (releaseSlug: ReleaseSlug) => ReleaseDestinationMetadata>;
13
14
  //#endregion
14
15
  export { destinations };
@@ -1 +1 @@
1
- import{resolve}from"./vercel.mjs";const destinations={vercel:resolve};export{destinations};
1
+ import{resolve}from"./cli.mjs";import{resolve as resolve$1}from"./cloudflare.mjs";import{resolve as resolve$2}from"./vercel.mjs";const destinations={vercel:resolve$2,cloudflare:resolve$1,cli:resolve};export{destinations};
@@ -1 +1 @@
1
- import{log}from"../../logger.mjs";import{PRODUCER_VERSION}from"../../version.mjs";import{destinations}from"./destinations/index.mjs";import{sources}from"./sources/index.mjs";import{destinationProviderSchema}from"@interfere/types/integrations";function resolveReleaseRequest(buildId,releaseSlug,config){if(!config.surface.sourceProvider)return log.fatal(`Missing version control provider`,[`This surface does not have a version control provider configured.`,`Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`]);let destinationProvider=destinationProviderSchema.safeParse(config.surface.destinationProvider);return destinationProvider.success?{source:sources[config.surface.sourceProvider](),destination:destinations[destinationProvider.data](),buildId,slug:releaseSlug,producerVersion:PRODUCER_VERSION}:log.fatal(`Missing deployment target`,[`This surface does not have a deployment target integration configured.`,`Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`])}export{resolveReleaseRequest};
1
+ import{log}from"../../logger.mjs";import{PRODUCER_VERSION}from"../../version.mjs";import{destinations}from"./destinations/index.mjs";import{sources}from"./sources/index.mjs";import{releaseDestinationProviderSchema}from"@interfere/types/integrations";function resolveReleaseRequest(buildId,releaseSlug,config){if(!config.surface.sourceProvider)return log.fatal(`Missing version control provider`,[`This surface does not have a version control provider configured.`,`Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`]);let destinationProvider=releaseDestinationProviderSchema.safeParse(config.surface.destinationProvider);return destinationProvider.success?{source:sources[config.surface.sourceProvider](),destination:destinations[destinationProvider.data](releaseSlug),buildId,slug:releaseSlug,producerVersion:PRODUCER_VERSION}:log.fatal(`Missing deployment target`,[`This surface does not have a deployment target integration configured.`,`Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`])}export{resolveReleaseRequest};
@@ -1,12 +1,6 @@
1
1
  import { SourceMapFile } from "./discover.mjs";
2
2
 
3
3
  //#region src/internal/build/source-maps/discover-webpack.d.ts
4
- /**
5
- * Reads an existing `//# debugId=…` comment from the bundle source.
6
- * Returns `null` when no debug ID comment is present — the caller is
7
- * expected to generate and inject one.
8
- */
9
- declare function readDebugIdFromJs(content: string): string | null;
10
4
  /**
11
5
  * Inserts a `//# debugId=…` comment into the chunk. Idempotent: if a
12
6
  * spec-compliant comment is already present (e.g. webpack 5.104+ with
@@ -50,4 +44,4 @@ declare function discoverWebpack(opts: {
50
44
  sourceFileCount: number;
51
45
  }>;
52
46
  //#endregion
53
- export { discoverWebpack, injectDebugIdIntoJs, injectDebugIdIntoMap, readDebugIdFromJs };
47
+ export { discoverWebpack, injectDebugIdIntoJs, injectDebugIdIntoMap };
@@ -1 +1 @@
1
- import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash,randomUUID}from"node:crypto";import{readFile,writeFile}from"node:fs/promises";const SOURCEMAPPING_RE=/\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/,SPEC_LAST_DEBUG_ID_RE=/\/\/# debugId=([a-fA-F0-9-]+)(?![\s\S]*\/\/# debugId=)/m;function readDebugIdFromJs(content){return SPEC_LAST_DEBUG_ID_RE.exec(content)?.[1]??null}function injectDebugIdIntoJs(content,debugId){let debugIdComment=`//# debugId=${debugId}`;if(SPEC_LAST_DEBUG_ID_RE.test(content))return content.replace(SPEC_LAST_DEBUG_ID_RE,debugIdComment);let match=SOURCEMAPPING_RE.exec(content);return match?`${content.slice(0,match.index).trimEnd()}\n${debugIdComment}\n${match[0]}`:`${content}\n${debugIdComment}`}function injectDebugIdIntoMap(content,debugId){let json=JSON.parse(content);return json.debugId=debugId,JSON.stringify(json)}async function discoverWebpack(opts){let absDistDir=resolveDistDir(opts.projectDir,opts.distDir),relDistDir=relative(opts.projectDir,absDistDir),jsPaths=await walkDistTrees(absDistDir,`.js`),pairs=new Map;for(let jsAbs of jsPaths){let content=await readFile(jsAbs,`utf8`),match=SOURCEMAPPING_RE.exec(content);if(!match?.[1])continue;let mapAbs=`${jsAbs.slice(0,jsAbs.lastIndexOf(`/`)+1)}${decodeURIComponent(match[1])}`,jsPublic=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir),mapPublic=toPublicPath(relative(opts.projectDir,mapAbs),relDistDir);pairs.set(mapPublic,{mapAbs,jsAbs,jsPublic,jsContent:content,existingDebugId:readDebugIdFromJs(content)})}return{files:(await Promise.all(Array.from(pairs.entries(),async([mapPublic,pair])=>{let mapContent=await readFile(pair.mapAbs,`utf8`).catch(()=>null);if(mapContent===null)return null;let debugId=pair.existingDebugId??randomUUID();if(!pair.existingDebugId){let injectedJs=injectDebugIdIntoJs(pair.jsContent,debugId);await writeFile(pair.jsAbs,injectedJs,`utf8`)}let finalMap=JSON.parse(mapContent).debugId===debugId?mapContent:injectDebugIdIntoMap(mapContent,debugId);return finalMap!==mapContent&&await writeFile(pair.mapAbs,finalMap,`utf8`),{absolute:pair.mapAbs,path:mapPublic,content:finalMap,hash:createHash(`sha256`).update(finalMap).digest(`hex`),debugId,chunkUrl:pair.jsPublic}}))).filter(f=>f!==null),sourceFileCount:pairs.size}}export{discoverWebpack,injectDebugIdIntoJs,injectDebugIdIntoMap,readDebugIdFromJs};
1
+ import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash,randomUUID}from"node:crypto";import{readFile,writeFile}from"node:fs/promises";const SOURCEMAPPING_RE=/\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/,SPEC_LAST_DEBUG_ID_RE=/\/\/# debugId=([a-fA-F0-9-]+)(?![\s\S]*\/\/# debugId=)/m;function readDebugIdFromJs(content){return SPEC_LAST_DEBUG_ID_RE.exec(content)?.[1]??null}function injectDebugIdIntoJs(content,debugId){let debugIdComment=`//# debugId=${debugId}`;if(SPEC_LAST_DEBUG_ID_RE.test(content))return content.replace(SPEC_LAST_DEBUG_ID_RE,debugIdComment);let match=SOURCEMAPPING_RE.exec(content);return match?`${content.slice(0,match.index).trimEnd()}\n${debugIdComment}\n${match[0]}`:`${content}\n${debugIdComment}`}function injectDebugIdIntoMap(content,debugId){let json=JSON.parse(content);return json.debugId=debugId,JSON.stringify(json)}async function discoverWebpack(opts){let absDistDir=resolveDistDir(opts.projectDir,opts.distDir),relDistDir=relative(opts.projectDir,absDistDir),jsPaths=await walkDistTrees(absDistDir,`.js`),pairs=new Map;for(let jsAbs of jsPaths){let content=await readFile(jsAbs,`utf8`),match=SOURCEMAPPING_RE.exec(content);if(!match?.[1])continue;let mapAbs=`${jsAbs.slice(0,jsAbs.lastIndexOf(`/`)+1)}${decodeURIComponent(match[1])}`,jsPublic=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir),mapPublic=toPublicPath(relative(opts.projectDir,mapAbs),relDistDir);pairs.set(mapPublic,{mapAbs,jsAbs,jsPublic,jsContent:content,existingDebugId:readDebugIdFromJs(content)})}return{files:(await Promise.all(Array.from(pairs.entries(),async([mapPublic,pair])=>{let mapContent=await readFile(pair.mapAbs,`utf8`).catch(()=>null);if(mapContent===null)return null;let debugId=pair.existingDebugId??randomUUID();if(!pair.existingDebugId){let injectedJs=injectDebugIdIntoJs(pair.jsContent,debugId);await writeFile(pair.jsAbs,injectedJs,`utf8`)}let finalMap=JSON.parse(mapContent).debugId===debugId?mapContent:injectDebugIdIntoMap(mapContent,debugId);return finalMap!==mapContent&&await writeFile(pair.mapAbs,finalMap,`utf8`),{absolute:pair.mapAbs,path:mapPublic,content:finalMap,hash:createHash(`sha256`).update(finalMap).digest(`hex`),debugId,chunkUrl:pair.jsPublic}}))).filter(f=>f!==null),sourceFileCount:pairs.size}}export{discoverWebpack,injectDebugIdIntoJs,injectDebugIdIntoMap};
@@ -1 +1 @@
1
- import{parseEnvValue}from"@interfere/types/sdk/env";import{API_URL}from"@interfere/constants/api";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return process.env.NODE_ENV===`production`?!0:!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function readInterfereEnv(){let nodeEnvironment=resolveEnvironment({env:process.env});return{apiKey:parseEnvValue(process.env.INTERFERE_API_KEY),apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,nextRuntime:parseEnvValue(process.env.NEXT_RUNTIME),nodeEnvironment,publicKey:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY),release:{sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID),destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID)}}}export{isEnabledOnServer,readInterfereEnv};
1
+ import{API_URL}from"@interfere/constants/api";import{parseEnvValue}from"@interfere/types/sdk/env";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return process.env.NODE_ENV===`production`?!0:!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function readInterfereEnv(){let nodeEnvironment=resolveEnvironment({env:process.env});return{apiKey:parseEnvValue(process.env.INTERFERE_API_KEY),apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,nextRuntime:parseEnvValue(process.env.NEXT_RUNTIME),nodeEnvironment,publicKey:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY),release:{sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID),destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID)}}}export{isEnabledOnServer,readInterfereEnv};
@@ -1,25 +1,9 @@
1
1
  import { ReleaseSlug } from "@interfere/types/releases/slug";
2
2
 
3
3
  //#region src/internal/release-slug.d.ts
4
- /**
5
- * Walks the same env keys at build time (`withInterfere`) and at runtime
6
- * (server-side `register()`), then falls back to `git rev-parse HEAD`. Both
7
- * call sites resolving the same SHA → both derive the same `release.slug`,
8
- * so server and client spans agree by construction.
9
- *
10
- * Override path for non-CI builds: set `INTERFERE_SOURCE_ID` (or any other
11
- * key in `releaseSourceIdEnvKeys`) on both the build env and the runtime
12
- * env. The `interfere.buildId` next.config knob was removed in 10.0 because
13
- * it only worked at build time and caused server/client slug drift.
14
- *
15
- * `runGitCommand` (`node:child_process`) keeps this module on the Node side
16
- * of `@interfere/next`'s dual entry — the edge entrypoint
17
- * (`instrumentation.edge.ts`) intentionally doesn't import this file.
18
- */
19
- declare function resolveCommitSha(): string | null;
20
4
  declare function resolveReleaseSlug(): {
21
5
  commitSha: string | null;
22
6
  slug: ReleaseSlug | null;
23
7
  };
24
8
  //#endregion
25
- export { resolveCommitSha, resolveReleaseSlug };
9
+ export { resolveReleaseSlug };
@@ -1 +1 @@
1
- import{runGitCommand}from"./build/release/git.mjs";import{deriveReleaseSlug}from"@interfere/types/releases/slug";import{readFirstEnvValue}from"@interfere/types/sdk/env";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";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};
1
+ import{runGitCommand}from"./build/release/git.mjs";import{deriveReleaseSlug,releaseSlugSchema}from"@interfere/types/releases/slug";import{readFirstEnvValue}from"@interfere/types/sdk/env";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";function readBakedReleaseSlug(){let value=globalThis.__INTERFERE_RELEASE_SLUG__;if(typeof value!=`string`)return null;let parsed=releaseSlugSchema.safeParse(value);return parsed.success?parsed.data:null}function resolveCommitSha(){return readFirstEnvValue(process.env,releaseSourceIdEnvKeys)??runGitCommand(`git rev-parse HEAD`)}function resolveReleaseSlug(){let baked=readBakedReleaseSlug();if(baked)return{commitSha:null,slug:baked};let commitSha=resolveCommitSha();return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
@@ -1,14 +1,6 @@
1
1
  import { InterfereEnv } from "../env.mjs";
2
2
 
3
3
  //#region src/internal/route/proxy.d.ts
4
- /**
5
- * Clone the inbound request's headers, removing entries that should never
6
- * cross the proxy boundary. Returns a mutable `Headers` so callers can
7
- * `.set()`/`.delete()` upstream-specific values without having to merge
8
- * record literals (which lose case-insensitivity and create the precedence
9
- * footguns that masked the 2026-05-01 incident in code review).
10
- */
11
- declare function forwardedHeaders(request: Request): Headers;
12
4
  interface AuthenticatedEnv {
13
5
  apiUrl: string;
14
6
  publicKey: string | null;
@@ -19,22 +11,10 @@ declare function resolveAuthenticatedEnv(): AuthenticatedEnv;
19
11
  declare function hasPublicKeyCredential(request: Request, envPublicKey: string | null): boolean;
20
12
  declare function notConfiguredResponse(): Response;
21
13
  declare function extractSubPath(request: Request): string;
22
- /**
23
- * Preserve any query string verbatim when forwarding upstream.
24
- *
25
- * The browser SDK uses `?_pv=…` and `?pk=…` (and `?_pt=…` in direct
26
- * mode) as a `navigator.sendBeacon` fallback when `visibilitychange→
27
- * hidden` aborts the keepalive fetch path. The collector's
28
- * `otlpProducerGuard` and `surfacePkAuth` accept those values verbatim
29
- * — but only if the proxy actually forwards them. Passing
30
- * `request.url`'s `search` through (`""` when absent, leading `?`
31
- * when present) keeps the rewrite transparent.
32
- */
33
- declare function extractSearch(request: Request): string;
34
14
  declare function formatProxyError(error: unknown): {
35
15
  message: string;
36
16
  lines: string[];
37
17
  };
38
18
  declare function forwardToCollector(request: Request, env: AuthenticatedEnv, subPath: string): Promise<Response>;
39
19
  //#endregion
40
- export { AuthenticatedEnv, extractSearch, extractSubPath, formatProxyError, forwardToCollector, forwardedHeaders, hasPublicKeyCredential, notConfiguredResponse, resolveAuthenticatedEnv };
20
+ export { AuthenticatedEnv, extractSubPath, formatProxyError, forwardToCollector, hasPublicKeyCredential, notConfiguredResponse, resolveAuthenticatedEnv };
@@ -1 +1 @@
1
- import{readInterfereEnv}from"../env.mjs";import{log}from"../logger.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";import{extractCountryHeader}from"@interfere/types/sdk/geo";const STRIPPED_AUTH_HEADERS=[`authorization`,`x-api-key`,`x-interfere-api-key`],STRIPPED_TRANSPORT_HEADERS=[`host`,`content-length`,`connection`,`keep-alive`,`transfer-encoding`,`te`,`trailer`,`upgrade`,`proxy-authenticate`,`proxy-authorization`];function forwardedHeaders(request){let headers=new Headers(request.headers);for(let name of STRIPPED_AUTH_HEADERS)headers.delete(name);for(let name of STRIPPED_TRANSPORT_HEADERS)headers.delete(name);return headers}function resolveAuthenticatedEnv(){let env=readInterfereEnv();return{apiUrl:env.apiUrl,publicKey:env.publicKey,release:env.release}}function hasPublicKeyCredential(request,envPublicKey){return envPublicKey?!0:new URL(request.url).searchParams.has(`pk`)}function notConfiguredResponse(){return log.warn(`Not configured`,[`INTERFERE_PUBLIC_KEY is not set and the request has no ?pk= query param. The proxy route will return 503.`]),Response.json({code:`INTERFERE_NOT_CONFIGURED`,message:`INTERFERE_PUBLIC_KEY is required.`},{status:503})}function extractSubPath(request){let prefix=resolveRoutePrefix(),url=new URL(request.url),idx=url.pathname.indexOf(prefix);if(idx===-1)return`/`;let remainder=url.pathname.slice(idx+prefix.length);return remainder===``?`/`:remainder.startsWith(`/`)?remainder:`/${remainder}`}function extractSearch(request){return new URL(request.url).search}function formatProxyError(error){if(!(error instanceof Error))return{message:String(error),lines:[String(error)]};let lines=[`${error.name}: ${error.message}`];if(`cause`in error&&error.cause){let cause=error.cause instanceof Error?error.cause.message:String(error.cause);lines.push(`cause: ${cause}`)}return`code`in error&&typeof error.code==`string`&&lines.push(`code: ${error.code}`),{message:`${error.name}: ${error.message}`,lines}}function buildUpstreamUrl(apiUrl,subPath,request,publicKey){let incoming=new URL(request.url),upstream=new URL(`${apiUrl}${subPath}`);for(let[key,value]of incoming.searchParams)upstream.searchParams.append(key,value);return publicKey&&!upstream.searchParams.has(`pk`)&&upstream.searchParams.set(`pk`,publicKey),upstream.toString()}async function forwardToCollector(request,env,subPath){let url=buildUpstreamUrl(env.apiUrl,subPath,request,env.publicKey),body=request.method!==`GET`&&request.method!==`HEAD`?new Uint8Array(await request.arrayBuffer()):void 0,headers=forwardedHeaders(request);headers.has(`content-type`)||headers.set(`content-type`,`application/json`);let country=extractCountryHeader(request);country!==null&&headers.set(`x-country-code`,country);let upstream=await fetch(url,{...omitUndefined({body}),method:request.method,headers,signal:AbortSignal.timeout(1e4)});if(!upstream.ok){let text=await upstream.text().catch(()=>``);return upstream.status>=500?(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status})):upstream.status>=400?(log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_WARNING`,message:text},{status:upstream.status})):(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status}))}return new Response(upstream.body,{status:upstream.status,headers:{"content-type":upstream.headers.get(`content-type`)??`application/json`}})}export{extractSearch,extractSubPath,formatProxyError,forwardToCollector,forwardedHeaders,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv};
1
+ import{readInterfereEnv}from"../env.mjs";import{log}from"../logger.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";import{extractCountryHeader}from"@interfere/types/sdk/geo";const STRIPPED_AUTH_HEADERS=[`authorization`,`x-api-key`,`x-interfere-api-key`],STRIPPED_TRANSPORT_HEADERS=[`host`,`content-length`,`connection`,`keep-alive`,`transfer-encoding`,`te`,`trailer`,`upgrade`,`proxy-authenticate`,`proxy-authorization`];function forwardedHeaders(request){let headers=new Headers(request.headers);for(let name of STRIPPED_AUTH_HEADERS)headers.delete(name);for(let name of STRIPPED_TRANSPORT_HEADERS)headers.delete(name);return headers}function resolveAuthenticatedEnv(){let env=readInterfereEnv();return{apiUrl:env.apiUrl,publicKey:env.publicKey,release:env.release}}function hasPublicKeyCredential(request,envPublicKey){return envPublicKey?!0:new URL(request.url).searchParams.has(`pk`)}function notConfiguredResponse(){return log.warn(`Not configured`,[`INTERFERE_PUBLIC_KEY is not set and the request has no ?pk= query param. The proxy route will return 503.`]),Response.json({code:`INTERFERE_NOT_CONFIGURED`,message:`INTERFERE_PUBLIC_KEY is required.`},{status:503})}function extractSubPath(request){let prefix=resolveRoutePrefix(),url=new URL(request.url),idx=url.pathname.indexOf(prefix);if(idx===-1)return`/`;let remainder=url.pathname.slice(idx+prefix.length);return remainder===``?`/`:remainder.startsWith(`/`)?remainder:`/${remainder}`}function formatProxyError(error){if(!(error instanceof Error))return{message:String(error),lines:[String(error)]};let lines=[`${error.name}: ${error.message}`];if(`cause`in error&&error.cause){let cause=error.cause instanceof Error?error.cause.message:String(error.cause);lines.push(`cause: ${cause}`)}return`code`in error&&typeof error.code==`string`&&lines.push(`code: ${error.code}`),{message:`${error.name}: ${error.message}`,lines}}function buildUpstreamUrl(apiUrl,subPath,request,publicKey){let incoming=new URL(request.url),upstream=new URL(`${apiUrl}${subPath}`);for(let[key,value]of incoming.searchParams)upstream.searchParams.append(key,value);return publicKey&&!upstream.searchParams.has(`pk`)&&upstream.searchParams.set(`pk`,publicKey),upstream.toString()}async function forwardToCollector(request,env,subPath){let url=buildUpstreamUrl(env.apiUrl,subPath,request,env.publicKey),body=request.method!==`GET`&&request.method!==`HEAD`?new Uint8Array(await request.arrayBuffer()):void 0,headers=forwardedHeaders(request);headers.has(`content-type`)||headers.set(`content-type`,`application/json`);let country=extractCountryHeader(request);country!==null&&headers.set(`x-country-code`,country);let upstream=await fetch(url,{...omitUndefined({body}),method:request.method,headers,signal:AbortSignal.timeout(1e4)});if(!upstream.ok){let text=await upstream.text().catch(()=>``);return upstream.status>=500?(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status})):upstream.status>=400?(log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_WARNING`,message:text},{status:upstream.status})):(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status}))}return new Response(upstream.body,{status:upstream.status,headers:{"content-type":upstream.headers.get(`content-type`)??`application/json`}})}export{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv};
@@ -2,8 +2,13 @@ import { CaptureErrorContext, OnRequestErrorContext } from "./types.mjs";
2
2
 
3
3
  //#region src/internal/server/capture.d.ts
4
4
  declare function captureError(error: unknown, _request?: unknown, context?: CaptureErrorContext): void;
5
+ interface OnRequestErrorRequest {
6
+ readonly headers?: Record<string, string>;
7
+ readonly method?: string;
8
+ readonly path?: string;
9
+ }
5
10
  declare function onRequestError(error: Error & {
6
11
  digest?: string;
7
- }, _request: unknown, context: OnRequestErrorContext): void;
12
+ }, request: OnRequestErrorRequest | undefined, context: OnRequestErrorContext): void;
8
13
  //#endregion
9
14
  export { captureError, onRequestError };
@@ -1 +1 @@
1
- import{isEnabledOnServer}from"../env.mjs";import{isPluginEnabled}from"./remote-config.mjs";import{SpanKind,SpanStatusCode,trace}from"@opentelemetry/api";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";const ON_REQUEST_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.onRequestError,handled:!1,synthetic:!1},DEFAULT_CAPTURE_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.captureError,handled:!0},seen=new WeakSet;function buildAttrs(value,mechanism,context){let attrs={"interfere.exception.mechanism":mechanism.type,"interfere.exception.handled":String(mechanism.handled)},digest=context?.nextjs?.errorDigest;return digest&&(attrs[`interfere.error.digest`]=digest),context?.nextjs?.requestMethod&&(attrs[`http.request.method`]=context.nextjs.requestMethod),context?.nextjs?.requestPath&&(attrs[`url.path`]=context.nextjs.requestPath),isNonErrorException(value)?(attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs):(attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs)}function recordException({error,mechanism,context}){let value=toException(error),attrs=buildAttrs(value,mechanism,context),tracer=trace.getTracer(`@interfere/next/server`),active=trace.getActiveSpan();if(active){active.addEvent(`exception`,attrs),mechanism.handled||active.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message});return}let span=tracer.startSpan(`interfere.captureError`,{kind:SpanKind.INTERNAL});span.addEvent(`exception`,attrs),mechanism.handled||span.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message}),span.end()}function captureError(error,_request,context){if(isEnabledOnServer()&&isPluginEnabled(`errors`)){if(error instanceof Error){if(seen.has(error))return;seen.add(error)}recordException({error,mechanism:context?.mechanism??DEFAULT_CAPTURE_ERROR_MECHANISM,context})}}function onRequestError(error,_request,context){seen.has(error)||(seen.add(error),isEnabledOnServer()&&isPluginEnabled(`errors`)&&recordException({error,mechanism:ON_REQUEST_ERROR_MECHANISM,context:{mechanism:ON_REQUEST_ERROR_MECHANISM,nextjs:{...context,...error.digest?{errorDigest:error.digest}:{}}}}))}export{captureError,onRequestError};
1
+ import{isEnabledOnServer}from"../env.mjs";import{isPluginEnabled}from"./remote-config.mjs";import{readServerAction}from"./server-action-marker.mjs";import{SpanKind,SpanStatusCode,trace}from"@opentelemetry/api";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";const ON_REQUEST_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.onRequestError,handled:!1,synthetic:!1},SERVER_ACTION_MECHANISM={type:MECHANISM_TYPE.nextjs.serverAction,handled:!1,synthetic:!1},DEFAULT_CAPTURE_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.captureError,handled:!0},seen=new WeakSet;function buildAttrs(value,mechanism,context){let attrs={"interfere.exception.mechanism":mechanism.type,"interfere.exception.handled":mechanism.handled},digest=context?.nextjs?.errorDigest;if(digest&&(attrs[`interfere.error.digest`]=digest),context?.nextjs?.requestMethod&&(attrs[`http.request.method`]=context.nextjs.requestMethod),context?.nextjs?.requestPath&&(attrs[`url.path`]=context.nextjs.requestPath),context?.nextjs?.routePath&&(attrs[`http.route`]=context.nextjs.routePath),context?.nextjs?.routeType===`route`&&!mechanism.handled&&(attrs[`http.response.status_code`]=500),context?.serverAction&&(attrs[`interfere.server_action.name`]=context.serverAction.name,context.serverAction.argsShape&&(attrs[`interfere.server_action.args_shape`]=context.serverAction.argsShape)),context?.requestHeaders)for(let[name,headerValue]of Object.entries(context.requestHeaders))attrs[`http.request.header.${name}`]=headerValue;return isNonErrorException(value)?(attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs):(attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs)}function recordException({error,mechanism,context}){let value=toException(error),attrs=buildAttrs(value,mechanism,context),tracer=trace.getTracer(`@interfere/next/server`),active=trace.getActiveSpan();if(active?.isRecording()){active.addEvent(`exception`,attrs),mechanism.handled||active.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message});return}let span=tracer.startSpan(`interfere.captureError`,{kind:SpanKind.INTERNAL});span.addEvent(`exception`,attrs),mechanism.handled||span.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message}),span.end()}function captureError(error,_request,context){if(isEnabledOnServer()&&isPluginEnabled(`errors`)){if(error instanceof Error){if(seen.has(error))return;seen.add(error)}recordException({error,mechanism:context?.mechanism??DEFAULT_CAPTURE_ERROR_MECHANISM,context})}}const CAPTURED_REQUEST_HEADERS=[`accept`,`accept-language`,`content-length`,`content-type`,`host`,`referer`,`user-agent`,`x-forwarded-for`,`x-forwarded-host`,`x-forwarded-proto`,`x-vercel-id`];function pickAllowedHeaders(headers){if(!headers)return;let picked={};for(let name of CAPTURED_REQUEST_HEADERS){let value=headers[name];typeof value==`string`&&(picked[name]=value)}return Object.keys(picked).length>0?picked:void 0}function onRequestError(error,request,context){if(seen.has(error)||(seen.add(error),!isEnabledOnServer())||!isPluginEnabled(`errors`))return;let action=readServerAction(error),mechanism=action?SERVER_ACTION_MECHANISM:ON_REQUEST_ERROR_MECHANISM,requestHeaders=pickAllowedHeaders(request?.headers);recordException({error,mechanism,context:{mechanism,nextjs:{...context,...request?.method?{requestMethod:request.method}:{},...request?.path?{requestPath:request.path}:{},...error.digest?{errorDigest:error.digest}:{}},...requestHeaders?{requestHeaders}:{},...action?{serverAction:action}:{}}})}export{captureError,onRequestError};
@@ -0,0 +1,14 @@
1
+ import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
2
+ import { ExportResult } from "@opentelemetry/core";
3
+
4
+ //#region src/internal/server/edge-exporter.d.ts
5
+ declare class FetchTraceExporter implements SpanExporter {
6
+ private readonly url;
7
+ private readonly headers;
8
+ constructor(url: string, headers: Record<string, string>);
9
+ export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
10
+ shutdown(): Promise<void>;
11
+ forceFlush(): Promise<void>;
12
+ }
13
+ //#endregion
14
+ export { FetchTraceExporter };
@@ -0,0 +1 @@
1
+ import{ExportResultCode}from"@opentelemetry/core";import{JsonTraceSerializer}from"@opentelemetry/otlp-transformer/build/esm/trace/json/trace.js";var FetchTraceExporter=class{url;headers;constructor(url,headers){this.url=url,this.headers=headers}export(spans,resultCallback){if(spans.length===0){resultCallback({code:ExportResultCode.SUCCESS});return}let payload=JsonTraceSerializer.serializeRequest(spans);if(!payload||payload.byteLength===0){resultCallback({code:ExportResultCode.SUCCESS});return}fetch(this.url,{method:`POST`,headers:{...this.headers,"content-type":`application/json`},body:new TextDecoder().decode(payload),keepalive:!0}).catch(()=>void 0),resultCallback({code:ExportResultCode.SUCCESS})}shutdown(){return Promise.resolve()}forceFlush(){return Promise.resolve()}};export{FetchTraceExporter};
@@ -0,0 +1,11 @@
1
+ //#region src/internal/server/global-handlers.d.ts
2
+ interface GlobalHandlerOptions {
3
+ readonly captureUncaughtException: boolean;
4
+ readonly captureUnhandledRejection: boolean;
5
+ readonly exitOnUncaughtException: boolean;
6
+ readonly flush: () => Promise<void>;
7
+ }
8
+ declare function installGlobalHandlers(options: GlobalHandlerOptions): void;
9
+ declare function uninstallGlobalHandlers(): void;
10
+ //#endregion
11
+ export { GlobalHandlerOptions, installGlobalHandlers, uninstallGlobalHandlers };
@@ -0,0 +1 @@
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)},process.on(`uncaughtException`,uncaughtHandler)),options.captureUnhandledRejection&&unhandledHandler===null&&(unhandledHandler=reason=>{handleUnhandledRejection(reason,options)},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,options){captureError(error,void 0,{mechanism:{type:MECHANISM_TYPE.node.uncaughtException,handled:!1}}),await flushWithTimeout(options.flush),options.exitOnUncaughtException&&process.exit(1)}async function handleUnhandledRejection(reason,options){captureError(reason,void 0,{mechanism:{type:MECHANISM_TYPE.node.unhandledRejection,handled:!1}}),await flushWithTimeout(options.flush)}function flushWithTimeout(flush){return new Promise(resolve=>{let timer=setTimeout(resolve,2e3);timer.unref?.(),flush().finally(()=>{clearTimeout(timer),resolve()})})}export{installGlobalHandlers,uninstallGlobalHandlers};
@@ -1,6 +1,6 @@
1
+ import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
1
2
  import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
2
3
  import { MetricReader } from "@opentelemetry/sdk-metrics";
3
- import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
4
4
 
5
5
  //#region src/internal/server/instrumentation-options.d.ts
6
6
  /**
@@ -54,6 +54,20 @@ interface ServerInstrumentationOptions {
54
54
  * list. See `_internalAdditionalLogRecordProcessors`.
55
55
  */
56
56
  _internalAdditionalSpanProcessors?: SpanProcessor[];
57
+ /**
58
+ * Install a `process.on("uncaughtException")` listener that captures the
59
+ * error, flushes pending telemetry, and exits the process with code 1 in
60
+ * production deployments (dev keeps the server alive). Default `true`.
61
+ * Disable to run your own handler.
62
+ */
63
+ captureUncaughtException?: boolean;
64
+ /**
65
+ * Install a `process.on("unhandledRejection")` listener that captures the
66
+ * rejection and flushes pending telemetry. Never forces an exit — Node's
67
+ * `--unhandled-rejections` flag governs fatality. Default `true`. Disable
68
+ * to run your own handler.
69
+ */
70
+ captureUnhandledRejection?: boolean;
57
71
  /**
58
72
  * `false` disables the `console.*` → OTel `LogRecord` bridge.
59
73
  * Defaults to enabled. Customers running their own structured
@@ -0,0 +1,9 @@
1
+ //#region src/internal/server/server-action-marker.d.ts
2
+ interface ServerActionMarker {
3
+ readonly argsShape?: string;
4
+ readonly name: string;
5
+ }
6
+ declare function markServerAction(error: unknown, info: ServerActionMarker): void;
7
+ declare function readServerAction(error: unknown): ServerActionMarker | undefined;
8
+ //#endregion
9
+ export { ServerActionMarker, markServerAction, readServerAction };
@@ -0,0 +1 @@
1
+ const MARKER=`__interfere_server_action__`;function markServerAction(error,info){typeof error!=`object`||!error||Object.defineProperty(error,MARKER,{value:info,enumerable:!1,configurable:!0,writable:!0})}function readServerAction(error){if(!(typeof error!=`object`||!error))return error[MARKER]}export{markServerAction,readServerAction};
@@ -27,6 +27,6 @@
27
27
  * missing meta tag the same as no parent — every browser span starts a
28
28
  * fresh root, which is at least internally consistent.
29
29
  */
30
- declare function TraceMeta(): import("react/jsx-runtime").JSX.Element | null;
30
+ declare function TraceMeta(): import("react").JSX.Element | null;
31
31
  //#endregion
32
32
  export { TraceMeta };
@@ -6,6 +6,11 @@ type OnRequestErrorContext = Omit<NextjsContext, "errorDigest" | "requestMethod"
6
6
  interface CaptureErrorContext {
7
7
  readonly mechanism?: ErrorMechanism;
8
8
  readonly nextjs?: Omit<NextjsContext, "runtime">;
9
+ readonly requestHeaders?: Readonly<Record<string, string>>;
10
+ readonly serverAction?: {
11
+ readonly name: string;
12
+ readonly argsShape?: string;
13
+ };
9
14
  }
10
15
  //#endregion
11
16
  export { CaptureErrorContext, OnRequestErrorContext };
@@ -0,0 +1,22 @@
1
+ import { NextMiddleware } from "next/server.js";
2
+
3
+ //#region src/internal/server/wrap-proxy.d.ts
4
+ /**
5
+ * Wraps a Next.js proxy (formerly `middleware`) so thrown errors are captured
6
+ * by Interfere (mechanism `auto.function.nextjs.proxy`, with request method +
7
+ * path) and then re-thrown unchanged. Next does not route proxy errors through
8
+ * `onRequestError`, so without this wrapper they are only visible via Next's
9
+ * generic OTel instrumentation — untagged.
10
+ *
11
+ * ```ts
12
+ * // proxy.ts
13
+ * import { withInterfereProxy } from "@interfere/next/server";
14
+ *
15
+ * export default withInterfereProxy((request) => {
16
+ * // ...your proxy
17
+ * });
18
+ * ```
19
+ */
20
+ declare function withInterfereProxy(proxy: NextMiddleware): NextMiddleware;
21
+ //#endregion
22
+ export { withInterfereProxy };
@@ -0,0 +1 @@
1
+ import{captureError}from"./capture.mjs";import{MECHANISM_TYPE}from"@interfere/types/sdk/errors";function withInterfereProxy(proxy){return(request,event)=>{try{let result=proxy(request,event);return result instanceof Promise?result.catch(error=>{throw reportProxyError(error,request),error}):result}catch(error){throw reportProxyError(error,request),error}}}function reportProxyError(error,request){captureError(error,request,{mechanism:{type:MECHANISM_TYPE.nextjs.proxy,handled:!1},nextjs:{requestMethod:request.method,requestPath:request.nextUrl.pathname}})}export{withInterfereProxy};