@interfere/next 12.1.4 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +19 -7
  2. package/dist/config.d.mts +2 -0
  3. package/dist/config.mjs +1 -1
  4. package/dist/instrumentation.d.mts +2 -2
  5. package/dist/instrumentation.edge.d.mts +1 -1
  6. package/dist/instrumentation.edge.mjs +1 -1
  7. package/dist/instrumentation.mjs +1 -1
  8. package/dist/internal/build/configure-build.d.mts +1 -0
  9. package/dist/internal/build/injected-values.d.mts +12 -0
  10. package/dist/internal/build/injected-values.mjs +1 -0
  11. package/dist/internal/build/pipeline.mjs +1 -1
  12. package/dist/internal/env.d.mts +1 -0
  13. package/dist/internal/env.mjs +1 -1
  14. package/dist/internal/release-slug.mjs +1 -1
  15. package/dist/internal/server/{server-action-marker.d.mts → actions/server-action-marker.d.mts} +1 -1
  16. package/dist/internal/server/{wrap-proxy.d.mts → actions/wrap-proxy.d.mts} +1 -1
  17. package/dist/internal/server/actions/wrap-proxy.mjs +1 -0
  18. package/dist/internal/server/{wrap-server-action.d.mts → actions/wrap-server-action.d.mts} +1 -1
  19. package/dist/internal/server/{capture.d.mts → capture/capture.d.mts} +2 -2
  20. package/dist/internal/server/capture/capture.mjs +1 -0
  21. package/dist/internal/server/{console-bridge.d.mts → capture/console-bridge.d.mts} +1 -1
  22. package/dist/internal/server/{global-handlers.d.mts → capture/global-handlers.d.mts} +1 -1
  23. package/dist/internal/server/{edge-exporter.d.mts → exporters/edge-exporter.d.mts} +1 -1
  24. package/dist/internal/server/exporters/edge-exporter.mjs +1 -0
  25. package/dist/internal/server/{server-flush.d.mts → exporters/server-flush.d.mts} +1 -1
  26. package/dist/internal/server/instrumentation-options.d.mts +2 -4
  27. package/dist/internal/server/{cache-safe-trace-context.d.mts → tracing/cache-safe-trace-context.d.mts} +1 -1
  28. package/dist/internal/server/tracing/cache-safe-trace-context.mjs +1 -0
  29. package/dist/internal/server/{counter-id-generator.d.mts → tracing/counter-id-generator.d.mts} +1 -1
  30. package/dist/internal/server/{freshness-propagator.d.mts → tracing/freshness-propagator.d.mts} +1 -1
  31. package/dist/internal/server/tracing/freshness-propagator.mjs +1 -0
  32. package/dist/internal/server/{id-generator.d.mts → tracing/id-generator.d.mts} +1 -1
  33. package/dist/internal/server/{trace-meta.d.mts → tracing/trace-meta.d.mts} +1 -1
  34. package/dist/internal/server/tracing/trace-meta.mjs +1 -0
  35. package/dist/internal/server/{trace-payload.d.mts → tracing/trace-payload.d.mts} +1 -1
  36. package/dist/internal/server/{traceparent.d.mts → tracing/traceparent.d.mts} +1 -1
  37. package/dist/internal/server/types.d.mts +1 -1
  38. package/dist/package.mjs +1 -1
  39. package/dist/server.d.mts +4 -4
  40. package/dist/server.mjs +1 -1
  41. package/package.json +7 -7
  42. package/dist/internal/server/cache-safe-trace-context.mjs +0 -1
  43. package/dist/internal/server/capture.mjs +0 -1
  44. package/dist/internal/server/edge-exporter.mjs +0 -1
  45. package/dist/internal/server/freshness-propagator.mjs +0 -1
  46. package/dist/internal/server/trace-meta.mjs +0 -1
  47. package/dist/internal/server/wrap-proxy.mjs +0 -1
  48. /package/dist/internal/server/{server-action-marker.mjs → actions/server-action-marker.mjs} +0 -0
  49. /package/dist/internal/server/{wrap-server-action.mjs → actions/wrap-server-action.mjs} +0 -0
  50. /package/dist/internal/server/{console-bridge.mjs → capture/console-bridge.mjs} +0 -0
  51. /package/dist/internal/server/{global-handlers.mjs → capture/global-handlers.mjs} +0 -0
  52. /package/dist/internal/server/{server-flush.mjs → exporters/server-flush.mjs} +0 -0
  53. /package/dist/internal/server/{counter-id-generator.mjs → tracing/counter-id-generator.mjs} +0 -0
  54. /package/dist/internal/server/{id-generator.mjs → tracing/id-generator.mjs} +0 -0
  55. /package/dist/internal/server/{trace-payload.mjs → tracing/trace-payload.mjs} +0 -0
  56. /package/dist/internal/server/{traceparent.mjs → tracing/traceparent.mjs} +0 -0
package/README.md CHANGED
@@ -84,14 +84,24 @@ export default function RootLayout({
84
84
 
85
85
  `InterfereProvider` captures render errors by default and rethrows them so your host error boundary keeps control of the fallback. Set `captureRenderErrors={false}` to disable this capture. The deprecated `errorBoundary` prop remains supported; `captureRenderErrors` takes precedence when both are set.
86
86
 
87
+ ### Service identity
88
+
89
+ `withInterfere` uses the application's unscoped `package.json` name as the default `service.name` for browser, server, and edge telemetry. For example, `@profound/platform` becomes `platform`. A build-time `OTEL_SERVICE_NAME` overrides the package name, or set a shared default explicitly:
90
+
91
+ ```ts
92
+ export default withInterfere({ serviceName: "platform" }, config);
93
+ ```
94
+
95
+ An explicit `serviceName` passed to browser `init()` or server/edge `register()` takes precedence. Server and edge runtimes also honor `OTEL_SERVICE_NAME` ahead of the build default. If no name can be inferred or configured, OpenTelemetry's unknown-service fallback remains.
96
+
87
97
  ### Environment Variables
88
98
 
89
- | Variable | Required | Description |
90
- | ---------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91
- | `INTERFERE_PUBLIC_KEY` | Yes | Surface public key in the `interfere_public_us_*` or `interfere_public_eu_*` format. Public by design; used for ingestion. A build without it logs a warning and sends no events. |
92
- | `INTERFERE_API_KEY` | Yes for release metadata | Interfere secret key in the `interfere_secret_us_*` or `interfere_secret_eu_*` format. Server-side build credential; never expose in browser code. A build without it skips the release. |
93
- | `INTERFERE_SOURCEMAP_UPLOAD_CONCURRENCY` | No | Maximum concurrent source-map uploads. Defaults to 20; must be a positive integer. |
94
- | `INTERFERE_API_URL` | No | Override the ingest endpoint in `direct` mode (e.g. a regional endpoint). Defaults to Interfere's endpoint. |
99
+ | Variable | Required | Description |
100
+ | ---------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
101
+ | `INTERFERE_PUBLIC_KEY` | Yes | Surface public key in the `interfere_public_us_*` or `interfere_public_eu_*` format. Public by design; used for ingestion, and at build time to name the app that a release belongs to. A build without it logs a warning and sends no events. |
102
+ | `INTERFERE_API_KEY` | Yes for release metadata | Workspace secret key in the `interfere_secret_us_*` or `interfere_secret_eu_*` format, with the **Publish releases** (`release:write`) permission. One key publishes releases for every app in the workspace. Server-side build credential; never expose in browser code. A build without it skips the release. |
103
+ | `INTERFERE_SOURCEMAP_UPLOAD_CONCURRENCY` | No | Maximum concurrent source-map uploads. Defaults to 20; must be a positive integer. |
104
+ | `INTERFERE_API_URL` | No | Override the ingest endpoint in `direct` mode (e.g. a regional endpoint). Defaults to Interfere's endpoint. |
95
105
 
96
106
  ### Connect releases to Vercel
97
107
 
@@ -129,12 +139,14 @@ A production build registers a release when `INTERFERE_API_KEY` is set, so set i
129
139
 
130
140
  To turn Interfere off for a build, pass `withInterfere({ disable: true }, nextConfig)`. The build skips the release, the source-map upload and every Interfere check, and the browser and server SDKs send no events.
131
141
 
132
- Only configuration that is wrong fails a production build: an `INTERFERE_API_KEY` with the wrong prefix, a missing commit SHA when the key is set, an invalid `apiHost`, or a public key without an `instrumentation-client` file. A missing `INTERFERE_PUBLIC_KEY` logs a warning. Errors from the Interfere API, including a rejected secret key, are logged and do not fail the build.
142
+ Only configuration that is wrong fails a production build: an `INTERFERE_API_KEY` with the wrong prefix, a missing commit SHA when the key is set, an invalid `apiHost`, or a public key without an `instrumentation-client` file. A missing `INTERFERE_PUBLIC_KEY` logs a warning; with a workspace secret key, the build also cannot publish its release. Errors from the Interfere API, including a rejected secret key, are logged and do not fail the build.
133
143
 
134
144
  ### Migrating From 0.x
135
145
 
136
146
  Upgrade all `@interfere/*` SDK packages together. Keep the env var names, but replace old `int_pub_*` / `interfere_pk_*` values with the dashboard's regional public key, and replace old `ak_*` / `interfere_ak_*` build keys with the regional Interfere secret key.
137
147
 
148
+ Secret keys that were created for one app keep working. New secret keys belong to the workspace, so set `INTERFERE_PUBLIC_KEY` on the build machine as well.
149
+
138
150
  ## Server tracing and Vercel drains
139
151
 
140
152
  The standard instrumentation export works on Vercel and other Next.js hosts. In `full` mode, Interfere exports application spans directly. When the build config selects `drain`, the Node SDK initializes `@vercel/otel` with its default processors so Vercel can forward application spans through the configured Trace Drain. Platform spans and application spans remain separate observations.
package/dist/config.d.mts CHANGED
@@ -26,6 +26,8 @@ export interface NextConfigWithInterfere extends NextConfig {
26
26
  * config so SDK options never collide with Next's own keys.
27
27
  */
28
28
  export interface NextPluginConfig {
29
+ /** Default service name for browser, server, and edge telemetry. Falls back to OTEL_SERVICE_NAME, then the app package name without its scope. */
30
+ readonly serviceName?: string;
29
31
  readonly sourceMaps?: {
30
32
  /** Upload source maps to Interfere. Defaults to true. Release registration and telemetry stay enabled when false. */
31
33
  readonly upload?: boolean;
package/dist/config.mjs CHANGED
@@ -1 +1 @@
1
- import{configureBuild}from"./internal/build/configure-build.mjs";import{runGitCommand}from"./internal/build/release/git.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{API_URL}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{checkReleaseConfig,diagnoseRelease}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{API_URL_GLOBAL,BUILD_MARKER_GLOBAL,DISABLED_GLOBAL,ENVIRONMENT_GLOBAL,FORCE_ENABLE_GLOBAL,PUBLIC_KEY_GLOBAL,RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";const CLIENT_TRACE_METADATA_KEYS=[`traceparent`,`tracestate`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`,docsUrl:`https://interfere.com/docs/sdk/next-js`};function isThenable(value){return typeof value==`object`&&!!value&&`then`in value&&typeof value.then==`function`}function withInterfere(...args){let[settings,nextConfig]=args.length===2?[args[0],args[1]]:[{},args[0]??{}];return(phase,context)=>{let resolved=typeof nextConfig==`function`?nextConfig(phase,context??{defaultConfig:{}}):nextConfig;return isThenable(resolved)?Promise.resolve(resolved).then(config=>buildInterfereConfig(phase,settings,config)):buildInterfereConfig(phase,settings,resolved)}}function interfereInjectedValues(config,apiHost,forceEnable){let releaseSlug=forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug;return omitUndefined({[RELEASE_SLUG_GLOBAL]:releaseSlug,[BUILD_MARKER_GLOBAL]:!0,[PUBLIC_KEY_GLOBAL]:config.publicKey||void 0,[API_URL_GLOBAL]:config.publicKey&&apiHost?apiHost:void 0,[ENVIRONMENT_GLOBAL]:config.environment||void 0,[FORCE_ENABLE_GLOBAL]:forceEnable?!0:void 0})}function interfereCompilerConfig(compiler,config){let existingHook=compiler?.runAfterProductionCompile;return omitUndefined({...compiler,async runAfterProductionCompile(context){existingHook&&await existingHook(context),config.publicKey===null&&log.warn(`Public key not set`,[`INTERFERE_PUBLIC_KEY is not set, so this build sends no events.`,`Set it on builds that you deploy.`]);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 finding when generating insights.`,`Error: ${message}`])}}})}function resolveBrowserSourceMaps(config,productionBrowserSourceMaps){let releasing=config.apiKey!==null&&config.uploadSourceMaps;return releasing&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),releasing?!0:productionBrowserSourceMaps}function disabledInterfereConfig(nextConfig){let{interfere:_interfere,webpack,turbopack,...rest}=nextConfig,projectDir=process.cwd(),build=configureBuild({existingTurbopack:turbopack,existingWebpack:webpack,nextMajor:readNextMajorVersion(projectDir),projectDir,remoteConfigFetch:void 0,values:{[BUILD_MARKER_GLOBAL]:!0,[DISABLED_GLOBAL]:!0,[RELEASE_SLUG_GLOBAL]:null}});return omitUndefined({...rest,turbopack:build.turbopack,webpack:build.webpack})}function buildInterfereConfig(phase,settings,nextConfig){if(settings.disable)return disabledInterfereConfig(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(settings,phase),apiHost=resolveBrowserApiHost(interfere?.apiHost,config.apiUrl),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,nextMajor=readNextMajorVersion(projectDir),build=configureBuild({existingTurbopack:turbopack,existingWebpack:webpack,nextMajor,projectDir,remoteConfigFetch:resolveRemoteConfigFetch(config,phase),values:interfereInjectedValues(config,apiHost,forceEnable)});if(config.publicKey!==null&&!build.clientInstrumentationApplies)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 experimental=interfereExperimentalConfig({existing:rest.experimental,instrumentationHook:nextMajor===null||nextMajor<=14,traceMetadata:config.publicKey!==null||forceEnable});return omitUndefined({...rest,env:userEnv,compiler:interfereCompilerConfig(compiler,config),productionBrowserSourceMaps:resolveBrowserSourceMaps(config,productionBrowserSourceMaps),turbopack:build.turbopack,webpack:build.webpack,experimental})}function interfereExperimentalConfig({existing,instrumentationHook,traceMetadata}){let merged={...existing};return instrumentationHook&&(merged.instrumentationHook=!0),traceMetadata&&(merged.clientTraceMetadata=[...new Set([...existing?.clientTraceMetadata??[],...CLIENT_TRACE_METADATA_KEYS])]),Object.keys(merged).length>0?merged:void 0}function resolveBrowserApiHost(apiHost,apiUrl){let host=apiHost??apiUrl;if(host!==API_URL)return normalizeHost(host)}function normalizeHost(host){try{return new URL(host).origin}catch{return log.fatal(`Invalid apiHost`,[`interfere.apiHost must be a full URL, e.g. "https://in.yourapp.com".`,`Received: ${host}`])}}function resolveBuildConfig(settings,phase){let{apiKey,apiUrl,publicKey}=readInterfereEnv(),{commitSha,slug}=resolveReleaseSlug(runGitCommand),environment=phase===PHASE_PRODUCTION_BUILD?resolveDeployEnvironment(process.env):null;return{apiKey:apiKey??null,apiUrl,buildId:commitSha,environment,publicKey:publicKey??null,purgeSourceMaps:settings.sourceMaps?.purge??!0,uploadSourceMaps:settings.sourceMaps?.upload??!0,releaseSlug:slug}}function resolveRemoteConfigFetch(config,phase){if(phase===PHASE_PRODUCTION_BUILD&&config.publicKey)return{apiUrl:config.apiUrl,cacheScope:crypto.randomUUID(),publicKey:config.publicKey}}async function runBuildReleasePipeline(context,config){let release=checkReleaseConfig(DIAGNOSTIC_CONTEXT,{apiKey:config.apiKey,commitSha:config.buildId,releaseSlug:config.releaseSlug});if(release.status===`skip`){log.warn(release.title,[...release.details]);return}if(release.status===`fail`)return log.fatal(release.title,[...release.details]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey:release.apiKey,buildId:release.commitSha,releaseSlug:release.releaseSlug});if(!result.accepted){result.reason===`no_source_maps`&&log.warn(`Release remains unready`,[`No source maps found. Enable source-map generation and upload them during the build.`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config.org.slug}/apps/${result.config.surface.slug}`,`Release: ${result.release.destination.slug}`,`Build: ${result.buildId}`,config.uploadSourceMaps?`Artifacts: ${result.fileCount} source maps`:`Source-map uploads disabled`])}catch(error){let diagnostic=diagnoseRelease(DIAGNOSTIC_CONTEXT,error);log[diagnostic.severity](diagnostic.title,[...diagnostic.details])}}export{withInterfere};
1
+ import{configureBuild}from"./internal/build/configure-build.mjs";import{runGitCommand}from"./internal/build/release/git.mjs";import{interfereInjectedValues}from"./internal/build/injected-values.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{resolveBuildServiceName}from"@interfere/build/service-name";import{API_URL}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{checkReleaseConfig,diagnoseRelease}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{BUILD_MARKER_GLOBAL,DISABLED_GLOBAL,RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";const CLIENT_TRACE_METADATA_KEYS=[`traceparent`,`tracestate`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`,docsUrl:`https://interfere.com/docs/sdk/next-js`};function isThenable(value){return typeof value==`object`&&!!value&&`then`in value&&typeof value.then==`function`}function withInterfere(...args){let[settings,nextConfig]=args.length===2?[args[0],args[1]]:[{},args[0]??{}];return(phase,context)=>{let resolved=typeof nextConfig==`function`?nextConfig(phase,context??{defaultConfig:{}}):nextConfig;return isThenable(resolved)?Promise.resolve(resolved).then(config=>buildInterfereConfig(phase,settings,config)):buildInterfereConfig(phase,settings,resolved)}}function interfereCompilerConfig(compiler,config){let existingHook=compiler?.runAfterProductionCompile;return omitUndefined({...compiler,async runAfterProductionCompile(context){existingHook&&await existingHook(context),config.publicKey===null&&log.warn(`Public key not set`,[`INTERFERE_PUBLIC_KEY is not set, so this build sends no events.`,`Set it on builds that you deploy.`]);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 finding when generating insights.`,`Error: ${message}`])}}})}function resolveBrowserSourceMaps(config,productionBrowserSourceMaps){let releasing=config.apiKey!==null&&config.uploadSourceMaps;return releasing&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),releasing?!0:productionBrowserSourceMaps}function disabledInterfereConfig(nextConfig){let{interfere:_interfere,webpack,turbopack,...rest}=nextConfig,projectDir=process.cwd(),build=configureBuild({existingTurbopack:turbopack,existingWebpack:webpack,nextMajor:readNextMajorVersion(projectDir),projectDir,remoteConfigFetch:void 0,values:{[BUILD_MARKER_GLOBAL]:!0,[DISABLED_GLOBAL]:!0,[RELEASE_SLUG_GLOBAL]:null}});return omitUndefined({...rest,turbopack:build.turbopack,webpack:build.webpack})}function buildInterfereConfig(phase,settings,nextConfig){if(settings.disable)return disabledInterfereConfig(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(settings,phase),apiHost=resolveBrowserApiHost(interfere?.apiHost,config.apiUrl),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,nextMajor=readNextMajorVersion(projectDir),build=configureBuild({existingTurbopack:turbopack,existingWebpack:webpack,nextMajor,projectDir,remoteConfigFetch:resolveRemoteConfigFetch(config,phase),values:interfereInjectedValues(config,apiHost,forceEnable,resolveBuildServiceName({root:projectDir,option:settings.serviceName}))});if(config.publicKey!==null&&!build.clientInstrumentationApplies)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 experimental=interfereExperimentalConfig({existing:rest.experimental,instrumentationHook:nextMajor===null||nextMajor<=14,traceMetadata:config.publicKey!==null||forceEnable});return omitUndefined({...rest,env:userEnv,compiler:interfereCompilerConfig(compiler,config),productionBrowserSourceMaps:resolveBrowserSourceMaps(config,productionBrowserSourceMaps),turbopack:build.turbopack,webpack:build.webpack,experimental})}function interfereExperimentalConfig({existing,instrumentationHook,traceMetadata}){let merged={...existing};return instrumentationHook&&(merged.instrumentationHook=!0),traceMetadata&&(merged.clientTraceMetadata=[...new Set([...existing?.clientTraceMetadata??[],...CLIENT_TRACE_METADATA_KEYS])]),Object.keys(merged).length>0?merged:void 0}function resolveBrowserApiHost(apiHost,apiUrl){let host=apiHost??apiUrl;if(host!==API_URL)return normalizeHost(host)}function normalizeHost(host){try{return new URL(host).origin}catch{return log.fatal(`Invalid apiHost`,[`interfere.apiHost must be a full URL, e.g. "https://in.yourapp.com".`,`Received: ${host}`])}}function resolveBuildConfig(settings,phase){let{apiKey,apiUrl,publicKey}=readInterfereEnv(),{commitSha,slug}=resolveReleaseSlug(runGitCommand),environment=phase===PHASE_PRODUCTION_BUILD?resolveDeployEnvironment(process.env):null;return{apiKey:apiKey??null,apiUrl,buildId:commitSha,environment,publicKey:publicKey??null,purgeSourceMaps:settings.sourceMaps?.purge??!0,uploadSourceMaps:settings.sourceMaps?.upload??!0,releaseSlug:slug}}function resolveRemoteConfigFetch(config,phase){if(phase===PHASE_PRODUCTION_BUILD&&config.publicKey)return{apiUrl:config.apiUrl,cacheScope:crypto.randomUUID(),publicKey:config.publicKey}}async function runBuildReleasePipeline(context,config){let release=checkReleaseConfig(DIAGNOSTIC_CONTEXT,{apiKey:config.apiKey,commitSha:config.buildId,releaseSlug:config.releaseSlug});if(release.status===`skip`){log.warn(release.title,[...release.details]);return}if(release.status===`fail`)return log.fatal(release.title,[...release.details]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey:release.apiKey,buildId:release.commitSha,releaseSlug:release.releaseSlug});if(!result.accepted){result.reason===`no_source_maps`&&log.warn(`Release remains unready`,[`No source maps found. Enable source-map generation and upload them during the build.`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config.org.slug}/apps/${result.config.surface.slug}`,`Release: ${result.release.destination.slug}`,`Build: ${result.buildId}`,config.uploadSourceMaps?`Artifacts: ${result.fileCount} source maps`:`Source-map uploads disabled`])}catch(error){let diagnostic=diagnoseRelease(DIAGNOSTIC_CONTEXT,error);log[diagnostic.severity](diagnostic.title,[...diagnostic.details])}}export{withInterfere};
@@ -1,6 +1,6 @@
1
- import { PrerenderSafeIdGenerator } from "./internal/server/id-generator.mjs";
1
+ import { PrerenderSafeIdGenerator } from "./internal/server/tracing/id-generator.mjs";
2
2
  import { ServerInstrumentationOptions } from "./internal/server/instrumentation-options.mjs";
3
- import { onRequestError } from "./internal/server/capture.mjs";
3
+ import { onRequestError } from "./internal/server/capture/capture.mjs";
4
4
  import { AttributeValue, TextMapPropagator } from "@opentelemetry/api";
5
5
  import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
6
6
  import { Instrumentation } from "@opentelemetry/instrumentation";
@@ -1,5 +1,5 @@
1
1
  import { ServerInstrumentationOptions } from "./internal/server/instrumentation-options.mjs";
2
- import { onRequestError } from "./internal/server/capture.mjs";
2
+ import { onRequestError } from "./internal/server/capture/capture.mjs";
3
3
  import { InterfereOtelKit } from "./instrumentation.mjs";
4
4
  //#region src/instrumentation.edge.d.ts
5
5
  /**
@@ -1 +1 @@
1
- import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{FetchTraceExporter}from"./internal/server/edge-exporter.mjs";import{setServerFlush}from"./internal/server/server-flush.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{getStringFromEnv}from"@opentelemetry/core";import{createUrlRedactionSpanProcessor,resolveUrlRedaction}from"@interfere/types/sdk/url-redaction";import{trace}from"@opentelemetry/api";import{defaultServiceName,resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;async 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:resolveReleaseSlug().slug;releaseSlug||console.warn(`[interfere] edge runtime has no release.slug; configure a build release or INTERFERE_SOURCE_ID.`);let provider=new BasicTracerProvider({resource:resourceFromAttributes(omitUndefined({"deployment.environment.name":env.nodeEnvironment,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,"service.name":opts.serviceName||getStringFromEnv(`OTEL_SERVICE_NAME`)||defaultServiceName(),"service.namespace":opts.serviceNamespace,"telemetry.sdk.language":`nodejs`,"release.slug":releaseSlug||void 0})),spanProcessors:[createUrlRedactionSpanProcessor(resolveUrlRedaction(opts.privacy?.urls)),new SimpleSpanProcessor(new FetchTraceExporter({baseUrl:env.apiUrl,publicKeyHeader:{apiKey:env.publicKey},headers:{"x-interfere-producer-version":PRODUCER_VERSION}}))]});return trace.setGlobalTracerProvider(provider),setServerFlush(async()=>provider.forceFlush({timeoutMillis:75e3})),registered=!0,Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,onRequestError,register};
1
+ import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{FetchTraceExporter}from"./internal/server/exporters/edge-exporter.mjs";import{setServerFlush}from"./internal/server/exporters/server-flush.mjs";import{onRequestError}from"./internal/server/capture/capture.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{getStringFromEnv}from"@opentelemetry/core";import{createUrlRedactionSpanProcessor,resolveUrlRedaction}from"@interfere/types/sdk/url-redaction";import{trace}from"@opentelemetry/api";import{defaultServiceName,resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;async 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:resolveReleaseSlug().slug;releaseSlug||console.warn(`[interfere] edge runtime has no release.slug; configure a build release or INTERFERE_SOURCE_ID.`);let provider=new BasicTracerProvider({resource:resourceFromAttributes(omitUndefined({"deployment.environment.name":env.nodeEnvironment,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,"service.name":opts.serviceName||getStringFromEnv(`OTEL_SERVICE_NAME`)||env.serviceName||defaultServiceName(),"service.namespace":opts.serviceNamespace,"telemetry.sdk.language":`nodejs`,"release.slug":releaseSlug||void 0})),spanProcessors:[createUrlRedactionSpanProcessor(resolveUrlRedaction(opts.privacy?.urls)),new SimpleSpanProcessor(new FetchTraceExporter({baseUrl:env.apiUrl,publicKeyHeader:{apiKey:env.publicKey},headers:{"x-interfere-producer-version":PRODUCER_VERSION}}))]});return trace.setGlobalTracerProvider(provider),setServerFlush(async()=>provider.forceFlush({timeoutMillis:75e3})),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{setServerFlush}from"./internal/server/server-flush.mjs";import{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{CacheSafeTraceContextPropagator}from"./internal/server/cache-safe-trace-context.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{FreshnessStampingPropagator}from"./internal/server/freshness-propagator.mjs";import{installGlobalHandlers}from"./internal/server/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{drainUrl}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{CompositePropagator,W3CBaggagePropagator,getStringFromEnv}from"@opentelemetry/core";import{createUrlRedactionSpanProcessor,resolveUrlRedaction}from"@interfere/types/sdk/url-redaction";import{ProxyTracerProvider,propagation,trace}from"@opentelemetry/api";import{defaultServiceName,resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{readLogSourcingMode}from"@interfere/types/sdk/log-sourcing";import{readServerTracingMode}from"@interfere/types/sdk/server-tracing";import{BaggageSpanProcessor}from"@opentelemetry/baggage-span-processor";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{OTLPLogExporter}from"@opentelemetry/exporter-logs-otlp-http";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{CompressionAlgorithm}from"@opentelemetry/otlp-exporter-base";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";const EXPORT_TIMEOUT_MS=65e3;function canFlush(provider){return`forceFlush`in provider&&typeof provider.forceFlush==`function`}let registered=!1,tracerProvider=null,loggerProvider=null;async function flush(){await Promise.all([tracerProvider?.forceFlush({timeoutMillis:75e3}),loggerProvider?.forceFlush({timeoutMillis:75e3})])}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 tracesUrl=drainUrl(env.apiUrl,`traces`,env.publicKey),logsUrl=drainUrl(env.apiUrl,`logs`,env.publicKey),exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION},resourceAttributes=omitUndefined({"deployment.environment.name":env.nodeEnvironment,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,"service.name":opts.serviceName||getStringFromEnv(`OTEL_SERVICE_NAME`)||defaultServiceName(),"service.namespace":opts.serviceNamespace,"telemetry.sdk.language":`nodejs`,"release.slug":releaseSlug||void 0}),compression=opts.compression===`gzip`?CompressionAlgorithm.GZIP:CompressionAlgorithm.NONE,isDrainMode=(opts.serverTracing??readServerTracingMode())===`drain`,spanProcessors=[createUrlRedactionSpanProcessor(resolveUrlRedaction(opts.privacy?.urls)),new BaggageSpanProcessor(()=>!0),...isDrainMode?[]:[new BatchSpanProcessor(new OTLPTraceExporter({compression,headers:exporterHeaders,timeoutMillis:EXPORT_TIMEOUT_MS,url:tracesUrl}),{exportTimeoutMillis:7e4})],...opts._internalAdditionalSpanProcessors??[]],isLogDrainMode=readLogSourcingMode()===`drain`,logRecordProcessors=[...isLogDrainMode?[]:[new BatchLogRecordProcessor({exportTimeoutMillis:7e4,exporter:new OTLPLogExporter({compression,headers:exporterHeaders,timeoutMillis:EXPORT_TIMEOUT_MS,url:logsUrl})})],...opts._internalAdditionalLogRecordProcessors??[]];return{enableConsoleBridge:()=>isLogDrainMode?()=>{}:bridgeConsoleToOtel(),fetchRemoteConfig:async()=>fetchAndCacheRemoteConfig(),idGenerator:new PrerenderSafeIdGenerator,instrumentations:[],traceContextPropagator:new CacheSafeTraceContextPropagator,logRecordProcessors,propagators:[new FreshnessStampingPropagator,new W3CBaggagePropagator],resourceAttributes,spanProcessors}}async function register(opts={}){if(registered)return;let drainMode=(opts.serverTracing??readServerTracingMode())===`drain`;if(drainMode&&process.env.OTEL_SDK_DISABLED?.trim())return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes);if(drainMode){let{registerOTel}=await import(`@vercel/otel`);registerOTel({serviceName:String(kit.resourceAttributes[`service.name`]),attributes:kit.resourceAttributes,idGenerator:kit.idGenerator,instrumentations:kit.instrumentations,propagators:[`auto`,kit.traceContextPropagator,...kit.propagators],spanProcessors:[`auto`,...kit.spanProcessors]});let provider=trace.getTracerProvider(),delegate=provider instanceof ProxyTracerProvider?provider.getDelegate():provider;if(!canFlush(delegate))throw Error(`Interfere could not register the Vercel tracer provider. Use buildInterfereOtelKit with an existing OpenTelemetry bootstrap.`);tracerProvider=delegate}else{let provider=new NodeTracerProvider({idGenerator:kit.idGenerator,resource,spanProcessors:kit.spanProcessors});propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[kit.traceContextPropagator,...kit.propagators]})),provider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),tracerProvider=provider}loggerProvider=new LoggerProvider({processors:kit.logRecordProcessors,resource});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),registerInstrumentations({instrumentations:kit.instrumentations,tracerProvider:trace.getTracerProvider()}),opts.consoleBridge!==!1&&kit.enableConsoleBridge(),installGlobalHandlers({captureUncaughtException:opts.captureUncaughtException??!0,captureUnhandledRejection:opts.captureUnhandledRejection??!0,exitOnUncaughtException:process.env.NODE_ENV===`production`,flush}),setServerFlush(flush),registered=!0}export{PrerenderSafeIdGenerator,buildInterfereOtelKit,flush,onRequestError,register};
1
+ import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{setServerFlush}from"./internal/server/exporters/server-flush.mjs";import{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture/capture.mjs";import{CacheSafeTraceContextPropagator}from"./internal/server/tracing/cache-safe-trace-context.mjs";import{bridgeConsoleToOtel}from"./internal/server/capture/console-bridge.mjs";import{FreshnessStampingPropagator}from"./internal/server/tracing/freshness-propagator.mjs";import{installGlobalHandlers}from"./internal/server/capture/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/tracing/id-generator.mjs";import{drainUrl}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{CompositePropagator,W3CBaggagePropagator,getStringFromEnv}from"@opentelemetry/core";import{createUrlRedactionSpanProcessor,resolveUrlRedaction}from"@interfere/types/sdk/url-redaction";import{ProxyTracerProvider,propagation,trace}from"@opentelemetry/api";import{defaultServiceName,resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{readLogSourcingMode}from"@interfere/types/sdk/log-sourcing";import{VERCEL_EXPORT_TIMEOUT_MS,readServerTracing}from"@interfere/types/sdk/server-tracing";import{BaggageSpanProcessor}from"@opentelemetry/baggage-span-processor";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{OTLPLogExporter}from"@opentelemetry/exporter-logs-otlp-http";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{CompressionAlgorithm}from"@opentelemetry/otlp-exporter-base";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";const FLUSH_TIMEOUT_MS=75e3;function exportTimeouts(vercel){return vercel?{attempt:VERCEL_EXPORT_TIMEOUT_MS,batch:VERCEL_EXPORT_TIMEOUT_MS,flush:VERCEL_EXPORT_TIMEOUT_MS}:{attempt:65e3,batch:7e4,flush:FLUSH_TIMEOUT_MS}}let flushTimeoutMs=FLUSH_TIMEOUT_MS;function canFlush(provider){return`forceFlush`in provider&&typeof provider.forceFlush==`function`}let registered=!1,tracerProvider=null,loggerProvider=null;async function flush(){await Promise.all([tracerProvider?.forceFlush({timeoutMillis:flushTimeoutMs}),loggerProvider?.forceFlush({timeoutMillis:flushTimeoutMs})])}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 tracesUrl=drainUrl(env.apiUrl,`traces`,env.publicKey),logsUrl=drainUrl(env.apiUrl,`logs`,env.publicKey),exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION},resourceAttributes=omitUndefined({"deployment.environment.name":env.nodeEnvironment,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,"service.name":opts.serviceName||getStringFromEnv(`OTEL_SERVICE_NAME`)||env.serviceName||defaultServiceName(),"service.namespace":opts.serviceNamespace,"telemetry.sdk.language":`nodejs`,"release.slug":releaseSlug||void 0}),compression=opts.compression===`gzip`?CompressionAlgorithm.GZIP:CompressionAlgorithm.NONE,tracing=readServerTracing(),isDrainMode=tracing.mode===`drain`,timeouts=exportTimeouts(tracing.vercel),spanProcessors=[createUrlRedactionSpanProcessor(resolveUrlRedaction(opts.privacy?.urls)),new BaggageSpanProcessor(()=>!0),...isDrainMode?[]:[new BatchSpanProcessor(new OTLPTraceExporter({compression,headers:exporterHeaders,timeoutMillis:timeouts.attempt,url:tracesUrl}),{exportTimeoutMillis:timeouts.batch})],...opts._internalAdditionalSpanProcessors??[]],isLogDrainMode=readLogSourcingMode()===`drain`,logRecordProcessors=[...isLogDrainMode?[]:[new BatchLogRecordProcessor({exportTimeoutMillis:timeouts.batch,exporter:new OTLPLogExporter({compression,headers:exporterHeaders,timeoutMillis:timeouts.attempt,url:logsUrl})})],...opts._internalAdditionalLogRecordProcessors??[]];return{enableConsoleBridge:()=>isLogDrainMode?()=>{}:bridgeConsoleToOtel(),fetchRemoteConfig:async()=>fetchAndCacheRemoteConfig(),idGenerator:new PrerenderSafeIdGenerator,instrumentations:[],traceContextPropagator:new CacheSafeTraceContextPropagator,logRecordProcessors,propagators:[new FreshnessStampingPropagator,new W3CBaggagePropagator],resourceAttributes,spanProcessors}}async function register(opts={}){if(registered)return;let tracing=readServerTracing(),drainMode=tracing.mode===`drain`;if(flushTimeoutMs=exportTimeouts(tracing.vercel).flush,drainMode&&process.env.OTEL_SDK_DISABLED?.trim())return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes);if(drainMode){let{registerOTel}=await import(`@vercel/otel`);registerOTel({serviceName:String(kit.resourceAttributes[`service.name`]),attributes:kit.resourceAttributes,idGenerator:kit.idGenerator,instrumentations:kit.instrumentations,propagators:[`auto`,kit.traceContextPropagator,...kit.propagators],spanProcessors:[`auto`,...kit.spanProcessors]});let provider=trace.getTracerProvider(),delegate=provider instanceof ProxyTracerProvider?provider.getDelegate():provider;if(!canFlush(delegate))throw Error(`Interfere could not register the Vercel tracer provider. Use buildInterfereOtelKit with an existing OpenTelemetry bootstrap.`);tracerProvider=delegate}else{let provider=new NodeTracerProvider({idGenerator:kit.idGenerator,resource,spanProcessors:kit.spanProcessors});propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[kit.traceContextPropagator,...kit.propagators]})),provider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),tracerProvider=provider}loggerProvider=new LoggerProvider({processors:kit.logRecordProcessors,resource});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),registerInstrumentations({instrumentations:kit.instrumentations,tracerProvider:trace.getTracerProvider()}),opts.consoleBridge!==!1&&kit.enableConsoleBridge(),installGlobalHandlers({captureUncaughtException:opts.captureUncaughtException??!0,captureUnhandledRejection:opts.captureUnhandledRejection??!0,exitOnUncaughtException:process.env.NODE_ENV===`production`,flush}),setServerFlush(flush),registered=!0}export{PrerenderSafeIdGenerator,buildInterfereOtelKit,flush,onRequestError,register};
@@ -8,6 +8,7 @@ interface InterfereInjectedValues {
8
8
  readonly __INTERFERE_FORCE_ENABLE__?: boolean;
9
9
  readonly __INTERFERE_PUBLIC_KEY__?: string;
10
10
  readonly __INTERFERE_RELEASE_SLUG__: string | null;
11
+ readonly __INTERFERE_SERVICE_NAME__?: string;
11
12
  }
12
13
  /** Build-time `/v3/config` request options for remote SDK configuration. */
13
14
  export interface RemoteConfigFetch {
@@ -0,0 +1,12 @@
1
+ import { ResolvedBuildConfig } from "../../config.mjs";
2
+ //#region src/internal/build/injected-values.d.ts
3
+ export declare function interfereInjectedValues(config: ResolvedBuildConfig, apiHost: string | undefined, forceEnable: boolean, serviceName: string | undefined): import("@interfere/helpers/omit-undefined").OmitUndefined<{
4
+ __INTERFERE_SERVICE_NAME__: string | undefined;
5
+ __INTERFERE_RELEASE_SLUG__: (`rel_${string}` & import("zod").$brand<"ReleaseSlug">) | null;
6
+ __INTERFERE_BUILD__: boolean;
7
+ __INTERFERE_PUBLIC_KEY__: string | undefined;
8
+ __INTERFERE_API_URL__: string | undefined;
9
+ __INTERFERE_ENVIRONMENT__: string | undefined;
10
+ __INTERFERE_FORCE_ENABLE__: boolean | undefined;
11
+ }>;
12
+ //#endregion
@@ -0,0 +1 @@
1
+ import{omitUndefined}from"@interfere/helpers/omit-undefined";import{API_URL_GLOBAL,BUILD_MARKER_GLOBAL,ENVIRONMENT_GLOBAL,FORCE_ENABLE_GLOBAL,PUBLIC_KEY_GLOBAL,RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{SERVICE_NAME_GLOBAL}from"@interfere/types/sdk/service-name";function interfereInjectedValues(config,apiHost,forceEnable,serviceName){let releaseSlug=forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug;return omitUndefined({[SERVICE_NAME_GLOBAL]:serviceName,[RELEASE_SLUG_GLOBAL]:releaseSlug,[BUILD_MARKER_GLOBAL]:!0,[PUBLIC_KEY_GLOBAL]:config.publicKey||void 0,[API_URL_GLOBAL]:config.publicKey&&apiHost?apiHost:void 0,[ENVIRONMENT_GLOBAL]:config.environment||void 0,[FORCE_ENABLE_GLOBAL]:forceEnable?!0:void 0})}export{interfereInjectedValues};
@@ -1 +1 @@
1
- import{log}from"../logger.mjs";import{readNextMajorVersion}from"../setup-warnings.mjs";import{detectActiveBundler}from"./detect-bundler.mjs";import{resolveReleaseRequest}from"./release/index.mjs";import{discoverSourceMaps}from"./source-maps/discover.mjs";import{reportUnmappedClientChunks}from"./source-maps/guard.mjs";import{EMPTY_TELEMETRY}from"./telemetry.mjs";import{classifyReleasePipelineError}from"@interfere/types/sdk/build-diagnostics";import{InterfereClient}from"@interfere/javascript";import{sourceMapUploadFetch}from"@interfere/build/source-map-upload-fetch";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{uploadSourceMapChunks}from"@interfere/build/upload-source-map-chunks";import{completeSourceMapsResponse}from"@interfere/types/data/source-maps";import{releaseBuildResultSchema}from"@interfere/types/releases/build-telemetry";import{createReleaseResponse}from"@interfere/types/releases/definition";import{extractRichness}from"@interfere/types/sdk/source-map-richness";async function runBuildPipeline(context,metadata){let client=new InterfereClient({fetch:sourceMapUploadFetch,baseUrl:metadata.apiUrl,apiKey:{token:metadata.apiKey},maxRetries:4,timeoutInSeconds:10}),start=performance.now(),timing={...EMPTY_TELEMETRY},{releaseSlug}=metadata,files=[],cleanupFailures=[],reportResult=`pipeline_failed`;try{let bundler=detectActiveBundler(process.env,process.argv,readNextMajorVersion(context.projectDir)),discovered=metadata.uploadSourceMaps||metadata.purgeSourceMaps?await discoverSourceMaps({bundler,...context}):{files:[],missingSourceMapCount:0,sourceFileCount:0};({files}=discovered);let{sourceFileCount}=discovered;metadata.uploadSourceMaps&&discovered.missingSourceMapCount>0&&log.warn(`Some source maps are missing`,[`${discovered.missingSourceMapCount} referenced source map(s) not found. Errors in those chunks will not resolve to source.`]);let config=await client.releases.getReleasesConfig({abortSignal:AbortSignal.timeout(6e4)});timing.discover=Math.round(performance.now()-start),timing.fileCount=files.length;let request=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config);if(!request.ok)return reportResult=null,{accepted:!1,fileCount:0,reason:request.reason};if(metadata.uploadSourceMaps&&files.length===0)return reportResult=`no_source_maps`,{accepted:!1,fileCount:0,reason:`no_source_maps`};metadata.uploadSourceMaps&&await reportUnmappedClientChunks({discovered:files,...context});let registering=performance.now(),release=createReleaseResponse.parse(await client.releases.createRelease(request.request,{abortSignal:AbortSignal.timeout(6e4)})),registered=performance.now();if(releaseSlug=release.destination.slug,timing.createRelease=Math.round(registered-registering),metadata.uploadSourceMaps){let signFiles=files.map(file=>({path:file.path,sizeBytes:new TextEncoder().encode(file.content).byteLength,...extractRichness(file.content)})),environment=metadata.environment??void 0,signed=await client.releases.signSourceMapUploads({releaseSlug,environment,files:signFiles},{abortSignal:AbortSignal.timeout(6e4)});timing.totalBytes=await uploadSourceMapChunks({compression:`gzip`,client,environment,releaseSlug,files,signed});let accepted=completeSourceMapsResponse.safeParse(await client.releases.finalizeSourceMaps({releaseSlug,environment,bundler,sourceFileCount,files:files.map(({path,hash,debugId,chunkUrl})=>({path,hash,debugId,chunkUrl}))},{abortSignal:AbortSignal.timeout(Math.max(0,signed.expiresAt*1e3-Date.now()))}));if(!accepted.success||!accepted.data.ok||accepted.data.fileCount!==files.length)throw Error(`Source-map server did not accept the complete batch`)}return timing.upload=metadata.uploadSourceMaps?Math.round(performance.now()-registered):0,reportResult=`ok`,{accepted:!0,buildId:release.build.hash,cleanupFailures,config,fileCount:metadata.uploadSourceMaps?files.length:0,release,timing}}catch(error){throw reportResult=releaseBuildResultSchema.safeParse(classifyReleasePipelineError(error).toLowerCase()).data??`pipeline_failed`,error}finally{let cleanupStart=performance.now();if(metadata.purgeSourceMaps)try{cleanupFailures.push(...await cleanupSourceMaps(files))}catch(error){cleanupFailures.push({absolute:context.distDir,code:void 0,message:error instanceof Error?error.message:String(error)})}timing.cleanup=Math.round(performance.now()-cleanupStart),cleanupFailures.length>0&&log.warn(`Source-map cleanup left files on disk`,cleanupFailures.map(failure=>`${failure.absolute}: ${failure.message}`)),timing.total=Math.round(performance.now()-start),reportResult!==null&&await client.releases.reportTelemetry({...timing,result:reportResult,releaseSlug},{abortSignal:AbortSignal.timeout(1e4)}).catch(()=>void 0)}}export{runBuildPipeline};
1
+ import{log}from"../logger.mjs";import{readNextMajorVersion}from"../setup-warnings.mjs";import{detectActiveBundler}from"./detect-bundler.mjs";import{resolveReleaseRequest}from"./release/index.mjs";import{discoverSourceMaps}from"./source-maps/discover.mjs";import{reportUnmappedClientChunks}from"./source-maps/guard.mjs";import{EMPTY_TELEMETRY}from"./telemetry.mjs";import{PUBLIC_KEY_HEADER}from"@interfere/constants/api";import{classifyReleasePipelineError}from"@interfere/types/sdk/build-diagnostics";import{InterfereClient}from"@interfere/javascript";import{sourceMapUploadFetch}from"@interfere/build/source-map-upload-fetch";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{uploadSourceMapChunks}from"@interfere/build/upload-source-map-chunks";import{completeSourceMapsResponse}from"@interfere/types/data/source-maps";import{releaseBuildResultSchema}from"@interfere/types/releases/build-telemetry";import{createReleaseResponse}from"@interfere/types/releases/definition";import{extractRichness}from"@interfere/types/sdk/source-map-richness";async function runBuildPipeline(context,metadata){let client=new InterfereClient({fetch:sourceMapUploadFetch,baseUrl:metadata.apiUrl,apiKey:{token:metadata.apiKey},headers:{[PUBLIC_KEY_HEADER]:metadata.publicKey},maxRetries:4,timeoutInSeconds:10}),start=performance.now(),timing={...EMPTY_TELEMETRY},{releaseSlug}=metadata,files=[],cleanupFailures=[],reportResult=`pipeline_failed`;try{let bundler=detectActiveBundler(process.env,process.argv,readNextMajorVersion(context.projectDir)),discovered=metadata.uploadSourceMaps||metadata.purgeSourceMaps?await discoverSourceMaps({bundler,...context}):{files:[],missingSourceMapCount:0,sourceFileCount:0};({files}=discovered);let{sourceFileCount}=discovered;metadata.uploadSourceMaps&&discovered.missingSourceMapCount>0&&log.warn(`Some source maps are missing`,[`${discovered.missingSourceMapCount} referenced source map(s) not found. Errors in those chunks will not resolve to source.`]);let config=await client.releases.getReleasesConfig({abortSignal:AbortSignal.timeout(6e4)});timing.discover=Math.round(performance.now()-start),timing.fileCount=files.length;let request=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config);if(!request.ok)return reportResult=null,{accepted:!1,fileCount:0,reason:request.reason};if(metadata.uploadSourceMaps&&files.length===0)return reportResult=`no_source_maps`,{accepted:!1,fileCount:0,reason:`no_source_maps`};metadata.uploadSourceMaps&&await reportUnmappedClientChunks({discovered:files,...context});let registering=performance.now(),release=createReleaseResponse.parse(await client.releases.createRelease(request.request,{abortSignal:AbortSignal.timeout(6e4)})),registered=performance.now();if(releaseSlug=release.destination.slug,timing.createRelease=Math.round(registered-registering),metadata.uploadSourceMaps){let signFiles=files.map(file=>({path:file.path,sizeBytes:new TextEncoder().encode(file.content).byteLength,...extractRichness(file.content)})),environment=metadata.environment??void 0,signed=await client.releases.signSourceMapUploads({releaseSlug,environment,files:signFiles},{abortSignal:AbortSignal.timeout(6e4)});timing.totalBytes=await uploadSourceMapChunks({compression:`gzip`,client,environment,releaseSlug,files,signed});let accepted=completeSourceMapsResponse.safeParse(await client.releases.finalizeSourceMaps({releaseSlug,environment,bundler,sourceFileCount,files:files.map(({path,hash,debugId,chunkUrl})=>({path,hash,debugId,chunkUrl}))},{abortSignal:AbortSignal.timeout(Math.max(0,signed.expiresAt*1e3-Date.now()))}));if(!accepted.success||!accepted.data.ok||accepted.data.fileCount!==files.length)throw Error(`Source-map server did not accept the complete batch`)}return timing.upload=metadata.uploadSourceMaps?Math.round(performance.now()-registered):0,reportResult=`ok`,{accepted:!0,buildId:release.build.hash,cleanupFailures,config,fileCount:metadata.uploadSourceMaps?files.length:0,release,timing}}catch(error){throw reportResult=releaseBuildResultSchema.safeParse(classifyReleasePipelineError(error).toLowerCase()).data??`pipeline_failed`,error}finally{let cleanupStart=performance.now();if(metadata.purgeSourceMaps)try{cleanupFailures.push(...await cleanupSourceMaps(files))}catch(error){cleanupFailures.push({absolute:context.distDir,code:void 0,message:error instanceof Error?error.message:String(error)})}timing.cleanup=Math.round(performance.now()-cleanupStart),cleanupFailures.length>0&&log.warn(`Source-map cleanup left files on disk`,cleanupFailures.map(failure=>`${failure.absolute}: ${failure.message}`)),timing.total=Math.round(performance.now()-start),reportResult!==null&&await client.releases.reportTelemetry({...timing,result:reportResult,releaseSlug},{abortSignal:AbortSignal.timeout(1e4)}).catch(()=>void 0)}}export{runBuildPipeline};
@@ -10,6 +10,7 @@
10
10
  */
11
11
  export declare function isEnabledOnServer(): boolean;
12
12
  export declare function readInterfereEnv(): {
13
+ serviceName: string | null;
13
14
  apiKey: string | null;
14
15
  apiUrl: string;
15
16
  nextRuntime: string | null;
@@ -1 +1 @@
1
- import{interfereDisabled}from"./build-marker-guard.mjs";import{API_URL}from"@interfere/constants/api";import{ENVIRONMENT_GLOBAL}from"@interfere/types/sdk/injected-globals";import{getStringGlobal}from"@interfere/react/util/global";import{parseEnvValue}from"@interfere/types/sdk/env";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return interfereDisabled()?!1:process.env.NODE_ENV===`production`||!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function readInterfereEnv(){let nodeEnvironment=resolveEnvironment({env:process.env,explicit:getStringGlobal(ENVIRONMENT_GLOBAL)});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:interfereDisabled()?null:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY),release:{destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID),sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID)}}}export{isEnabledOnServer,readInterfereEnv};
1
+ import{interfereDisabled}from"./build-marker-guard.mjs";import{API_URL}from"@interfere/constants/api";import{ENVIRONMENT_GLOBAL}from"@interfere/types/sdk/injected-globals";import{SERVICE_NAME_GLOBAL}from"@interfere/types/sdk/service-name";import{getStringGlobal}from"@interfere/react/util/global";import{parseEnvValue}from"@interfere/types/sdk/env";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return interfereDisabled()?!1:process.env.NODE_ENV===`production`||!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function readInterfereEnv(){let nodeEnvironment=resolveEnvironment({env:process.env,explicit:getStringGlobal(ENVIRONMENT_GLOBAL)});return{serviceName:getStringGlobal(SERVICE_NAME_GLOBAL)??null,apiKey:parseEnvValue(process.env.INTERFERE_API_KEY),apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,nextRuntime:parseEnvValue(process.env.NEXT_RUNTIME),nodeEnvironment,publicKey:interfereDisabled()?null:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY),release:{destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID),sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID)}}}export{isEnabledOnServer,readInterfereEnv};
@@ -1 +1 @@
1
- import{deriveReleaseSlug,releaseSlugSchema}from"@interfere/types/releases/slug";import{RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{resolveReleaseCommitSha}from"@interfere/types/sdk/github-source";const noGit=()=>null;function readBakedReleaseSlug(){let globals=globalThis;if(!(RELEASE_SLUG_GLOBAL in globals))return null;let parsed=releaseSlugSchema.safeParse(globals[RELEASE_SLUG_GLOBAL]);return parsed.success?parsed.data:null}function resolveReleaseSlug(runGit=noGit){let baked=readBakedReleaseSlug();if(baked)return{commitSha:null,slug:baked};let commitSha=resolveReleaseCommitSha(process.env,runGit);return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
1
+ import{RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{deriveReleaseSlug,releaseSlugSchema}from"@interfere/types/releases/slug";import{resolveReleaseCommitSha}from"@interfere/types/sdk/github-source";const noGit=()=>null;function readBakedReleaseSlug(){let globals=globalThis;if(!(RELEASE_SLUG_GLOBAL in globals))return null;let parsed=releaseSlugSchema.safeParse(globals[RELEASE_SLUG_GLOBAL]);return parsed.success?parsed.data:null}function resolveReleaseSlug(runGit=noGit){let baked=readBakedReleaseSlug();if(baked)return{commitSha:null,slug:baked};let commitSha=resolveReleaseCommitSha(process.env,runGit);return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
@@ -1,4 +1,4 @@
1
- //#region src/internal/server/server-action-marker.d.ts
1
+ //#region src/internal/server/actions/server-action-marker.d.ts
2
2
  export interface ServerActionMarker {
3
3
  readonly argsShape?: string;
4
4
  readonly name: string;
@@ -1,5 +1,5 @@
1
1
  import { NextMiddleware, NextRequest } from "next/server";
2
- //#region src/internal/server/wrap-proxy.d.ts
2
+ //#region src/internal/server/actions/wrap-proxy.d.ts
3
3
  /**
4
4
  * Wraps a Next.js proxy (formerly `middleware`) so thrown errors are captured
5
5
  * by Interfere (mechanism `auto.function.nextjs.proxy`, with request method +
@@ -0,0 +1 @@
1
+ import{captureError}from"../capture/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:{handled:!1,type:MECHANISM_TYPE.nextjs.proxy},nextjs:{requestMethod:request.method,requestPath:request.nextUrl.pathname}})}export{withInterfereProxy};
@@ -1,4 +1,4 @@
1
- //#region src/internal/server/wrap-server-action.d.ts
1
+ //#region src/internal/server/actions/wrap-server-action.d.ts
2
2
  interface ServerActionOptions {
3
3
  /** Derives argument types and class names for args_shape without recording values. */
4
4
  readonly args?: readonly unknown[];
@@ -1,5 +1,5 @@
1
- import { CaptureErrorContext, OnRequestErrorContext } from "./types.mjs";
2
- //#region src/internal/server/capture.d.ts
1
+ import { CaptureErrorContext, OnRequestErrorContext } from "../types.mjs";
2
+ //#region src/internal/server/capture/capture.d.ts
3
3
  export declare function captureError(error: unknown, _request?: unknown, context?: CaptureErrorContext): void;
4
4
  interface OnRequestErrorRequest {
5
5
  readonly headers?: Record<string, string>;
@@ -0,0 +1 @@
1
+ import{isEnabledOnServer}from"../../env.mjs";import{flushServerTelemetry}from"../exporters/server-flush.mjs";import{isPluginEnabled}from"../remote-config.mjs";import{readServerAction}from"../actions/server-action-marker.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{SpanKind,SpanStatusCode,trace}from"@opentelemetry/api";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";import{exceptionSpanName}from"@interfere/types/sdk/throw-site";const ON_REQUEST_ERROR_MECHANISM={handled:!1,synthetic:!1,type:MECHANISM_TYPE.nextjs.onRequestError},SERVER_ACTION_MECHANISM={handled:!1,synthetic:!1,type:MECHANISM_TYPE.nextjs.serverAction},ROUTE_HANDLER_MECHANISM={handled:!1,synthetic:!1,type:MECHANISM_TYPE.nextjs.routeHandler},DEFAULT_CAPTURE_ERROR_MECHANISM={handled:!0,type:MECHANISM_TYPE.nextjs.captureError},CONTROL_FLOW_DIGEST_PREFIXES=[`NEXT_REDIRECT`,`NEXT_NOT_FOUND`,`NEXT_HTTP_ERROR_FALLBACK`];function isNextControlFlowError(error){if(!(error instanceof Error))return!1;let digest=`digest`in error?error.digest:void 0;return typeof digest==`string`&&CONTROL_FLOW_DIGEST_PREFIXES.some(prefix=>digest.startsWith(prefix))}const seen=new WeakSet;function addNextjsAttrs(attrs,nextjs,handled){nextjs&&(nextjs.errorDigest&&(attrs[`interfere.error.digest`]=nextjs.errorDigest),nextjs.requestMethod&&(attrs[`http.request.method`]=nextjs.requestMethod),nextjs.requestPath&&(attrs[`url.path`]=nextjs.requestPath),nextjs.routePath&&(attrs[`http.route`]=nextjs.routePath),nextjs.routeType===`route`&&!handled&&(attrs[`http.response.status_code`]=500))}function addRequestContextAttrs(attrs,context){if(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}function addExceptionAttrs(attrs,value){if(isNonErrorException(value)){attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`;return}attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack)}function buildAttrs(value,mechanism,context){let attrs={"interfere.exception.handled":mechanism.handled,"interfere.exception.mechanism":mechanism.type};return addNextjsAttrs(attrs,context?.nextjs,mechanism.handled),addRequestContextAttrs(attrs,context),addExceptionAttrs(attrs,value),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(exceptionSpanName(value),{attributes:{"interfere.capture.kind":`exception`},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`)&&!isNextControlFlowError(error)){if(error instanceof Error){if(seen.has(error))return;seen.add(error)}recordException({context,error,mechanism:context?.mechanism??DEFAULT_CAPTURE_ERROR_MECHANISM})}}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}async function onRequestError(error,request,context){if(isNextControlFlowError(error)||seen.has(error)||(seen.add(error),!isEnabledOnServer())||!isPluginEnabled(`errors`))return;let action=readServerAction(error),mechanism=ON_REQUEST_ERROR_MECHANISM;action?mechanism=SERVER_ACTION_MECHANISM:context.routeType===`route`&&(mechanism=ROUTE_HANDLER_MECHANISM);let requestHeaders=pickAllowedHeaders(request?.headers);recordException({context:omitUndefined({mechanism,nextjs:omitUndefined({...context,requestMethod:request?.method||void 0,requestPath:request?.path||void 0,errorDigest:error.digest||void 0}),requestHeaders,serverAction:action??void 0}),error,mechanism});try{await flushServerTelemetry()}catch{return}}export{captureError,onRequestError};
@@ -1,4 +1,4 @@
1
- //#region src/internal/server/console-bridge.d.ts
1
+ //#region src/internal/server/capture/console-bridge.d.ts
2
2
  /**
3
3
  * Patches `console.{debug,log,info,warn,error}` to additionally emit OTel
4
4
  * `LogRecord`s alongside the original output. Every log record carries
@@ -1,4 +1,4 @@
1
- //#region src/internal/server/global-handlers.d.ts
1
+ //#region src/internal/server/capture/global-handlers.d.ts
2
2
  export interface GlobalHandlerOptions {
3
3
  readonly captureUncaughtException: boolean;
4
4
  readonly captureUnhandledRejection: boolean;
@@ -1,7 +1,7 @@
1
1
  import { ExportResult } from "@opentelemetry/core";
2
2
  import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
3
3
  import { InterfereClient } from "@interfere/javascript";
4
- //#region src/internal/server/edge-exporter.d.ts
4
+ //#region src/internal/server/exporters/edge-exporter.d.ts
5
5
  export declare class FetchTraceExporter implements SpanExporter {
6
6
  private readonly client;
7
7
  constructor(options: InterfereClient.Options);
@@ -0,0 +1 @@
1
+ import{exportTracePayload}from"../tracing/trace-payload.mjs";import{ExportResultCode}from"@opentelemetry/core";import{TELEMETRY_EXPORT_TIMEOUT_MS,retryTelemetry}from"@interfere/helpers/retry-telemetry";import{InterfereClient}from"@interfere/javascript";import{JsonTraceSerializer}from"@opentelemetry/otlp-transformer";var FetchTraceExporter=class{client;constructor(options){this.client=new InterfereClient({...options,maxRetries:0,timeoutInSeconds:TELEMETRY_EXPORT_TIMEOUT_MS/1e3,fetch:Object.assign(async(url,init)=>retryTelemetry(async signal=>globalThis.fetch(url,{...init,keepalive:!0,signal}),init?.signal),globalThis.fetch)})}export(spans,resultCallback){if(spans.length===0){resultCallback({code:ExportResultCode.SUCCESS});return}let payload=JsonTraceSerializer.serializeRequest(spans);exportTracePayload(payload,this.client,resultCallback)}async shutdown(){return Promise.resolve()}async forceFlush(){return Promise.resolve()}};export{FetchTraceExporter};
@@ -1,4 +1,4 @@
1
- //#region src/internal/server/server-flush.d.ts
1
+ //#region src/internal/server/exporters/server-flush.d.ts
2
2
  export declare function setServerFlush(fn: () => Promise<void>): void;
3
3
  export declare function flushServerTelemetry(): Promise<void>;
4
4
  //#endregion
@@ -63,13 +63,11 @@ export interface ServerInstrumentationOptions {
63
63
  urls?: UrlRedactionOption;
64
64
  };
65
65
  /**
66
- * Application service name. Defaults to `OTEL_SERVICE_NAME`, then OTel's
67
- * `unknown_service:<executable>` or `unknown_service` when unavailable.
66
+ * Application service name. Defaults to `OTEL_SERVICE_NAME`, then the
67
+ * build-inferred app name, then OpenTelemetry's unknown-service fallback.
68
68
  */
69
69
  serviceName?: string;
70
70
  /** Optional application namespace for grouping related services. */
71
71
  serviceNamespace?: string;
72
- /** Overrides build-injected and environment tracing settings. Use full for direct export during local development. */
73
- serverTracing?: "full" | "drain";
74
72
  }
75
73
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
2
2
  import { Context, TextMapSetter } from "@opentelemetry/api";
3
- //#region src/internal/server/cache-safe-trace-context.d.ts
3
+ //#region src/internal/server/tracing/cache-safe-trace-context.d.ts
4
4
  export interface CacheSafeTraceContextPropagatorOptions {
5
5
  isStaticRender?: () => boolean;
6
6
  }
@@ -0,0 +1 @@
1
+ import{isStaticGenerationRender}from"../static-generation.mjs";import{W3CTraceContextPropagator}from"@opentelemetry/core";import{z}from"zod";const metadataEntrySchema=z.object({key:z.string()});var CacheSafeTraceContextPropagator=class extends W3CTraceContextPropagator{isStaticRender;constructor(options={}){super(),this.isStaticRender=options.isStaticRender??isStaticGenerationRender}inject(context,carrier,setter){if(Array.isArray(carrier)){for(let index=carrier.length-1;index>=0;index--){let entry=metadataEntrySchema.safeParse(carrier[index]);entry.success&&(entry.data.key===`traceparent`||entry.data.key===`tracestate`)&&carrier.splice(index,1)}if(this.isStaticRender())return}super.inject(context,carrier,setter)}};export{CacheSafeTraceContextPropagator};
@@ -1,5 +1,5 @@
1
1
  import { IdGenerator } from "@opentelemetry/sdk-trace-base";
2
- //#region src/internal/server/counter-id-generator.d.ts
2
+ //#region src/internal/server/tracing/counter-id-generator.d.ts
3
3
  export declare class CounterIdGenerator implements IdGenerator {
4
4
  private readonly tracePrefix;
5
5
  private readonly spanPrefix;
@@ -1,5 +1,5 @@
1
1
  import { Context, TextMapPropagator, TextMapSetter } from "@opentelemetry/api";
2
- //#region src/internal/server/freshness-propagator.d.ts
2
+ //#region src/internal/server/tracing/freshness-propagator.d.ts
3
3
  export interface FreshnessStampingPropagatorOptions {
4
4
  isStaticRender?: () => boolean;
5
5
  }
@@ -0,0 +1 @@
1
+ import{isStaticGenerationRender}from"../static-generation.mjs";import{TraceState,isTracingSuppressed}from"@opentelemetry/core";import{isSpanContextValid,trace}from"@opentelemetry/api";import{TRACESTATE_FRESHNESS_KEY,freshnessStamp}from"@interfere/types/sdk/trace-freshness";const TRACE_STATE_HEADER=`tracestate`;function carrierHoldsTraceparent(carrier){return carrier.some(entry=>typeof entry==`object`&&!!entry&&`key`in entry&&entry.key===`traceparent`)}var FreshnessStampingPropagator=class{isStaticRender;constructor(options={}){this.isStaticRender=options.isStaticRender??isStaticGenerationRender}inject(context,carrier,setter){let spanContext=trace.getSpanContext(context);if(!spanContext||!isSpanContextValid(spanContext)||isTracingSuppressed(context)||Array.isArray(carrier)&&this.isStaticRender()&&!carrierHoldsTraceparent(carrier))return;let traceState=(spanContext.traceState??new TraceState).set(TRACESTATE_FRESHNESS_KEY,freshnessStamp(Date.now()));setter.set(carrier,TRACE_STATE_HEADER,traceState.serialize())}extract(context){return context}fields(){return[TRACE_STATE_HEADER]}};export{FreshnessStampingPropagator};
@@ -1,5 +1,5 @@
1
1
  import { IdGenerator } from "@opentelemetry/sdk-trace-base";
2
- //#region src/internal/server/id-generator.d.ts
2
+ //#region src/internal/server/tracing/id-generator.d.ts
3
3
  /** Generates random telemetry IDs, with counter IDs when runtime randomness is unavailable. */
4
4
  export declare class PrerenderSafeIdGenerator implements IdGenerator {
5
5
  private readonly random;
@@ -1,4 +1,4 @@
1
- //#region src/internal/server/trace-meta.d.ts
1
+ //#region src/internal/server/tracing/trace-meta.d.ts
2
2
  /**
3
3
  * Server Component that emits a `<meta name="traceparent">` tag so the
4
4
  * client SDK can stitch browser spans onto the server-side trace.
@@ -0,0 +1 @@
1
+ import{isStaticGenerationRender}from"../static-generation.mjs";import{activeTraceparent}from"./traceparent.mjs";import{jsx}from"react/jsx-runtime";function TraceMeta(){if(isStaticGenerationRender())return null;let traceparent=activeTraceparent();return traceparent?jsx(`meta`,{content:traceparent,name:`traceparent`}):null}export{TraceMeta};
@@ -1,5 +1,5 @@
1
1
  import { ExportResult } from "@opentelemetry/core";
2
2
  import { InterfereClient } from "@interfere/javascript";
3
- //#region src/internal/server/trace-payload.d.ts
3
+ //#region src/internal/server/tracing/trace-payload.d.ts
4
4
  export declare function exportTracePayload(payload: Uint8Array | null | undefined, client: InterfereClient, resultCallback: (result: ExportResult) => void): void;
5
5
  //#endregion
@@ -1,4 +1,4 @@
1
- //#region src/internal/server/traceparent.d.ts
1
+ //#region src/internal/server/tracing/traceparent.d.ts
2
2
  /**
3
3
  * Build a W3C `traceparent` string from the currently-active OTel
4
4
  * span, or `null` if no span is active or the trace is not sampled.
@@ -1,4 +1,4 @@
1
- import { ServerActionMarker } from "./server-action-marker.mjs";
1
+ import { ServerActionMarker } from "./actions/server-action-marker.mjs";
2
2
  import { NextjsContext } from "@interfere/types/sdk/plugins/context/next";
3
3
  import { ErrorMechanism } from "@interfere/types/sdk/plugins/payload/errors";
4
4
  //#region src/internal/server/types.d.ts
package/dist/package.mjs CHANGED
@@ -1 +1 @@
1
- var name=`@interfere/next`,version=`12.1.4`;export{name,version};
1
+ var name=`@interfere/next`,version=`12.3.0`;export{name,version};
package/dist/server.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CaptureErrorContext, OnRequestErrorContext } from "./internal/server/types.mjs";
2
- import { captureError, onRequestError } from "./internal/server/capture.mjs";
3
- import { TraceMeta } from "./internal/server/trace-meta.mjs";
4
- import { withInterfereProxy } from "./internal/server/wrap-proxy.mjs";
5
- import { withInterfereServerAction } from "./internal/server/wrap-server-action.mjs";
2
+ import { captureError, onRequestError } from "./internal/server/capture/capture.mjs";
3
+ import { withInterfereProxy } from "./internal/server/actions/wrap-proxy.mjs";
4
+ import { withInterfereServerAction } from "./internal/server/actions/wrap-server-action.mjs";
5
+ import { TraceMeta } from "./internal/server/tracing/trace-meta.mjs";
6
6
  export { type CaptureErrorContext, type OnRequestErrorContext, TraceMeta, captureError, onRequestError, withInterfereProxy, withInterfereServerAction };
package/dist/server.mjs CHANGED
@@ -1 +1 @@
1
- import{captureError,onRequestError}from"./internal/server/capture.mjs";import{TraceMeta}from"./internal/server/trace-meta.mjs";import{withInterfereProxy}from"./internal/server/wrap-proxy.mjs";import{withInterfereServerAction}from"./internal/server/wrap-server-action.mjs";export{TraceMeta,captureError,onRequestError,withInterfereProxy,withInterfereServerAction};
1
+ import{captureError,onRequestError}from"./internal/server/capture/capture.mjs";import{TraceMeta}from"./internal/server/tracing/trace-meta.mjs";import{withInterfereProxy}from"./internal/server/actions/wrap-proxy.mjs";import{withInterfereServerAction}from"./internal/server/actions/wrap-server-action.mjs";export{TraceMeta,captureError,onRequestError,withInterfereProxy,withInterfereServerAction};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/next",
3
- "version": "12.1.4",
3
+ "version": "12.3.0",
4
4
  "description": "Build software that never breaks.",
5
5
  "keywords": [
6
6
  "error-tracking",
@@ -97,12 +97,12 @@
97
97
  "typecheck": "bun check"
98
98
  },
99
99
  "dependencies": {
100
- "@interfere/build": "^0.3.2",
101
- "@interfere/constants": "^9.2.0",
102
- "@interfere/helpers": "^9.2.1",
100
+ "@interfere/build": "^0.4.0",
101
+ "@interfere/constants": "^9.3.0",
102
+ "@interfere/helpers": "^9.3.0",
103
103
  "@interfere/javascript": "^0.3.0",
104
- "@interfere/react": "^12.1.4",
105
- "@interfere/types": "^10.3.0",
104
+ "@interfere/react": "^12.3.0",
105
+ "@interfere/types": "^10.5.0",
106
106
  "@opentelemetry/api": "1.9.1",
107
107
  "@opentelemetry/api-logs": "0.222.0",
108
108
  "@opentelemetry/baggage-span-processor": "0.5.0",
@@ -123,7 +123,7 @@
123
123
  "zod": "4.6.6"
124
124
  },
125
125
  "devDependencies": {
126
- "@interfere/test-utils": "^10.0.1",
126
+ "@interfere/test-utils": "^10.0.2",
127
127
  "@interfere/typescript-config": "^9.0.2",
128
128
  "@interfere/vitest-config": "0.0.0",
129
129
  "@playwright/test": "1.63.0",
@@ -1 +0,0 @@
1
- import{isStaticGenerationRender}from"./static-generation.mjs";import{W3CTraceContextPropagator}from"@opentelemetry/core";import{z}from"zod";const metadataEntrySchema=z.object({key:z.string()});var CacheSafeTraceContextPropagator=class extends W3CTraceContextPropagator{isStaticRender;constructor(options={}){super(),this.isStaticRender=options.isStaticRender??isStaticGenerationRender}inject(context,carrier,setter){if(Array.isArray(carrier)){for(let index=carrier.length-1;index>=0;index--){let entry=metadataEntrySchema.safeParse(carrier[index]);entry.success&&(entry.data.key===`traceparent`||entry.data.key===`tracestate`)&&carrier.splice(index,1)}if(this.isStaticRender())return}super.inject(context,carrier,setter)}};export{CacheSafeTraceContextPropagator};
@@ -1 +0,0 @@
1
- import{isEnabledOnServer}from"../env.mjs";import{flushServerTelemetry}from"./server-flush.mjs";import{isPluginEnabled}from"./remote-config.mjs";import{readServerAction}from"./server-action-marker.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{SpanKind,SpanStatusCode,trace}from"@opentelemetry/api";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";const ON_REQUEST_ERROR_MECHANISM={handled:!1,synthetic:!1,type:MECHANISM_TYPE.nextjs.onRequestError},SERVER_ACTION_MECHANISM={handled:!1,synthetic:!1,type:MECHANISM_TYPE.nextjs.serverAction},ROUTE_HANDLER_MECHANISM={handled:!1,synthetic:!1,type:MECHANISM_TYPE.nextjs.routeHandler},DEFAULT_CAPTURE_ERROR_MECHANISM={handled:!0,type:MECHANISM_TYPE.nextjs.captureError},CONTROL_FLOW_DIGEST_PREFIXES=[`NEXT_REDIRECT`,`NEXT_NOT_FOUND`,`NEXT_HTTP_ERROR_FALLBACK`];function isNextControlFlowError(error){if(!(error instanceof Error))return!1;let digest=`digest`in error?error.digest:void 0;return typeof digest==`string`&&CONTROL_FLOW_DIGEST_PREFIXES.some(prefix=>digest.startsWith(prefix))}const seen=new WeakSet;function addNextjsAttrs(attrs,nextjs,handled){nextjs&&(nextjs.errorDigest&&(attrs[`interfere.error.digest`]=nextjs.errorDigest),nextjs.requestMethod&&(attrs[`http.request.method`]=nextjs.requestMethod),nextjs.requestPath&&(attrs[`url.path`]=nextjs.requestPath),nextjs.routePath&&(attrs[`http.route`]=nextjs.routePath),nextjs.routeType===`route`&&!handled&&(attrs[`http.response.status_code`]=500))}function addRequestContextAttrs(attrs,context){if(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}function addExceptionAttrs(attrs,value){if(isNonErrorException(value)){attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`;return}attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack)}function buildAttrs(value,mechanism,context){let attrs={"interfere.exception.handled":mechanism.handled,"interfere.exception.mechanism":mechanism.type};return addNextjsAttrs(attrs,context?.nextjs,mechanism.handled),addRequestContextAttrs(attrs,context),addExceptionAttrs(attrs,value),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`)&&!isNextControlFlowError(error)){if(error instanceof Error){if(seen.has(error))return;seen.add(error)}recordException({context,error,mechanism:context?.mechanism??DEFAULT_CAPTURE_ERROR_MECHANISM})}}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}async function onRequestError(error,request,context){if(isNextControlFlowError(error)||seen.has(error)||(seen.add(error),!isEnabledOnServer())||!isPluginEnabled(`errors`))return;let action=readServerAction(error),mechanism=ON_REQUEST_ERROR_MECHANISM;action?mechanism=SERVER_ACTION_MECHANISM:context.routeType===`route`&&(mechanism=ROUTE_HANDLER_MECHANISM);let requestHeaders=pickAllowedHeaders(request?.headers);recordException({context:omitUndefined({mechanism,nextjs:omitUndefined({...context,requestMethod:request?.method||void 0,requestPath:request?.path||void 0,errorDigest:error.digest||void 0}),requestHeaders,serverAction:action??void 0}),error,mechanism});try{await flushServerTelemetry()}catch{return}}export{captureError,onRequestError};
@@ -1 +0,0 @@
1
- import{exportTracePayload}from"./trace-payload.mjs";import{ExportResultCode}from"@opentelemetry/core";import{TELEMETRY_EXPORT_TIMEOUT_MS,retryTelemetry}from"@interfere/helpers/retry-telemetry";import{InterfereClient}from"@interfere/javascript";import{JsonTraceSerializer}from"@opentelemetry/otlp-transformer";var FetchTraceExporter=class{client;constructor(options){this.client=new InterfereClient({...options,maxRetries:0,timeoutInSeconds:TELEMETRY_EXPORT_TIMEOUT_MS/1e3,fetch:Object.assign(async(url,init)=>retryTelemetry(async signal=>globalThis.fetch(url,{...init,keepalive:!0,signal}),init?.signal),globalThis.fetch)})}export(spans,resultCallback){if(spans.length===0){resultCallback({code:ExportResultCode.SUCCESS});return}let payload=JsonTraceSerializer.serializeRequest(spans);exportTracePayload(payload,this.client,resultCallback)}async shutdown(){return Promise.resolve()}async forceFlush(){return Promise.resolve()}};export{FetchTraceExporter};
@@ -1 +0,0 @@
1
- import{isStaticGenerationRender}from"./static-generation.mjs";import{TraceState,isTracingSuppressed}from"@opentelemetry/core";import{isSpanContextValid,trace}from"@opentelemetry/api";import{TRACESTATE_FRESHNESS_KEY,freshnessStamp}from"@interfere/types/sdk/trace-freshness";const TRACE_STATE_HEADER=`tracestate`;function carrierHoldsTraceparent(carrier){return carrier.some(entry=>typeof entry==`object`&&!!entry&&`key`in entry&&entry.key===`traceparent`)}var FreshnessStampingPropagator=class{isStaticRender;constructor(options={}){this.isStaticRender=options.isStaticRender??isStaticGenerationRender}inject(context,carrier,setter){let spanContext=trace.getSpanContext(context);if(!spanContext||!isSpanContextValid(spanContext)||isTracingSuppressed(context)||Array.isArray(carrier)&&this.isStaticRender()&&!carrierHoldsTraceparent(carrier))return;let traceState=(spanContext.traceState??new TraceState).set(TRACESTATE_FRESHNESS_KEY,freshnessStamp(Date.now()));setter.set(carrier,TRACE_STATE_HEADER,traceState.serialize())}extract(context){return context}fields(){return[TRACE_STATE_HEADER]}};export{FreshnessStampingPropagator};
@@ -1 +0,0 @@
1
- import{isStaticGenerationRender}from"./static-generation.mjs";import{activeTraceparent}from"./traceparent.mjs";import{jsx}from"react/jsx-runtime";function TraceMeta(){if(isStaticGenerationRender())return null;let traceparent=activeTraceparent();return traceparent?jsx(`meta`,{content:traceparent,name:`traceparent`}):null}export{TraceMeta};
@@ -1 +0,0 @@
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:{handled:!1,type:MECHANISM_TYPE.nextjs.proxy},nextjs:{requestMethod:request.method,requestPath:request.nextUrl.pathname}})}export{withInterfereProxy};