@interfere/next 9.0.2 → 10.0.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 (166) hide show
  1. package/README.md +33 -5
  2. package/dist/config.d.mts +24 -5
  3. package/dist/config.d.mts.map +1 -1
  4. package/dist/config.mjs +38 -28
  5. package/dist/config.mjs.map +1 -1
  6. package/dist/instrument-client.d.mts +14 -3
  7. package/dist/instrument-client.d.mts.map +1 -1
  8. package/dist/instrument-client.mjs +7 -9
  9. package/dist/instrument-client.mjs.map +1 -1
  10. package/dist/instrumentation-client.d.mts +1 -0
  11. package/dist/instrumentation-client.mjs +22 -0
  12. package/dist/instrumentation-client.mjs.map +1 -0
  13. package/dist/instrumentation.d.mts +134 -0
  14. package/dist/instrumentation.d.mts.map +1 -0
  15. package/dist/instrumentation.edge.d.mts +35 -0
  16. package/dist/instrumentation.edge.d.mts.map +1 -0
  17. package/dist/instrumentation.edge.mjs +34 -0
  18. package/dist/instrumentation.edge.mjs.map +1 -0
  19. package/dist/instrumentation.mjs +165 -0
  20. package/dist/instrumentation.mjs.map +1 -0
  21. package/dist/internal/build/configure-build.d.mts +1 -2
  22. package/dist/internal/build/configure-build.d.mts.map +1 -1
  23. package/dist/internal/build/configure-build.mjs +10 -2
  24. package/dist/internal/build/configure-build.mjs.map +1 -1
  25. package/dist/internal/build/detect-bundler.d.mts +6 -0
  26. package/dist/internal/build/detect-bundler.d.mts.map +1 -0
  27. package/dist/internal/build/detect-bundler.mjs +9 -0
  28. package/dist/internal/build/detect-bundler.mjs.map +1 -0
  29. package/dist/internal/build/pipeline.d.mts +14 -1
  30. package/dist/internal/build/pipeline.d.mts.map +1 -1
  31. package/dist/internal/build/pipeline.mjs +26 -10
  32. package/dist/internal/build/pipeline.mjs.map +1 -1
  33. package/dist/internal/build/release/destinations/index.d.mts +14 -0
  34. package/dist/internal/build/release/destinations/index.d.mts.map +1 -0
  35. package/dist/internal/build/release/destinations/index.mjs +13 -0
  36. package/dist/internal/build/release/destinations/index.mjs.map +1 -0
  37. package/dist/internal/build/release/destinations/vercel.mjs.map +1 -1
  38. package/dist/internal/build/release/git.d.mts +13 -0
  39. package/dist/internal/build/release/git.d.mts.map +1 -1
  40. package/dist/internal/build/release/git.mjs +13 -2
  41. package/dist/internal/build/release/git.mjs.map +1 -1
  42. package/dist/internal/build/release/index.d.mts +2 -1
  43. package/dist/internal/build/release/index.d.mts.map +1 -1
  44. package/dist/internal/build/release/index.mjs +4 -5
  45. package/dist/internal/build/release/index.mjs.map +1 -1
  46. package/dist/internal/build/release/sources/github.mjs.map +1 -1
  47. package/dist/internal/build/release/sources/index.d.mts +21 -0
  48. package/dist/internal/build/release/sources/index.d.mts.map +1 -0
  49. package/dist/internal/build/release/sources/index.mjs +20 -0
  50. package/dist/internal/build/release/sources/index.mjs.map +1 -0
  51. package/dist/internal/build/source-maps/discover-turbopack.d.mts +32 -0
  52. package/dist/internal/build/source-maps/discover-turbopack.d.mts.map +1 -0
  53. package/dist/internal/build/source-maps/discover-turbopack.mjs +68 -0
  54. package/dist/internal/build/source-maps/discover-turbopack.mjs.map +1 -0
  55. package/dist/internal/build/source-maps/discover-webpack.d.mts +53 -0
  56. package/dist/internal/build/source-maps/discover-webpack.d.mts.map +1 -0
  57. package/dist/internal/build/source-maps/discover-webpack.mjs +112 -0
  58. package/dist/internal/build/source-maps/discover-webpack.mjs.map +1 -0
  59. package/dist/internal/build/source-maps/discover.d.mts +28 -10
  60. package/dist/internal/build/source-maps/discover.d.mts.map +1 -1
  61. package/dist/internal/build/source-maps/discover.mjs +22 -83
  62. package/dist/internal/build/source-maps/discover.mjs.map +1 -1
  63. package/dist/internal/build/source-maps/index.d.mts +2 -24
  64. package/dist/internal/build/source-maps/index.d.mts.map +1 -1
  65. package/dist/internal/build/source-maps/index.mjs +13 -23
  66. package/dist/internal/build/source-maps/index.mjs.map +1 -1
  67. package/dist/internal/build/source-maps/paths.d.mts +28 -0
  68. package/dist/internal/build/source-maps/paths.d.mts.map +1 -0
  69. package/dist/internal/build/source-maps/paths.mjs +49 -0
  70. package/dist/internal/build/source-maps/paths.mjs.map +1 -0
  71. package/dist/internal/build/source-maps/upload.d.mts +46 -0
  72. package/dist/internal/build/source-maps/upload.d.mts.map +1 -0
  73. package/dist/internal/build/source-maps/upload.mjs +134 -0
  74. package/dist/internal/build/source-maps/upload.mjs.map +1 -0
  75. package/dist/internal/build/value-injection-loader.mjs.map +1 -1
  76. package/dist/internal/env.d.mts +11 -2
  77. package/dist/internal/env.d.mts.map +1 -1
  78. package/dist/internal/env.mjs +12 -3
  79. package/dist/internal/env.mjs.map +1 -1
  80. package/dist/internal/logger.d.mts +9 -1
  81. package/dist/internal/logger.d.mts.map +1 -1
  82. package/dist/internal/logger.mjs +10 -2
  83. package/dist/internal/logger.mjs.map +1 -1
  84. package/dist/internal/release-slug.d.mts +25 -0
  85. package/dist/internal/release-slug.d.mts.map +1 -0
  86. package/dist/internal/release-slug.mjs +32 -0
  87. package/dist/internal/release-slug.mjs.map +1 -0
  88. package/dist/internal/route/handle-get.d.mts +14 -1
  89. package/dist/internal/route/handle-get.d.mts.map +1 -1
  90. package/dist/internal/route/handle-get.mjs +35 -14
  91. package/dist/internal/route/handle-get.mjs.map +1 -1
  92. package/dist/internal/route/handle-post.d.mts +11 -0
  93. package/dist/internal/route/handle-post.d.mts.map +1 -1
  94. package/dist/internal/route/handle-post.mjs +11 -50
  95. package/dist/internal/route/handle-post.mjs.map +1 -1
  96. package/dist/internal/route/proxy.d.mts +21 -1
  97. package/dist/internal/route/proxy.d.mts.map +1 -1
  98. package/dist/internal/route/proxy.mjs +61 -16
  99. package/dist/internal/route/proxy.mjs.map +1 -1
  100. package/dist/internal/server/capture.d.mts +2 -2
  101. package/dist/internal/server/capture.d.mts.map +1 -1
  102. package/dist/internal/server/capture.mjs +71 -37
  103. package/dist/internal/server/capture.mjs.map +1 -1
  104. package/dist/internal/server/console-bridge.d.mts +19 -0
  105. package/dist/internal/server/console-bridge.d.mts.map +1 -0
  106. package/dist/internal/server/console-bridge.mjs +112 -0
  107. package/dist/internal/server/console-bridge.mjs.map +1 -0
  108. package/dist/internal/server/id-generator.d.mts +38 -0
  109. package/dist/internal/server/id-generator.d.mts.map +1 -0
  110. package/dist/internal/server/id-generator.mjs +68 -0
  111. package/dist/internal/server/id-generator.mjs.map +1 -0
  112. package/dist/internal/server/instrumentation-options.d.mts +86 -0
  113. package/dist/internal/server/instrumentation-options.d.mts.map +1 -0
  114. package/dist/internal/server/instrumentation-options.mjs +1 -0
  115. package/dist/internal/server/remote-config.mjs +2 -2
  116. package/dist/internal/server/remote-config.mjs.map +1 -1
  117. package/dist/internal/server/trace-meta.d.mts +34 -0
  118. package/dist/internal/server/trace-meta.d.mts.map +1 -0
  119. package/dist/internal/server/trace-meta.mjs +41 -0
  120. package/dist/internal/server/trace-meta.mjs.map +1 -0
  121. package/dist/internal/server/traceparent.d.mts +16 -0
  122. package/dist/internal/server/traceparent.d.mts.map +1 -0
  123. package/dist/internal/server/traceparent.mjs +26 -0
  124. package/dist/internal/server/traceparent.mjs.map +1 -0
  125. package/dist/internal/server/types.d.mts +1 -7
  126. package/dist/internal/server/types.d.mts.map +1 -1
  127. package/dist/internal/setup-warnings.d.mts +17 -0
  128. package/dist/internal/setup-warnings.d.mts.map +1 -0
  129. package/dist/internal/setup-warnings.mjs +45 -0
  130. package/dist/internal/setup-warnings.mjs.map +1 -0
  131. package/dist/package.mjs +1 -1
  132. package/dist/provider.d.mts +23 -2
  133. package/dist/provider.d.mts.map +1 -0
  134. package/dist/provider.mjs +23 -1
  135. package/dist/provider.mjs.map +1 -0
  136. package/dist/route-handler.d.mts +7 -2
  137. package/dist/route-handler.d.mts.map +1 -1
  138. package/dist/route-handler.mjs +11 -9
  139. package/dist/route-handler.mjs.map +1 -1
  140. package/dist/server.d.mts +2 -2
  141. package/dist/server.mjs +2 -2
  142. package/package.json +73 -20
  143. package/dist/internal/route/sw-script.d.mts +0 -4
  144. package/dist/internal/route/sw-script.d.mts.map +0 -1
  145. package/dist/internal/route/sw-script.mjs +0 -38
  146. package/dist/internal/route/sw-script.mjs.map +0 -1
  147. package/dist/internal/server/dedupe.d.mts +0 -5
  148. package/dist/internal/server/dedupe.d.mts.map +0 -1
  149. package/dist/internal/server/dedupe.mjs +0 -11
  150. package/dist/internal/server/dedupe.mjs.map +0 -1
  151. package/dist/internal/server/envelope.d.mts +0 -14
  152. package/dist/internal/server/envelope.d.mts.map +0 -1
  153. package/dist/internal/server/envelope.mjs +0 -59
  154. package/dist/internal/server/envelope.mjs.map +0 -1
  155. package/dist/internal/server/normalize-request.d.mts +0 -7
  156. package/dist/internal/server/normalize-request.d.mts.map +0 -1
  157. package/dist/internal/server/normalize-request.mjs +0 -50
  158. package/dist/internal/server/normalize-request.mjs.map +0 -1
  159. package/dist/internal/server/runtime.d.mts +0 -14
  160. package/dist/internal/server/runtime.d.mts.map +0 -1
  161. package/dist/internal/server/runtime.mjs +0 -18
  162. package/dist/internal/server/runtime.mjs.map +0 -1
  163. package/dist/internal/server/transport.d.mts +0 -12
  164. package/dist/internal/server/transport.d.mts.map +0 -1
  165. package/dist/internal/server/transport.mjs +0 -26
  166. package/dist/internal/server/transport.mjs.map +0 -1
@@ -0,0 +1,112 @@
1
+ import { context, trace } from "@opentelemetry/api";
2
+ import { logs } from "@opentelemetry/api-logs";
3
+ //#region src/internal/server/console-bridge.ts
4
+ const ATTR_EXCEPTION_TYPE = "exception.type";
5
+ const ATTR_EXCEPTION_MESSAGE = "exception.message";
6
+ const ATTR_EXCEPTION_STACKTRACE = "exception.stacktrace";
7
+ const CONSOLE_METHODS = [
8
+ "debug",
9
+ "log",
10
+ "info",
11
+ "warn",
12
+ "error"
13
+ ];
14
+ const SEVERITY = {
15
+ debug: 5,
16
+ log: 9,
17
+ info: 9,
18
+ warn: 13,
19
+ error: 17
20
+ };
21
+ /**
22
+ * Bound at first failure log; subsequent failures only re-log every Nth
23
+ * occurrence so a misconfigured logger backend doesn't itself spam the
24
+ * console with bridge errors at line rate.
25
+ */
26
+ const BRIDGE_FAILURE_LOG_INTERVAL = 100;
27
+ function stringify(value) {
28
+ if (typeof value === "string") return value;
29
+ if (value instanceof Error) return value.stack ?? `${value.name}: ${value.message}`;
30
+ try {
31
+ return JSON.stringify(value);
32
+ } catch {
33
+ return String(value);
34
+ }
35
+ }
36
+ function extractException(args) {
37
+ for (const arg of args) if (arg instanceof Error) return arg;
38
+ }
39
+ function buildExceptionAttributes(args) {
40
+ const exception = extractException(args);
41
+ if (!exception) return;
42
+ const attrs = {
43
+ [ATTR_EXCEPTION_TYPE]: exception.name,
44
+ [ATTR_EXCEPTION_MESSAGE]: exception.message
45
+ };
46
+ if (exception.stack) attrs[ATTR_EXCEPTION_STACKTRACE] = exception.stack;
47
+ return attrs;
48
+ }
49
+ /**
50
+ * Patches `console.{debug,log,info,warn,error}` to additionally emit OTel
51
+ * `LogRecord`s alongside the original output. Every log record carries
52
+ * the active span's context (so trace ↔ log correlation works in
53
+ * dashboards) and any `Error` arg gets unpacked into semconv exception
54
+ * attrs.
55
+ *
56
+ * Returns a disposer that restores the original `console.*` methods.
57
+ *
58
+ * Cycle protection: `emitting` guards against the bridge re-entering
59
+ * itself if a logger backend itself logs to console (which would
60
+ * recursively emit forever). Failures are bounded-rate logged via the
61
+ * original `console.error` so a broken backend doesn't drown the
62
+ * customer's logs in bridge-failure messages.
63
+ */
64
+ function bridgeConsoleToOtel() {
65
+ const logger = logs.getLogger("@interfere/next/console");
66
+ let emitting = false;
67
+ let bridgeFailures = 0;
68
+ const original = {
69
+ debug: console.debug,
70
+ log: console.log,
71
+ info: console.info,
72
+ warn: console.warn,
73
+ error: console.error
74
+ };
75
+ function emitLog(orig, severity, method, body, activeContext, activeSpan, attributes) {
76
+ if (emitting) return;
77
+ emitting = true;
78
+ try {
79
+ logger.emit({
80
+ severityNumber: severity,
81
+ severityText: method.toUpperCase(),
82
+ body,
83
+ ...activeSpan ? { context: activeContext } : {},
84
+ ...attributes ? { attributes } : {}
85
+ });
86
+ } catch (error) {
87
+ bridgeFailures++;
88
+ if (bridgeFailures % BRIDGE_FAILURE_LOG_INTERVAL === 1) orig.call(console, `[interfere] console→OTel bridge emission failed (${bridgeFailures} total):`, error);
89
+ } finally {
90
+ emitting = false;
91
+ }
92
+ }
93
+ for (const method of CONSOLE_METHODS) {
94
+ const orig = original[method];
95
+ const severity = SEVERITY[method];
96
+ console[method] = (...args) => {
97
+ orig.apply(console, args);
98
+ const body = args.map(stringify).join(" ");
99
+ const activeContext = context.active();
100
+ const activeSpan = trace.getSpan(activeContext);
101
+ const attributes = buildExceptionAttributes(args);
102
+ queueMicrotask(() => {
103
+ emitLog(orig, severity, method, body, activeContext, activeSpan, attributes);
104
+ });
105
+ };
106
+ }
107
+ return () => {
108
+ for (const method of CONSOLE_METHODS) console[method] = original[method];
109
+ };
110
+ }
111
+ //#endregion
112
+ export { bridgeConsoleToOtel };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-bridge.mjs","names":[],"sources":["../../../src/internal/server/console-bridge.ts"],"sourcesContent":["import { context, trace } from \"@opentelemetry/api\";\nimport { logs, type SeverityNumber } from \"@opentelemetry/api-logs\";\n\nconst ATTR_EXCEPTION_TYPE = \"exception.type\" as const;\nconst ATTR_EXCEPTION_MESSAGE = \"exception.message\" as const;\nconst ATTR_EXCEPTION_STACKTRACE = \"exception.stacktrace\" as const;\n\nconst CONSOLE_METHODS = [\"debug\", \"log\", \"info\", \"warn\", \"error\"] as const;\ntype ConsoleMethod = (typeof CONSOLE_METHODS)[number];\n\nconst SEVERITY: Record<ConsoleMethod, SeverityNumber> = {\n debug: 5,\n log: 9,\n info: 9,\n warn: 13,\n error: 17,\n};\n\n/**\n * Bound at first failure log; subsequent failures only re-log every Nth\n * occurrence so a misconfigured logger backend doesn't itself spam the\n * console with bridge errors at line rate.\n */\nconst BRIDGE_FAILURE_LOG_INTERVAL = 100;\n\nfunction stringify(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n if (value instanceof Error) {\n return value.stack ?? `${value.name}: ${value.message}`;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\nfunction extractException(args: unknown[]): Error | undefined {\n for (const arg of args) {\n if (arg instanceof Error) {\n return arg;\n }\n }\n return;\n}\n\nfunction buildExceptionAttributes(\n args: unknown[]\n): Record<string, string> | undefined {\n const exception = extractException(args);\n if (!exception) {\n return;\n }\n const attrs: Record<string, string> = {\n [ATTR_EXCEPTION_TYPE]: exception.name,\n [ATTR_EXCEPTION_MESSAGE]: exception.message,\n };\n if (exception.stack) {\n attrs[ATTR_EXCEPTION_STACKTRACE] = exception.stack;\n }\n return attrs;\n}\n\n/**\n * Patches `console.{debug,log,info,warn,error}` to additionally emit OTel\n * `LogRecord`s alongside the original output. Every log record carries\n * the active span's context (so trace ↔ log correlation works in\n * dashboards) and any `Error` arg gets unpacked into semconv exception\n * attrs.\n *\n * Returns a disposer that restores the original `console.*` methods.\n *\n * Cycle protection: `emitting` guards against the bridge re-entering\n * itself if a logger backend itself logs to console (which would\n * recursively emit forever). Failures are bounded-rate logged via the\n * original `console.error` so a broken backend doesn't drown the\n * customer's logs in bridge-failure messages.\n */\nexport function bridgeConsoleToOtel(): () => void {\n const logger = logs.getLogger(\"@interfere/next/console\");\n let emitting = false;\n let bridgeFailures = 0;\n\n const original: Record<ConsoleMethod, (...args: unknown[]) => void> = {\n debug: console.debug,\n log: console.log,\n info: console.info,\n warn: console.warn,\n error: console.error,\n };\n\n function emitLog(\n orig: (...args: unknown[]) => void,\n severity: SeverityNumber,\n method: ConsoleMethod,\n body: string,\n activeContext: ReturnType<typeof context.active>,\n activeSpan: ReturnType<typeof trace.getSpan>,\n attributes: Record<string, string> | undefined\n ): void {\n if (emitting) {\n return;\n }\n emitting = true;\n try {\n logger.emit({\n severityNumber: severity,\n severityText: method.toUpperCase(),\n body,\n ...(activeSpan ? { context: activeContext } : {}),\n ...(attributes ? { attributes } : {}),\n });\n } catch (error) {\n bridgeFailures++;\n if (bridgeFailures % BRIDGE_FAILURE_LOG_INTERVAL === 1) {\n orig.call(\n console,\n `[interfere] console→OTel bridge emission failed (${bridgeFailures} total):`,\n error\n );\n }\n } finally {\n emitting = false;\n }\n }\n\n for (const method of CONSOLE_METHODS) {\n const orig = original[method];\n const severity = SEVERITY[method];\n console[method] = (...args: unknown[]) => {\n orig.apply(console, args);\n\n const body = args.map(stringify).join(\" \");\n const activeContext = context.active();\n const activeSpan = trace.getSpan(activeContext);\n const attributes = buildExceptionAttributes(args);\n\n // Defer emission via microtask so the synchronous return path\n // out of `console.X` doesn't include the LoggerProvider's batch\n // logic. Customer code's `console.error(...)` call should feel\n // synchronous; the OTel emission rides the next microtask.\n // `queueMicrotask` (not `setTimeout(0)`): macrotask-deferred\n // emissions risk being lost on hard process exit.\n queueMicrotask(() => {\n emitLog(\n orig,\n severity,\n method,\n body,\n activeContext,\n activeSpan,\n attributes\n );\n });\n };\n }\n\n return () => {\n for (const method of CONSOLE_METHODS) {\n console[method] = original[method];\n }\n };\n}\n"],"mappings":";;;AAGA,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAC/B,MAAM,4BAA4B;AAElC,MAAM,kBAAkB;CAAC;CAAS;CAAO;CAAQ;CAAQ;CAAQ;AAGjE,MAAM,WAAkD;CACtD,OAAO;CACP,KAAK;CACL,MAAM;CACN,MAAM;CACN,OAAO;CACR;;;;;;AAOD,MAAM,8BAA8B;AAEpC,SAAS,UAAU,OAAwB;CACzC,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,IAAI,iBAAiB,OACnB,OAAO,MAAM,SAAS,GAAG,MAAM,KAAK,IAAI,MAAM;CAEhD,IAAI;EACF,OAAO,KAAK,UAAU,MAAM;SACtB;EACN,OAAO,OAAO,MAAM;;;AAIxB,SAAS,iBAAiB,MAAoC;CAC5D,KAAK,MAAM,OAAO,MAChB,IAAI,eAAe,OACjB,OAAO;;AAMb,SAAS,yBACP,MACoC;CACpC,MAAM,YAAY,iBAAiB,KAAK;CACxC,IAAI,CAAC,WACH;CAEF,MAAM,QAAgC;GACnC,sBAAsB,UAAU;GAChC,yBAAyB,UAAU;EACrC;CACD,IAAI,UAAU,OACZ,MAAM,6BAA6B,UAAU;CAE/C,OAAO;;;;;;;;;;;;;;;;;AAkBT,SAAgB,sBAAkC;CAChD,MAAM,SAAS,KAAK,UAAU,0BAA0B;CACxD,IAAI,WAAW;CACf,IAAI,iBAAiB;CAErB,MAAM,WAAgE;EACpE,OAAO,QAAQ;EACf,KAAK,QAAQ;EACb,MAAM,QAAQ;EACd,MAAM,QAAQ;EACd,OAAO,QAAQ;EAChB;CAED,SAAS,QACP,MACA,UACA,QACA,MACA,eACA,YACA,YACM;EACN,IAAI,UACF;EAEF,WAAW;EACX,IAAI;GACF,OAAO,KAAK;IACV,gBAAgB;IAChB,cAAc,OAAO,aAAa;IAClC;IACA,GAAI,aAAa,EAAE,SAAS,eAAe,GAAG,EAAE;IAChD,GAAI,aAAa,EAAE,YAAY,GAAG,EAAE;IACrC,CAAC;WACK,OAAO;GACd;GACA,IAAI,iBAAiB,gCAAgC,GACnD,KAAK,KACH,SACA,oDAAoD,eAAe,WACnE,MACD;YAEK;GACR,WAAW;;;CAIf,KAAK,MAAM,UAAU,iBAAiB;EACpC,MAAM,OAAO,SAAS;EACtB,MAAM,WAAW,SAAS;EAC1B,QAAQ,WAAW,GAAG,SAAoB;GACxC,KAAK,MAAM,SAAS,KAAK;GAEzB,MAAM,OAAO,KAAK,IAAI,UAAU,CAAC,KAAK,IAAI;GAC1C,MAAM,gBAAgB,QAAQ,QAAQ;GACtC,MAAM,aAAa,MAAM,QAAQ,cAAc;GAC/C,MAAM,aAAa,yBAAyB,KAAK;GAQjD,qBAAqB;IACnB,QACE,MACA,UACA,QACA,MACA,eACA,YACA,WACD;KACD;;;CAIN,aAAa;EACX,KAAK,MAAM,UAAU,iBACnB,QAAQ,UAAU,SAAS"}
@@ -0,0 +1,38 @@
1
+ import { IdGenerator } from "@opentelemetry/sdk-trace-base";
2
+
3
+ //#region src/internal/server/id-generator.d.ts
4
+ /**
5
+ * IdGenerator hybrid that uses OTel's default `RandomIdGenerator` whenever
6
+ * available and falls back to a counter-based generator inside Next 16's
7
+ * prerender contexts.
8
+ *
9
+ * Why: Next 16's prerender extension throws synchronously on any
10
+ * `Math.random()` / `crypto.getRandomValues()` call inside a Server
11
+ * Component that hasn't first awaited Request data
12
+ * (`next-prerender-random` / `next-prerender-crypto`). OTel's default
13
+ * generator hits `Math.random()` on every span creation, so spans Next
14
+ * opens around prerendered route renders blow up the build.
15
+ *
16
+ * The hybrid path keeps full 128-bit trace IDs / 64-bit span IDs for the
17
+ * common case (SSR, runtime requests, fluid-compute invocations) and only
18
+ * degrades inside prerender — where the alternative is build failure.
19
+ *
20
+ * Inside prerender, IDs are minted from a per-process random prefix
21
+ * (seeded once at construction time, outside any prerender ALS frame) plus
22
+ * a monotonic counter. Counter widths are masked to keep IDs at their
23
+ * spec-mandated lengths even after wrap.
24
+ */
25
+ declare class PrerenderSafeIdGenerator implements IdGenerator {
26
+ private readonly random;
27
+ private readonly tracePrefix;
28
+ private readonly spanPrefix;
29
+ private traceCounter;
30
+ private spanCounter;
31
+ constructor();
32
+ generateTraceId(): string;
33
+ generateSpanId(): string;
34
+ private fallbackTraceId;
35
+ private fallbackSpanId;
36
+ }
37
+ //#endregion
38
+ export { PrerenderSafeIdGenerator };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-generator.d.mts","names":[],"sources":["../../../src/internal/server/id-generator.ts"],"mappings":";;;;;AAoCA;;;;;;;;;;;;;;;;;;;cAAa,wBAAA,YAAoC,WAAA;EAAA,iBAC9B,MAAA;EAAA,iBACA,WAAA;EAAA,iBACA,UAAA;EAAA,QACT,YAAA;EAAA,QACA,WAAA;;EAaR,eAAA,CAAA;EAQA,cAAA,CAAA;EAAA,QAQQ,eAAA;EAAA,QAQA,cAAA;AAAA"}
@@ -0,0 +1,68 @@
1
+ import { RandomIdGenerator } from "@opentelemetry/sdk-trace-base";
2
+ import { randomBytes } from "node:crypto";
3
+ //#region src/internal/server/id-generator.ts
4
+ const TRACE_ID_HEX_LEN = 32;
5
+ const SPAN_ID_HEX_LEN = 16;
6
+ const TRACE_PREFIX_HEX_LEN = 24;
7
+ const SPAN_PREFIX_HEX_LEN = 12;
8
+ const TRACE_COUNTER_HEX_LEN = TRACE_ID_HEX_LEN - TRACE_PREFIX_HEX_LEN;
9
+ const SPAN_COUNTER_HEX_LEN = SPAN_ID_HEX_LEN - SPAN_PREFIX_HEX_LEN;
10
+ const TRACE_COUNTER_MODULUS = 2 ** (TRACE_COUNTER_HEX_LEN * 4);
11
+ const SPAN_COUNTER_MODULUS = 2 ** (SPAN_COUNTER_HEX_LEN * 4);
12
+ /**
13
+ * IdGenerator hybrid that uses OTel's default `RandomIdGenerator` whenever
14
+ * available and falls back to a counter-based generator inside Next 16's
15
+ * prerender contexts.
16
+ *
17
+ * Why: Next 16's prerender extension throws synchronously on any
18
+ * `Math.random()` / `crypto.getRandomValues()` call inside a Server
19
+ * Component that hasn't first awaited Request data
20
+ * (`next-prerender-random` / `next-prerender-crypto`). OTel's default
21
+ * generator hits `Math.random()` on every span creation, so spans Next
22
+ * opens around prerendered route renders blow up the build.
23
+ *
24
+ * The hybrid path keeps full 128-bit trace IDs / 64-bit span IDs for the
25
+ * common case (SSR, runtime requests, fluid-compute invocations) and only
26
+ * degrades inside prerender — where the alternative is build failure.
27
+ *
28
+ * Inside prerender, IDs are minted from a per-process random prefix
29
+ * (seeded once at construction time, outside any prerender ALS frame) plus
30
+ * a monotonic counter. Counter widths are masked to keep IDs at their
31
+ * spec-mandated lengths even after wrap.
32
+ */
33
+ var PrerenderSafeIdGenerator = class {
34
+ random = new RandomIdGenerator();
35
+ tracePrefix;
36
+ spanPrefix;
37
+ traceCounter = 0;
38
+ spanCounter = 0;
39
+ constructor() {
40
+ const seed = randomBytes((TRACE_PREFIX_HEX_LEN + SPAN_PREFIX_HEX_LEN) / 2);
41
+ this.tracePrefix = seed.subarray(0, TRACE_PREFIX_HEX_LEN / 2).toString("hex");
42
+ this.spanPrefix = seed.subarray(TRACE_PREFIX_HEX_LEN / 2).toString("hex");
43
+ }
44
+ generateTraceId() {
45
+ try {
46
+ return this.random.generateTraceId();
47
+ } catch {
48
+ return this.fallbackTraceId();
49
+ }
50
+ }
51
+ generateSpanId() {
52
+ try {
53
+ return this.random.generateSpanId();
54
+ } catch {
55
+ return this.fallbackSpanId();
56
+ }
57
+ }
58
+ fallbackTraceId() {
59
+ this.traceCounter = (this.traceCounter + 1) % TRACE_COUNTER_MODULUS;
60
+ return this.tracePrefix + this.traceCounter.toString(16).padStart(TRACE_COUNTER_HEX_LEN, "0");
61
+ }
62
+ fallbackSpanId() {
63
+ this.spanCounter = (this.spanCounter + 1) % SPAN_COUNTER_MODULUS;
64
+ return this.spanPrefix + this.spanCounter.toString(16).padStart(SPAN_COUNTER_HEX_LEN, "0");
65
+ }
66
+ };
67
+ //#endregion
68
+ export { PrerenderSafeIdGenerator };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-generator.mjs","names":[],"sources":["../../../src/internal/server/id-generator.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport {\n type IdGenerator,\n RandomIdGenerator,\n} from \"@opentelemetry/sdk-trace-base\";\n\nconst TRACE_ID_HEX_LEN = 32;\nconst SPAN_ID_HEX_LEN = 16;\nconst TRACE_PREFIX_HEX_LEN = 24;\nconst SPAN_PREFIX_HEX_LEN = 12;\nconst TRACE_COUNTER_HEX_LEN = TRACE_ID_HEX_LEN - TRACE_PREFIX_HEX_LEN;\nconst SPAN_COUNTER_HEX_LEN = SPAN_ID_HEX_LEN - SPAN_PREFIX_HEX_LEN;\nconst TRACE_COUNTER_MODULUS = 2 ** (TRACE_COUNTER_HEX_LEN * 4);\nconst SPAN_COUNTER_MODULUS = 2 ** (SPAN_COUNTER_HEX_LEN * 4);\n\n/**\n * IdGenerator hybrid that uses OTel's default `RandomIdGenerator` whenever\n * available and falls back to a counter-based generator inside Next 16's\n * prerender contexts.\n *\n * Why: Next 16's prerender extension throws synchronously on any\n * `Math.random()` / `crypto.getRandomValues()` call inside a Server\n * Component that hasn't first awaited Request data\n * (`next-prerender-random` / `next-prerender-crypto`). OTel's default\n * generator hits `Math.random()` on every span creation, so spans Next\n * opens around prerendered route renders blow up the build.\n *\n * The hybrid path keeps full 128-bit trace IDs / 64-bit span IDs for the\n * common case (SSR, runtime requests, fluid-compute invocations) and only\n * degrades inside prerender — where the alternative is build failure.\n *\n * Inside prerender, IDs are minted from a per-process random prefix\n * (seeded once at construction time, outside any prerender ALS frame) plus\n * a monotonic counter. Counter widths are masked to keep IDs at their\n * spec-mandated lengths even after wrap.\n */\nexport class PrerenderSafeIdGenerator implements IdGenerator {\n private readonly random = new RandomIdGenerator();\n private readonly tracePrefix: string;\n private readonly spanPrefix: string;\n private traceCounter = 0;\n private spanCounter = 0;\n\n constructor() {\n // Seeded once at `register()` time — module init runs outside any\n // prerender ALS frame, so `randomBytes` is permitted here even if\n // every later call from inside a render is not.\n const seed = randomBytes((TRACE_PREFIX_HEX_LEN + SPAN_PREFIX_HEX_LEN) / 2);\n this.tracePrefix = seed\n .subarray(0, TRACE_PREFIX_HEX_LEN / 2)\n .toString(\"hex\");\n this.spanPrefix = seed.subarray(TRACE_PREFIX_HEX_LEN / 2).toString(\"hex\");\n }\n\n generateTraceId(): string {\n try {\n return this.random.generateTraceId();\n } catch {\n return this.fallbackTraceId();\n }\n }\n\n generateSpanId(): string {\n try {\n return this.random.generateSpanId();\n } catch {\n return this.fallbackSpanId();\n }\n }\n\n private fallbackTraceId(): string {\n this.traceCounter = (this.traceCounter + 1) % TRACE_COUNTER_MODULUS;\n return (\n this.tracePrefix +\n this.traceCounter.toString(16).padStart(TRACE_COUNTER_HEX_LEN, \"0\")\n );\n }\n\n private fallbackSpanId(): string {\n this.spanCounter = (this.spanCounter + 1) % SPAN_COUNTER_MODULUS;\n return (\n this.spanPrefix +\n this.spanCounter.toString(16).padStart(SPAN_COUNTER_HEX_LEN, \"0\")\n );\n }\n}\n"],"mappings":";;;AAMA,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB,mBAAmB;AACjD,MAAM,uBAAuB,kBAAkB;AAC/C,MAAM,wBAAwB,MAAM,wBAAwB;AAC5D,MAAM,uBAAuB,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;AAuB1D,IAAa,2BAAb,MAA6D;CAC3D,SAA0B,IAAI,mBAAmB;CACjD;CACA;CACA,eAAuB;CACvB,cAAsB;CAEtB,cAAc;EAIZ,MAAM,OAAO,aAAa,uBAAuB,uBAAuB,EAAE;EAC1E,KAAK,cAAc,KAChB,SAAS,GAAG,uBAAuB,EAAE,CACrC,SAAS,MAAM;EAClB,KAAK,aAAa,KAAK,SAAS,uBAAuB,EAAE,CAAC,SAAS,MAAM;;CAG3E,kBAA0B;EACxB,IAAI;GACF,OAAO,KAAK,OAAO,iBAAiB;UAC9B;GACN,OAAO,KAAK,iBAAiB;;;CAIjC,iBAAyB;EACvB,IAAI;GACF,OAAO,KAAK,OAAO,gBAAgB;UAC7B;GACN,OAAO,KAAK,gBAAgB;;;CAIhC,kBAAkC;EAChC,KAAK,gBAAgB,KAAK,eAAe,KAAK;EAC9C,OACE,KAAK,cACL,KAAK,aAAa,SAAS,GAAG,CAAC,SAAS,uBAAuB,IAAI;;CAIvE,iBAAiC;EAC/B,KAAK,eAAe,KAAK,cAAc,KAAK;EAC5C,OACE,KAAK,aACL,KAAK,YAAY,SAAS,GAAG,CAAC,SAAS,sBAAsB,IAAI"}
@@ -0,0 +1,86 @@
1
+ import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
2
+ import { MetricReader } from "@opentelemetry/sdk-metrics";
3
+ import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
4
+
5
+ //#region src/internal/server/instrumentation-options.d.ts
6
+ /**
7
+ * Per-instrumentation tuning for the server-side `register()`. Mirrors
8
+ * the browser-side `TracingOptions` — same names, same semantics —
9
+ * so a customer running both client + server with the same SDK has
10
+ * one mental model.
11
+ *
12
+ * The shape lives in its own module (rather than alongside
13
+ * `instrumentation.ts`) so the Node entry (`instrumentation.ts`) and the
14
+ * Edge no-op entry (`instrumentation.edge.ts`) can both pull the same
15
+ * type contract — the Edge entry never imports the Node-only OTel
16
+ * implementation, but its `register()` signature must stay byte-for-byte
17
+ * compatible so customer code compiles unchanged when bundled for either
18
+ * runtime.
19
+ *
20
+ * `LogRecordProcessor`, `MetricReader`, and `SpanProcessor` come from
21
+ * the OTel "base" packages (`@opentelemetry/sdk-logs`,
22
+ * `@opentelemetry/sdk-metrics`, `@opentelemetry/sdk-trace-base`),
23
+ * which are edge-safe at the import level. They're also `import type` here
24
+ * so they're erased before bundling — the Edge runtime bundle never sees
25
+ * them at all.
26
+ */
27
+ interface ServerInstrumentationOptions {
28
+ /**
29
+ * @internal
30
+ *
31
+ * Extra log-record processors fanned into the LoggerProvider's
32
+ * processor list. Used by `@interfere/observability` for internal-only
33
+ * dual-write to BetterStack from `interfere/homepage` +
34
+ * `interfere/dashboard`. Customers don't get a fan-out hook on the
35
+ * SDK surface; this is a private bridge for our own dogfood apps.
36
+ */
37
+ _internalAdditionalLogRecordProcessors?: LogRecordProcessor[];
38
+ /**
39
+ * @internal
40
+ *
41
+ * Extra metric readers fanned into the MeterProvider's reader list.
42
+ * See `_internalAdditionalLogRecordProcessors`. Used to keep
43
+ * `http.client.request.duration` (from `UndiciInstrumentation`) and
44
+ * any first-party `metrics.getMeter(...)` counters landing on the
45
+ * BetterStack-fronting OTel collector after the `@vercel/otel` →
46
+ * `@interfere/next` migration removed the default metric reader
47
+ * `registerOTel({ metricReader: "auto" })` used to install.
48
+ */
49
+ _internalAdditionalMetricReaders?: MetricReader[];
50
+ /**
51
+ * @internal
52
+ *
53
+ * Extra span processors fanned into the NodeTracerProvider's processor
54
+ * list. See `_internalAdditionalLogRecordProcessors`.
55
+ */
56
+ _internalAdditionalSpanProcessors?: SpanProcessor[];
57
+ /**
58
+ * `false` disables the `console.*` → OTel `LogRecord` bridge.
59
+ * Defaults to enabled. Customers running their own structured
60
+ * logger (Pino, Winston, etc.) that already emits OTel logs can
61
+ * opt out to avoid double-emit.
62
+ */
63
+ consoleBridge?: boolean;
64
+ /**
65
+ * URL patterns the undici fetch instrumentation skips (no spans
66
+ * created for these). The collector OTLP endpoint is always
67
+ * skipped automatically — without this the SDK would trace its
68
+ * own export requests in an infinite loop.
69
+ */
70
+ ignoreUrls?: (string | RegExp)[];
71
+ /**
72
+ * URL patterns the undici fetch instrumentation injects W3C
73
+ * `traceparent` + `baggage` headers on for cross-process trace
74
+ * correlation. Same-origin requests aren't a concept on the server
75
+ * the way they are in a browser, so the allowlist is the only
76
+ * propagation control.
77
+ */
78
+ propagateContextUrls?: (string | RegExp)[];
79
+ /**
80
+ * Override the OTel `service.name` resource attribute. Defaults to
81
+ * `"interfere-sdk-next-server"`.
82
+ */
83
+ serviceName?: string;
84
+ }
85
+ //#endregion
86
+ export { ServerInstrumentationOptions };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation-options.d.mts","names":[],"sources":["../../../src/internal/server/instrumentation-options.ts"],"mappings":";;;;;;;AAyBA;;;;;;;;;;;;;;;;;;;UAAiB,4BAAA;EAmDkB;;;;;;;;;EAzCjC,sCAAA,GAAyC,kBAAA;;;;;;;;;;;;EAYzC,gCAAA,GAAmC,YAAA;;;;;;;EAOnC,iCAAA,GAAoC,aAAA;;;;;;;EAOpC,aAAA;;;;;;;EAOA,UAAA,aAAuB,MAAA;;;;;;;;EAQvB,oBAAA,aAAiC,MAAA;;;;;EAKjC,WAAA;AAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,9 @@
1
- import { isEnabledInEnvironment, readInterfereEnv } from "../env.mjs";
1
+ import { isEnabledOnServer, readInterfereEnv } from "../env.mjs";
2
2
  import { API_PATHS } from "@interfere/constants/api";
3
3
  //#region src/internal/server/remote-config.ts
4
4
  let cachedConfig = null;
5
5
  async function fetchAndCacheRemoteConfig() {
6
- if (!isEnabledInEnvironment()) return;
6
+ if (!isEnabledOnServer()) return;
7
7
  const env = readInterfereEnv();
8
8
  if (env.apiKey === null) return;
9
9
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"remote-config.mjs","names":[],"sources":["../../../src/internal/server/remote-config.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport type {\n RemoteConfig,\n RemotePluginConfig,\n} from \"@interfere/types/sdk/remote-config\";\n\nimport { isEnabledInEnvironment, readInterfereEnv } from \"../env.js\";\n\nlet cachedConfig: RemotePluginConfig | null = null;\n\nexport async function fetchAndCacheRemoteConfig(): Promise<void> {\n if (!isEnabledInEnvironment()) {\n return;\n }\n\n const env = readInterfereEnv();\n if (env.apiKey === null) {\n return;\n }\n\n try {\n const url = `${env.apiUrl}${API_PATHS.CONFIG}`;\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-api-key\": env.apiKey,\n },\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!response.ok) {\n return;\n }\n\n const config = (await response.json()) as RemoteConfig;\n if (config?.plugins) {\n cachedConfig = config.plugins;\n }\n } catch {\n // Fail silently — all plugins remain enabled\n }\n}\n\nexport function isPluginEnabled(plugin: string): boolean {\n if (!cachedConfig) {\n return true;\n }\n return cachedConfig[plugin as keyof RemotePluginConfig] !== false;\n}\n"],"mappings":";;;AAQA,IAAI,eAA0C;AAE9C,eAAsB,4BAA2C;AAC/D,KAAI,CAAC,wBAAwB,CAC3B;CAGF,MAAM,MAAM,kBAAkB;AAC9B,KAAI,IAAI,WAAW,KACjB;AAGF,KAAI;EACF,MAAM,MAAM,GAAG,IAAI,SAAS,UAAU;EACtC,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,aAAa,IAAI;IAClB;GACD,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC;AAEF,MAAI,CAAC,SAAS,GACZ;EAGF,MAAM,SAAU,MAAM,SAAS,MAAM;AACrC,MAAI,QAAQ,QACV,gBAAe,OAAO;SAElB;;AAKV,SAAgB,gBAAgB,QAAyB;AACvD,KAAI,CAAC,aACH,QAAO;AAET,QAAO,aAAa,YAAwC"}
1
+ {"version":3,"file":"remote-config.mjs","names":[],"sources":["../../../src/internal/server/remote-config.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport type {\n RemoteConfig,\n RemotePluginConfig,\n} from \"@interfere/types/sdk/remote-config\";\n\nimport { isEnabledOnServer, readInterfereEnv } from \"../env.js\";\n\nlet cachedConfig: RemotePluginConfig | null = null;\n\nexport async function fetchAndCacheRemoteConfig(): Promise<void> {\n if (!isEnabledOnServer()) {\n return;\n }\n\n const env = readInterfereEnv();\n if (env.apiKey === null) {\n return;\n }\n\n try {\n const url = `${env.apiUrl}${API_PATHS.CONFIG}`;\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-api-key\": env.apiKey,\n },\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!response.ok) {\n return;\n }\n\n const config = (await response.json()) as RemoteConfig;\n if (config?.plugins) {\n cachedConfig = config.plugins;\n }\n } catch {\n // Fail silently — all plugins remain enabled\n }\n}\n\nexport function isPluginEnabled(plugin: string): boolean {\n if (!cachedConfig) {\n return true;\n }\n return cachedConfig[plugin as keyof RemotePluginConfig] !== false;\n}\n"],"mappings":";;;AAQA,IAAI,eAA0C;AAE9C,eAAsB,4BAA2C;CAC/D,IAAI,CAAC,mBAAmB,EACtB;CAGF,MAAM,MAAM,kBAAkB;CAC9B,IAAI,IAAI,WAAW,MACjB;CAGF,IAAI;EACF,MAAM,MAAM,GAAG,IAAI,SAAS,UAAU;EACtC,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,aAAa,IAAI;IAClB;GACD,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC;EAEF,IAAI,CAAC,SAAS,IACZ;EAGF,MAAM,SAAU,MAAM,SAAS,MAAM;EACrC,IAAI,QAAQ,SACV,eAAe,OAAO;SAElB;;AAKV,SAAgB,gBAAgB,QAAyB;CACvD,IAAI,CAAC,cACH,OAAO;CAET,OAAO,aAAa,YAAwC"}
@@ -0,0 +1,34 @@
1
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/internal/server/trace-meta.d.ts
4
+ /**
5
+ * Server Component that emits a `<meta name="traceparent">` tag so the
6
+ * client SDK can stitch every browser span onto the server-side trace.
7
+ *
8
+ * Customer usage — drop into the root layout's `<head>`:
9
+ *
10
+ * ```tsx
11
+ * import { TraceMeta } from "@interfere/next/server";
12
+ *
13
+ * export default function RootLayout({ children }) {
14
+ * return (
15
+ * <html>
16
+ * <head>
17
+ * <TraceMeta />
18
+ * </head>
19
+ * <body>{children}</body>
20
+ * </html>
21
+ * );
22
+ * }
23
+ * ```
24
+ *
25
+ * Renders nothing when no OTel span is active (dev without OTel
26
+ * registered, edge runtime where `@interfere/next/instrumentation`'s
27
+ * async `register()` hasn't completed before the layout renders, or
28
+ * unsampled traces). The client SDK's propagation reader handles a
29
+ * missing meta tag the same as no parent — every browser span starts a
30
+ * fresh root, which is at least internally consistent.
31
+ */
32
+ declare function TraceMeta(): _$react_jsx_runtime0.JSX.Element | null;
33
+ //#endregion
34
+ export { TraceMeta };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-meta.d.mts","names":[],"sources":["../../../src/internal/server/trace-meta.tsx"],"mappings":";;;;;;AA8BA;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAA,GAAS,oBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -0,0 +1,41 @@
1
+ import { activeTraceparent } from "./traceparent.mjs";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/internal/server/trace-meta.tsx
4
+ /**
5
+ * Server Component that emits a `<meta name="traceparent">` tag so the
6
+ * client SDK can stitch every browser span onto the server-side trace.
7
+ *
8
+ * Customer usage — drop into the root layout's `<head>`:
9
+ *
10
+ * ```tsx
11
+ * import { TraceMeta } from "@interfere/next/server";
12
+ *
13
+ * export default function RootLayout({ children }) {
14
+ * return (
15
+ * <html>
16
+ * <head>
17
+ * <TraceMeta />
18
+ * </head>
19
+ * <body>{children}</body>
20
+ * </html>
21
+ * );
22
+ * }
23
+ * ```
24
+ *
25
+ * Renders nothing when no OTel span is active (dev without OTel
26
+ * registered, edge runtime where `@interfere/next/instrumentation`'s
27
+ * async `register()` hasn't completed before the layout renders, or
28
+ * unsampled traces). The client SDK's propagation reader handles a
29
+ * missing meta tag the same as no parent — every browser span starts a
30
+ * fresh root, which is at least internally consistent.
31
+ */
32
+ function TraceMeta() {
33
+ const traceparent = activeTraceparent();
34
+ if (!traceparent) return null;
35
+ return /* @__PURE__ */ jsx("meta", {
36
+ content: traceparent,
37
+ name: "traceparent"
38
+ });
39
+ }
40
+ //#endregion
41
+ export { TraceMeta };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-meta.mjs","names":[],"sources":["../../../src/internal/server/trace-meta.tsx"],"sourcesContent":["import { activeTraceparent } from \"./traceparent.js\";\n\n/**\n * Server Component that emits a `<meta name=\"traceparent\">` tag so the\n * client SDK can stitch every browser span onto the server-side trace.\n *\n * Customer usage — drop into the root layout's `<head>`:\n *\n * ```tsx\n * import { TraceMeta } from \"@interfere/next/server\";\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <head>\n * <TraceMeta />\n * </head>\n * <body>{children}</body>\n * </html>\n * );\n * }\n * ```\n *\n * Renders nothing when no OTel span is active (dev without OTel\n * registered, edge runtime where `@interfere/next/instrumentation`'s\n * async `register()` hasn't completed before the layout renders, or\n * unsampled traces). The client SDK's propagation reader handles a\n * missing meta tag the same as no parent — every browser span starts a\n * fresh root, which is at least internally consistent.\n */\nexport function TraceMeta() {\n const traceparent = activeTraceparent();\n if (!traceparent) {\n return null;\n }\n return <meta content={traceparent} name=\"traceparent\" />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YAAY;CAC1B,MAAM,cAAc,mBAAmB;CACvC,IAAI,CAAC,aACH,OAAO;CAET,OAAO,oBAAC,QAAD;EAAM,SAAS;EAAa,MAAK;EAAgB,CAAA"}
@@ -0,0 +1,16 @@
1
+ //#region src/internal/server/traceparent.d.ts
2
+ /**
3
+ * Build a W3C `traceparent` string from the currently-active OTel
4
+ * span, or `null` if no span is active or the trace is not sampled.
5
+ *
6
+ * Server-side counterpart to the client SDK's
7
+ * `internal/otel/propagation.ts`: both ends of the wire filter
8
+ * unsampled traces. The browser doesn't honor the `sampled` bit when
9
+ * creating child spans, so propagating an unsampled trace_id from the
10
+ * server would orphan every browser span under a trace with no
11
+ * recorded server segments. Returning null lets the browser fall back
12
+ * to its own root, which is at least internally consistent.
13
+ */
14
+ declare function activeTraceparent(): string | null;
15
+ //#endregion
16
+ export { activeTraceparent };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traceparent.d.mts","names":[],"sources":["../../../src/internal/server/traceparent.ts"],"mappings":";;AAiBA;;;;;;;;;;;iBAAgB,iBAAA,CAAA"}
@@ -0,0 +1,26 @@
1
+ import { TraceFlags, trace } from "@opentelemetry/api";
2
+ //#region src/internal/server/traceparent.ts
3
+ const SAMPLED_BIT = TraceFlags.SAMPLED;
4
+ const W3C_TRACECONTEXT_VERSION = "00";
5
+ /**
6
+ * Build a W3C `traceparent` string from the currently-active OTel
7
+ * span, or `null` if no span is active or the trace is not sampled.
8
+ *
9
+ * Server-side counterpart to the client SDK's
10
+ * `internal/otel/propagation.ts`: both ends of the wire filter
11
+ * unsampled traces. The browser doesn't honor the `sampled` bit when
12
+ * creating child spans, so propagating an unsampled trace_id from the
13
+ * server would orphan every browser span under a trace with no
14
+ * recorded server segments. Returning null lets the browser fall back
15
+ * to its own root, which is at least internally consistent.
16
+ */
17
+ function activeTraceparent() {
18
+ const span = trace.getActiveSpan();
19
+ if (!span) return null;
20
+ const ctx = span.spanContext();
21
+ if ((ctx.traceFlags & SAMPLED_BIT) !== SAMPLED_BIT) return null;
22
+ const flags = ctx.traceFlags.toString(16).padStart(2, "0");
23
+ return `${W3C_TRACECONTEXT_VERSION}-${ctx.traceId}-${ctx.spanId}-${flags}`;
24
+ }
25
+ //#endregion
26
+ export { activeTraceparent };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traceparent.mjs","names":[],"sources":["../../../src/internal/server/traceparent.ts"],"sourcesContent":["import { TraceFlags, trace } from \"@opentelemetry/api\";\n\nconst SAMPLED_BIT = TraceFlags.SAMPLED;\nconst W3C_TRACECONTEXT_VERSION = \"00\";\n\n/**\n * Build a W3C `traceparent` string from the currently-active OTel\n * span, or `null` if no span is active or the trace is not sampled.\n *\n * Server-side counterpart to the client SDK's\n * `internal/otel/propagation.ts`: both ends of the wire filter\n * unsampled traces. The browser doesn't honor the `sampled` bit when\n * creating child spans, so propagating an unsampled trace_id from the\n * server would orphan every browser span under a trace with no\n * recorded server segments. Returning null lets the browser fall back\n * to its own root, which is at least internally consistent.\n */\nexport function activeTraceparent(): string | null {\n const span = trace.getActiveSpan();\n if (!span) {\n return null;\n }\n const ctx = span.spanContext();\n // biome-ignore lint/suspicious/noBitwiseOperators: W3C trace_flags is a bitmask; SAMPLED (0x01) must be tested independently of any future reserved flags.\n if ((ctx.traceFlags & SAMPLED_BIT) !== SAMPLED_BIT) {\n return null;\n }\n const flags = ctx.traceFlags.toString(16).padStart(2, \"0\");\n return `${W3C_TRACECONTEXT_VERSION}-${ctx.traceId}-${ctx.spanId}-${flags}`;\n}\n"],"mappings":";;AAEA,MAAM,cAAc,WAAW;AAC/B,MAAM,2BAA2B;;;;;;;;;;;;;AAcjC,SAAgB,oBAAmC;CACjD,MAAM,OAAO,MAAM,eAAe;CAClC,IAAI,CAAC,MACH,OAAO;CAET,MAAM,MAAM,KAAK,aAAa;CAE9B,KAAK,IAAI,aAAa,iBAAiB,aACrC,OAAO;CAET,MAAM,QAAQ,IAAI,WAAW,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;CAC1D,OAAO,GAAG,yBAAyB,GAAG,IAAI,QAAQ,GAAG,IAAI,OAAO,GAAG"}
@@ -6,12 +6,6 @@ type OnRequestErrorContext = Omit<NextjsContext, "errorDigest" | "requestMethod"
6
6
  interface CaptureErrorContext {
7
7
  readonly mechanism?: ErrorMechanism;
8
8
  readonly nextjs?: Omit<NextjsContext, "runtime">;
9
- readonly traceparent?: string;
10
- }
11
- interface NormalizedRequest {
12
- readonly headers: Headers;
13
- readonly method: string;
14
- readonly path: string;
15
9
  }
16
10
  //#endregion
17
- export { CaptureErrorContext, NormalizedRequest, OnRequestErrorContext };
11
+ export { CaptureErrorContext, OnRequestErrorContext };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/internal/server/types.ts"],"mappings":";;;;KAGY,qBAAA,GAAwB,IAAA,CAClC,aAAA;AAAA,UAIe,mBAAA;EAAA,SACN,SAAA,GAAY,cAAA;EAAA,SACZ,MAAA,GAAS,IAAA,CAAK,aAAA;EAAA,SACd,WAAA;AAAA;AAAA,UAGM,iBAAA;EAAA,SACN,OAAA,EAAS,OAAA;EAAA,SACT,MAAA;EAAA,SACA,IAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/internal/server/types.ts"],"mappings":";;;;KAGY,qBAAA,GAAwB,IAAA,CAClC,aAAA;AAAA,UAIe,mBAAA;EAAA,SACN,SAAA,GAAY,cAAA;EAAA,SACZ,MAAA,GAAS,IAAA,CAAK,aAAA;AAAA"}
@@ -0,0 +1,17 @@
1
+ //#region src/internal/setup-warnings.d.ts
2
+ /**
3
+ * Best-effort warning when the customer set `INTERFERE_API_KEY` but never
4
+ * created `instrumentation.ts`. Server-side tracing then silently does
5
+ * nothing — that's surprising and there's no other place we get to tell
6
+ * them. Never blocks the build: some customers genuinely don't want
7
+ * server-side tracing.
8
+ */
9
+ declare function warnIfServerInstrumentationMissing(projectDir: string): void;
10
+ /**
11
+ * Reads the customer's installed `next` package version. Returns `null` if
12
+ * Next isn't resolvable from `projectDir` (e.g. running outside a Next app),
13
+ * in which case callers should skip version-conditional behaviour.
14
+ */
15
+ declare function readNextMajorVersion(projectDir: string): number | null;
16
+ //#endregion
17
+ export { readNextMajorVersion, warnIfServerInstrumentationMissing };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-warnings.d.mts","names":[],"sources":["../../src/internal/setup-warnings.ts"],"mappings":";;AA8BA;;;;;AAeA;iBAfgB,kCAAA,CAAmC,UAAA;;;;;;iBAenC,oBAAA,CAAqB,UAAA"}
@@ -0,0 +1,45 @@
1
+ import { log } from "./logger.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { existsSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ //#region src/internal/setup-warnings.ts
6
+ const SERVER_INSTRUMENTATION_FILES = [
7
+ "instrumentation.ts",
8
+ "instrumentation.tsx",
9
+ "instrumentation.js",
10
+ "instrumentation.jsx",
11
+ "src/instrumentation.ts",
12
+ "src/instrumentation.tsx",
13
+ "src/instrumentation.js",
14
+ "src/instrumentation.jsx"
15
+ ];
16
+ function hasServerInstrumentation(projectDir) {
17
+ return SERVER_INSTRUMENTATION_FILES.some((c) => existsSync(resolve(projectDir, c)));
18
+ }
19
+ /**
20
+ * Best-effort warning when the customer set `INTERFERE_API_KEY` but never
21
+ * created `instrumentation.ts`. Server-side tracing then silently does
22
+ * nothing — that's surprising and there's no other place we get to tell
23
+ * them. Never blocks the build: some customers genuinely don't want
24
+ * server-side tracing.
25
+ */
26
+ function warnIfServerInstrumentationMissing(projectDir) {
27
+ if (hasServerInstrumentation(projectDir)) return;
28
+ log.warn("No instrumentation.ts found", ["Server-side traces will be skipped. To enable, create instrumentation.ts at the project root with:", " export { register } from '@interfere/next/instrumentation';"]);
29
+ }
30
+ /**
31
+ * Reads the customer's installed `next` package version. Returns `null` if
32
+ * Next isn't resolvable from `projectDir` (e.g. running outside a Next app),
33
+ * in which case callers should skip version-conditional behaviour.
34
+ */
35
+ function readNextMajorVersion(projectDir) {
36
+ try {
37
+ const pkg = createRequire(`${projectDir}/_`)("next/package.json");
38
+ const major = Number.parseInt(pkg.version?.split(".")[0] ?? "", 10);
39
+ return Number.isFinite(major) ? major : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ //#endregion
45
+ export { readNextMajorVersion, warnIfServerInstrumentationMissing };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-warnings.mjs","names":[],"sources":["../../src/internal/setup-warnings.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { resolve } from \"node:path\";\n\nimport { log } from \"./logger.js\";\n\nconst SERVER_INSTRUMENTATION_FILES = [\n \"instrumentation.ts\",\n \"instrumentation.tsx\",\n \"instrumentation.js\",\n \"instrumentation.jsx\",\n \"src/instrumentation.ts\",\n \"src/instrumentation.tsx\",\n \"src/instrumentation.js\",\n \"src/instrumentation.jsx\",\n] as const;\n\nfunction hasServerInstrumentation(projectDir: string): boolean {\n return SERVER_INSTRUMENTATION_FILES.some((c) =>\n existsSync(resolve(projectDir, c))\n );\n}\n\n/**\n * Best-effort warning when the customer set `INTERFERE_API_KEY` but never\n * created `instrumentation.ts`. Server-side tracing then silently does\n * nothing — that's surprising and there's no other place we get to tell\n * them. Never blocks the build: some customers genuinely don't want\n * server-side tracing.\n */\nexport function warnIfServerInstrumentationMissing(projectDir: string): void {\n if (hasServerInstrumentation(projectDir)) {\n return;\n }\n log.warn(\"No instrumentation.ts found\", [\n \"Server-side traces will be skipped. To enable, create instrumentation.ts at the project root with:\",\n \" export { register } from '@interfere/next/instrumentation';\",\n ]);\n}\n\n/**\n * Reads the customer's installed `next` package version. Returns `null` if\n * Next isn't resolvable from `projectDir` (e.g. running outside a Next app),\n * in which case callers should skip version-conditional behaviour.\n */\nexport function readNextMajorVersion(projectDir: string): number | null {\n try {\n const require = createRequire(`${projectDir}/_`);\n const pkg = require(\"next/package.json\") as { version?: string };\n const major = Number.parseInt(pkg.version?.split(\".\")[0] ?? \"\", 10);\n return Number.isFinite(major) ? major : null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;AAMA,MAAM,+BAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,yBAAyB,YAA6B;CAC7D,OAAO,6BAA6B,MAAM,MACxC,WAAW,QAAQ,YAAY,EAAE,CAAC,CACnC;;;;;;;;;AAUH,SAAgB,mCAAmC,YAA0B;CAC3E,IAAI,yBAAyB,WAAW,EACtC;CAEF,IAAI,KAAK,+BAA+B,CACtC,sGACA,gEACD,CAAC;;;;;;;AAQJ,SAAgB,qBAAqB,YAAmC;CACtE,IAAI;EAEF,MAAM,MADU,cAAc,GAAG,WAAW,IACzB,CAAC,oBAAoB;EACxC,MAAM,QAAQ,OAAO,SAAS,IAAI,SAAS,MAAM,IAAI,CAAC,MAAM,IAAI,GAAG;EACnE,OAAO,OAAO,SAAS,MAAM,GAAG,QAAQ;SAClC;EACN,OAAO"}
package/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
2
  var name = "@interfere/next";
3
- var version = "9.0.2";
3
+ var version = "10.0.0";
4
4
  //#endregion
5
5
  export { name, version };
@@ -1,3 +1,24 @@
1
- import { InterfereProvider, useInterfere, useSession } from "@interfere/react/provider";
2
- import { ConsentCategory, ConsentState, GateableCategory } from "@interfere/types/sdk/plugins/manifest";
1
+ import { useInterfere, useSession } from "@interfere/react/provider";
2
+ import { PropsWithChildren, ReactNode } from "react";
3
+ import { ConsentCategory, ConsentState, ConsentState as ConsentState$1, GateableCategory } from "@interfere/types/sdk/plugins/manifest";
4
+
5
+ //#region src/provider.d.ts
6
+ interface InterfereProviderProps extends PropsWithChildren {
7
+ consent?: ConsentState$1 | undefined;
8
+ errorBoundary?: boolean;
9
+ }
10
+ /**
11
+ * Next.js wrapper around `@interfere/react`'s provider that resolves the
12
+ * kernel from module scope (set by the matching `init()` call). Customer
13
+ * code never passes the kernel explicitly. Uses `getKernelOrNull()` so the
14
+ * provider is safe to render during SSR/SSG when `init()` hasn't run
15
+ * (`init()` is client-only); the core provider mounts with null-safe
16
+ * accessors and switches to the real kernel on client hydration.
17
+ */
18
+ declare function InterfereProvider({
19
+ children,
20
+ consent,
21
+ errorBoundary
22
+ }: InterfereProviderProps): ReactNode;
23
+ //#endregion
3
24
  export { type ConsentCategory, type ConsentState, type GateableCategory, InterfereProvider, useInterfere, useSession };