react-on-rails-pro-node-renderer 16.7.0-rc.1 → 16.7.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,6 @@
24
24
  */
25
25
  export { default as log } from '../shared/log.js';
26
26
  export { addErrorNotifier, addMessageNotifier, addNotifier, error, message, Notifier, ErrorNotifier, MessageNotifier, } from '../shared/errorReporter.js';
27
- export { setupTracing, TracingContext, TracingIntegrationOptions, UnitOfWorkOptions, } from '../shared/tracing.js';
28
- export { configureFastify, FastifyConfigFunction } from '../worker.js';
27
+ export { setupTracing, setupSubSpan, subSpan, TracingContext, TracingIntegrationOptions, UnitOfWorkOptions, SubSpanOptions, SubSpanFn, SubSpanController, } from '../shared/tracing.js';
28
+ export { configureFastify, FastifyConfigFunction } from '../worker/fastifyConfig.js';
29
29
  //# sourceMappingURL=api.d.ts.map
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
28
  };
29
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.configureFastify = exports.setupTracing = exports.message = exports.error = exports.addNotifier = exports.addMessageNotifier = exports.addErrorNotifier = exports.log = void 0;
30
+ exports.configureFastify = exports.subSpan = exports.setupSubSpan = exports.setupTracing = exports.message = exports.error = exports.addNotifier = exports.addMessageNotifier = exports.addErrorNotifier = exports.log = void 0;
31
31
  var log_js_1 = require("../shared/log.js");
32
32
  Object.defineProperty(exports, "log", { enumerable: true, get: function () { return __importDefault(log_js_1).default; } });
33
33
  var errorReporter_js_1 = require("../shared/errorReporter.js");
@@ -38,6 +38,8 @@ Object.defineProperty(exports, "error", { enumerable: true, get: function () { r
38
38
  Object.defineProperty(exports, "message", { enumerable: true, get: function () { return errorReporter_js_1.message; } });
39
39
  var tracing_js_1 = require("../shared/tracing.js");
40
40
  Object.defineProperty(exports, "setupTracing", { enumerable: true, get: function () { return tracing_js_1.setupTracing; } });
41
- var worker_js_1 = require("../worker.js");
42
- Object.defineProperty(exports, "configureFastify", { enumerable: true, get: function () { return worker_js_1.configureFastify; } });
41
+ Object.defineProperty(exports, "setupSubSpan", { enumerable: true, get: function () { return tracing_js_1.setupSubSpan; } });
42
+ Object.defineProperty(exports, "subSpan", { enumerable: true, get: function () { return tracing_js_1.subSpan; } });
43
+ var fastifyConfig_js_1 = require("../worker/fastifyConfig.js");
44
+ Object.defineProperty(exports, "configureFastify", { enumerable: true, get: function () { return fastifyConfig_js_1.configureFastify; } });
43
45
  //# sourceMappingURL=api.js.map
@@ -0,0 +1,34 @@
1
+ import type { Attributes } from '@opentelemetry/api';
2
+ import type { SpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base';
3
+ declare module '../shared/tracing.js' {
4
+ interface UnitOfWorkOptions {
5
+ opentelemetry?: {
6
+ name: string;
7
+ attributes?: Attributes;
8
+ };
9
+ }
10
+ }
11
+ export interface OpenTelemetryInitOptions {
12
+ /** Service name reported in traces. Defaults to "react-on-rails-pro-node-renderer".
13
+ * `OTEL_SERVICE_NAME` env var takes precedence over this value. If neither is
14
+ * set, `resourceAttributes["service.name"]` or `OTEL_RESOURCE_ATTRIBUTES`
15
+ * can override the default service name. */
16
+ serviceName?: string;
17
+ /** Register HTTP + Fastify auto-instrumentation. Default: false.
18
+ * OpenTelemetry module patches are process-global and cannot be rolled back;
19
+ * if later init steps fail, patched modules remain installed with a no-op tracer. */
20
+ fastify?: boolean;
21
+ /** Wrap SSR work in spans via setupTracing + setupSubSpan. Default: false. */
22
+ tracing?: boolean;
23
+ /** Override the default OTLP HTTP exporter. */
24
+ exporter?: SpanExporter;
25
+ /** Override the default span processor.
26
+ * Default: BatchSpanProcessor in production, SimpleSpanProcessor otherwise. */
27
+ spanProcessor?: SpanProcessor;
28
+ /** Additional resource attributes merged into the default resource. */
29
+ resourceAttributes?: Record<string, string>;
30
+ /** Maximum time to wait for provider.shutdown() during Fastify onClose. Default: 5000ms. */
31
+ shutdownTimeoutMs?: number;
32
+ }
33
+ export declare function init(opts?: OpenTelemetryInitOptions): void;
34
+ //# sourceMappingURL=opentelemetry.d.ts.map
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.init = init;
4
+ /* eslint-disable no-restricted-imports --
5
+ * This integration needs internal worker/shared modules that the public
6
+ * api.ts does not yet re-export (tracing adapter slots, OTel global state,
7
+ * fastify config + worker shutdown hook registration). Tracked in #3419
8
+ * — once api.ts surfaces these, remove this disable. */
9
+ const tracing_js_1 = require("../shared/tracing.js");
10
+ const opentelemetryState_js_1 = require("../shared/opentelemetryState.js");
11
+ const fastifyConfig_js_1 = require("../worker/fastifyConfig.js");
12
+ const shutdownHooks_js_1 = require("../worker/shutdownHooks.js");
13
+ /* eslint-enable no-restricted-imports */
14
+ const api_js_1 = require("./api.js");
15
+ const DEFAULT_SERVICE_NAME = 'react-on-rails-pro-node-renderer';
16
+ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 5000;
17
+ // Leave 1s of headroom under the worker's hard cap so the shutdown hook can
18
+ // resolve cleanly even when provider.shutdown() runs right at its limit.
19
+ const MAX_SHUTDOWN_TIMEOUT_MS = shutdownHooks_js_1.WORKER_SHUTDOWN_HOOKS_TIMEOUT_MS - 1000;
20
+ function isProduction() {
21
+ return process.env.NODE_ENV === 'production' || process.env.RAILS_ENV === 'production';
22
+ }
23
+ function resolveConfiguredServiceName(opts) {
24
+ return process.env.OTEL_SERVICE_NAME ?? opts.serviceName;
25
+ }
26
+ function resolveShutdownTimeoutMs(opts) {
27
+ const requested = opts.shutdownTimeoutMs;
28
+ if (requested === undefined) {
29
+ return DEFAULT_SHUTDOWN_TIMEOUT_MS;
30
+ }
31
+ if (!Number.isFinite(requested) || requested <= 0) {
32
+ return DEFAULT_SHUTDOWN_TIMEOUT_MS;
33
+ }
34
+ if (requested > MAX_SHUTDOWN_TIMEOUT_MS) {
35
+ api_js_1.log.warn('[OpenTelemetry] shutdownTimeoutMs=%dms exceeds worker shutdown hook cap (%dms); capping to %dms so the hook can resolve before the worker is forcibly destroyed.', requested, shutdownHooks_js_1.WORKER_SHUTDOWN_HOOKS_TIMEOUT_MS, MAX_SHUTDOWN_TIMEOUT_MS);
36
+ return MAX_SHUTDOWN_TIMEOUT_MS;
37
+ }
38
+ return requested;
39
+ }
40
+ function parseResourceAttributes(value) {
41
+ if (!value)
42
+ return {};
43
+ // OTel resource attributes are comma-separated. Literal commas in values must
44
+ // be percent-encoded by callers; unencoded commas split the value.
45
+ const attributes = {};
46
+ for (const pair of value.split(',')) {
47
+ const [rawKey, ...rawValueParts] = pair.split('=');
48
+ const key = rawKey?.trim();
49
+ if (key && rawValueParts.length > 0) {
50
+ const rawValue = rawValueParts.join('=').trim().replace(/^"|"$/g, '');
51
+ try {
52
+ attributes[key] = decodeURIComponent(rawValue);
53
+ }
54
+ catch {
55
+ // Keep init resilient when callers provide malformed percent-encoding.
56
+ attributes[key] = rawValue;
57
+ }
58
+ }
59
+ }
60
+ return attributes;
61
+ }
62
+ function configureOpenTelemetryDiagnostics(otelApi) {
63
+ otelApi.diag.setLogger({
64
+ error: (diagnosticMessage, ...args) => api_js_1.log.error({ otel: true, level: 'error', args }, diagnosticMessage),
65
+ warn: (diagnosticMessage, ...args) => api_js_1.log.warn({ otel: true, level: 'warn', args }, diagnosticMessage),
66
+ // DiagLogLevel.WARN below suppresses lower-severity diagnostics before
67
+ // these callbacks run. Keep no-op methods to satisfy the OTel logger API.
68
+ info: () => undefined,
69
+ debug: () => undefined,
70
+ verbose: () => undefined,
71
+ }, otelApi.DiagLogLevel.WARN);
72
+ }
73
+ function disableOpenTelemetryGlobals(otelApi) {
74
+ otelApi.trace.disable();
75
+ otelApi.context.disable();
76
+ otelApi.propagation.disable();
77
+ otelApi.diag.disable();
78
+ }
79
+ function resetInstalledTracingAdapters(installedAdapters) {
80
+ if (installedAdapters.subSpan) {
81
+ (0, tracing_js_1.resetSubSpan)();
82
+ }
83
+ if (installedAdapters.tracing) {
84
+ (0, tracing_js_1.resetTracing)();
85
+ }
86
+ return { tracing: false, subSpan: false };
87
+ }
88
+ async function shutdownProviderWithTimeout(provider, shutdownTimeoutMs) {
89
+ let timeoutId;
90
+ let timedOut = false;
91
+ const shutdownPromise = provider.shutdown();
92
+ const observedShutdownPromise = shutdownPromise.catch((error) => {
93
+ if (!timedOut) {
94
+ api_js_1.log.warn({ error }, '[OpenTelemetry] provider.shutdown() failed');
95
+ }
96
+ });
97
+ try {
98
+ await Promise.race([
99
+ observedShutdownPromise,
100
+ new Promise((resolve) => {
101
+ timeoutId = setTimeout(() => {
102
+ timedOut = true;
103
+ // shutdownPromise rejection (if any) is handled by observedShutdownPromise above.
104
+ void shutdownPromise.catch(() => undefined);
105
+ api_js_1.log.warn('[OpenTelemetry] provider.shutdown() timed out after %dms; continuing worker shutdown', shutdownTimeoutMs);
106
+ resolve();
107
+ }, shutdownTimeoutMs);
108
+ }),
109
+ ]);
110
+ }
111
+ finally {
112
+ if (timeoutId) {
113
+ clearTimeout(timeoutId);
114
+ }
115
+ }
116
+ }
117
+ function init(opts = {}) {
118
+ if ((0, opentelemetryState_js_1.getOpenTelemetryTracerProvider)()) {
119
+ (0, api_js_1.message)('[OpenTelemetry] init() called more than once; ignoring duplicate call.');
120
+ return;
121
+ }
122
+ let installedAdapters = { tracing: false, subSpan: false };
123
+ let otelApi;
124
+ let registeredProvider;
125
+ let unregisterFastifyConfig;
126
+ let unregisterWorkerShutdownHook;
127
+ let ownsOpenTelemetryGlobals = false;
128
+ try {
129
+ /* eslint-disable @typescript-eslint/no-require-imports, global-require --
130
+ * Lazy require so init() can no-op when peer deps are missing instead of
131
+ * crashing at module load time. */
132
+ const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
133
+ const { BatchSpanProcessor, SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
134
+ const { resourceFromAttributes } = require('@opentelemetry/resources');
135
+ const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
136
+ otelApi = require('@opentelemetry/api');
137
+ const loadedOtelApi = otelApi;
138
+ const resourceAttributes = {
139
+ ...parseResourceAttributes(process.env.OTEL_RESOURCE_ATTRIBUTES),
140
+ ...(opts.resourceAttributes ?? {}),
141
+ };
142
+ const configuredServiceName = resolveConfiguredServiceName(opts);
143
+ const serviceName = configuredServiceName ?? resourceAttributes[ATTR_SERVICE_NAME] ?? DEFAULT_SERVICE_NAME;
144
+ const shutdownTimeoutMs = resolveShutdownTimeoutMs(opts);
145
+ const resource = resourceFromAttributes({
146
+ [ATTR_SERVICE_NAME]: serviceName,
147
+ ...resourceAttributes,
148
+ ...(configuredServiceName ? { [ATTR_SERVICE_NAME]: configuredServiceName } : {}),
149
+ });
150
+ const defaultExporter = () => {
151
+ const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
152
+ return new OTLPTraceExporter();
153
+ };
154
+ /* eslint-enable @typescript-eslint/no-require-imports, global-require */
155
+ const spanProcessor = opts.spanProcessor ??
156
+ (() => {
157
+ const exporter = opts.exporter ?? defaultExporter();
158
+ return isProduction() ? new BatchSpanProcessor(exporter) : new SimpleSpanProcessor(exporter);
159
+ })();
160
+ const provider = new NodeTracerProvider({
161
+ resource,
162
+ spanProcessors: [spanProcessor],
163
+ });
164
+ // Take ownership of the global tracer provider BEFORE installing module
165
+ // patches via registerInstrumentations(). provider.register() calls
166
+ // trace.setGlobalTracerProvider() internally, which silently fails (returns
167
+ // false but does not throw) when another OpenTelemetry SDK already owns the
168
+ // global proxy's delegate. Call setGlobalTracerProvider() directly first so
169
+ // we can detect that silent failure and bail before patching modules.
170
+ const acquiredTracerGlobal = loadedOtelApi.trace.setGlobalTracerProvider(provider);
171
+ if (!acquiredTracerGlobal) {
172
+ installedAdapters = resetInstalledTracingAdapters(installedAdapters);
173
+ void provider.shutdown().catch(() => undefined);
174
+ (0, api_js_1.message)('[OpenTelemetry] init: another OpenTelemetry tracer provider is already registered globally; aborting.');
175
+ return;
176
+ }
177
+ // Mark ownership BEFORE provider.register() so the outer catch's cleanup
178
+ // (which keys off ownsOpenTelemetryGlobals + the module-local provider
179
+ // reference) correctly disables the globals if register() throws.
180
+ ownsOpenTelemetryGlobals = true;
181
+ registeredProvider = provider;
182
+ (0, opentelemetryState_js_1.setOpenTelemetryTracerProvider)(provider);
183
+ // Re-call provider.register() to set context manager + propagator globals.
184
+ // The second setGlobalTracerProvider() call inside register() is a no-op
185
+ // (registerGlobal returns false because the proxy is already owned by us),
186
+ // but the propagator + context manager setup still runs.
187
+ provider.register();
188
+ configureOpenTelemetryDiagnostics(loadedOtelApi);
189
+ if (opts.fastify) {
190
+ /* eslint-disable @typescript-eslint/no-require-imports, global-require */
191
+ const { registerInstrumentations } = require('@opentelemetry/instrumentation');
192
+ const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
193
+ // @fastify/otel uses `export = exported` so the require() returns the namespace
194
+ // object; the constructor lives on `.FastifyOtelInstrumentation` (also as `.default`).
195
+ const { FastifyOtelInstrumentation } = require('@fastify/otel');
196
+ /* eslint-enable @typescript-eslint/no-require-imports, global-require */
197
+ registerInstrumentations({
198
+ instrumentations: [
199
+ // HTTP first — Fastify instrumentation depends on it.
200
+ new HttpInstrumentation(),
201
+ new FastifyOtelInstrumentation({ registerOnInitialization: true }),
202
+ ],
203
+ tracerProvider: provider,
204
+ });
205
+ }
206
+ if (opts.tracing) {
207
+ const tracer = loadedOtelApi.trace.getTracer(serviceName);
208
+ installedAdapters.tracing = (0, tracing_js_1.setupTracing)({
209
+ startSsrRequestOptions: () => ({
210
+ // Keep the root span free of request payload data. Future safe
211
+ // attributes should be derived from structured metadata supplied by
212
+ // Ruby, not parsed out of the executable renderingRequest string.
213
+ opentelemetry: { name: 'ror.ssr.request' },
214
+ }),
215
+ executor: async (fn, unitOfWorkOptions) => {
216
+ const otelOpts = unitOfWorkOptions.opentelemetry ?? { name: 'ror.ssr.request' };
217
+ return tracer.startActiveSpan(otelOpts.name, { attributes: otelOpts.attributes }, async (span) => {
218
+ try {
219
+ return await fn();
220
+ }
221
+ catch (err) {
222
+ span.setStatus({
223
+ code: loadedOtelApi.SpanStatusCode.ERROR,
224
+ message: err instanceof Error ? err.message : String(err),
225
+ });
226
+ throw err;
227
+ }
228
+ finally {
229
+ span.end();
230
+ }
231
+ });
232
+ },
233
+ });
234
+ if (installedAdapters.tracing) {
235
+ const subSpanImpl = (subOpts, fn) => tracer.startActiveSpan(subOpts.name, { attributes: subOpts.attributes }, async (span) => {
236
+ const controller = {
237
+ setAttributes(attributes) {
238
+ span.setAttributes(attributes);
239
+ },
240
+ };
241
+ try {
242
+ return await fn(controller);
243
+ }
244
+ catch (err) {
245
+ span.setStatus({
246
+ code: loadedOtelApi.SpanStatusCode.ERROR,
247
+ message: err instanceof Error ? err.message : String(err),
248
+ });
249
+ throw err;
250
+ }
251
+ finally {
252
+ span.end();
253
+ }
254
+ });
255
+ installedAdapters.subSpan = (0, tracing_js_1.setupSubSpan)(subSpanImpl);
256
+ }
257
+ else {
258
+ (0, api_js_1.message)('[OpenTelemetry] tracing integration was not installed because another tracing integration is ' +
259
+ 'active; skipping OpenTelemetry sub-spans.');
260
+ }
261
+ }
262
+ let shutdownOpenTelemetryPromise;
263
+ const shutdownOpenTelemetry = () => {
264
+ shutdownOpenTelemetryPromise ?? (shutdownOpenTelemetryPromise = (async () => {
265
+ try {
266
+ await shutdownProviderWithTimeout(provider, shutdownTimeoutMs);
267
+ if ((0, opentelemetryState_js_1.getOpenTelemetryTracerProvider)() === provider) {
268
+ (0, opentelemetryState_js_1.setOpenTelemetryTracerProvider)(null);
269
+ disableOpenTelemetryGlobals(loadedOtelApi);
270
+ ownsOpenTelemetryGlobals = false;
271
+ installedAdapters = resetInstalledTracingAdapters(installedAdapters);
272
+ }
273
+ }
274
+ finally {
275
+ unregisterFastifyConfig?.();
276
+ unregisterWorkerShutdownHook?.();
277
+ }
278
+ })());
279
+ return shutdownOpenTelemetryPromise;
280
+ };
281
+ // Register these last so failed init paths do not leave partial shutdown hooks
282
+ // behind. The worker hook runs during cluster restarts, while Fastify onClose
283
+ // still handles explicit app.close() calls from tests or custom integrations.
284
+ unregisterWorkerShutdownHook = (0, shutdownHooks_js_1.registerWorkerShutdownHook)(shutdownOpenTelemetry);
285
+ unregisterFastifyConfig = (0, fastifyConfig_js_1.registerFastifyConfigFunction)((app) => {
286
+ app.addHook('onClose', shutdownOpenTelemetry);
287
+ });
288
+ api_js_1.log.info('[OpenTelemetry] Tracer provider initialized');
289
+ }
290
+ catch (err) {
291
+ unregisterFastifyConfig?.();
292
+ unregisterWorkerShutdownHook?.();
293
+ if (ownsOpenTelemetryGlobals &&
294
+ registeredProvider &&
295
+ otelApi &&
296
+ (0, opentelemetryState_js_1.getOpenTelemetryTracerProvider)() === registeredProvider) {
297
+ (0, opentelemetryState_js_1.setOpenTelemetryTracerProvider)(null);
298
+ disableOpenTelemetryGlobals(otelApi);
299
+ ownsOpenTelemetryGlobals = false;
300
+ }
301
+ installedAdapters = resetInstalledTracingAdapters(installedAdapters);
302
+ (0, api_js_1.message)(`[OpenTelemetry] init failed: ${String(err)}`);
303
+ }
304
+ }
305
+ //# sourceMappingURL=opentelemetry.js.map
@@ -0,0 +1,4 @@
1
+ import type { NodeTracerProvider as NodeTracerProviderType } from '@opentelemetry/sdk-trace-node';
2
+ export declare function getOpenTelemetryTracerProvider(): NodeTracerProviderType | null;
3
+ export declare function setOpenTelemetryTracerProvider(provider: NodeTracerProviderType | null): void;
4
+ //# sourceMappingURL=opentelemetryState.d.ts.map
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOpenTelemetryTracerProvider = getOpenTelemetryTracerProvider;
4
+ exports.setOpenTelemetryTracerProvider = setOpenTelemetryTracerProvider;
5
+ let tracerProvider = null;
6
+ function getOpenTelemetryTracerProvider() {
7
+ return tracerProvider;
8
+ }
9
+ function setOpenTelemetryTracerProvider(provider) {
10
+ tracerProvider = provider;
11
+ }
12
+ //# sourceMappingURL=opentelemetryState.js.map
@@ -43,11 +43,90 @@ export interface TracingIntegrationOptions {
43
43
  * @param options.startSsrRequestOptions - Options used to start a new unit of work for an SSR request.
44
44
  * Should be an object with your integration name as the only property.
45
45
  * It will be passed to the executor.
46
+ * @returns true when this call installed the tracing integration.
46
47
  */
47
- export declare function setupTracing(options: TracingIntegrationOptions): void;
48
+ export declare function setupTracing(options: TracingIntegrationOptions): boolean;
48
49
  /**
49
50
  * Reports a unit of work to the tracing service, if any.
50
51
  */
51
52
  export declare function trace<T>(fn: UnitOfWork<T>, unitOfWorkOptions: UnitOfWorkOptions): Promise<T>;
53
+ /**
54
+ * Resets the installed tracing executor + startSsrRequestOptions back to
55
+ * defaults. Internal integrations use this during lifecycle teardown.
56
+ */
57
+ export declare function resetTracing(): void;
58
+ /**
59
+ * Test-only: reset the installed tracing executor + startSsrRequestOptions back
60
+ * to defaults. Not part of the public api — do not re-export from
61
+ * `integrations/api.ts`.
62
+ */
63
+ export declare function __resetTracingForTest(): void;
64
+ /**
65
+ * Options passed to a sub-span wrapper.
66
+ *
67
+ * `name` is the span name (use dot.namespaced form, e.g. `ror.bundle.upload`).
68
+ * `attributes` are arbitrary key/value pairs attached to the span at creation.
69
+ */
70
+ export interface SubSpanOptions {
71
+ name: string;
72
+ attributes?: Record<string, string | number | boolean>;
73
+ }
74
+ /**
75
+ * Handed to the wrapped function so it can record attributes that are only
76
+ * known after the work runs (e.g., response byte counts). Implementations
77
+ * forward calls to the underlying span; the no-op default discards them.
78
+ *
79
+ * Only include byte counts, hashes, and counts here — never raw request or
80
+ * response payloads. Span attributes are not redacted by the renderer's
81
+ * logging policy.
82
+ */
83
+ export interface SubSpanController {
84
+ setAttributes(attributes: Record<string, string | number | boolean>): void;
85
+ }
86
+ /**
87
+ * Signature of a sub-span implementation installed via {@link setupSubSpan}.
88
+ * Must invoke `fn(controller)` and return its result. May wrap `fn` in a
89
+ * tracing span. The implementation supplies a controller that forwards
90
+ * `setAttributes` to the span; pass a no-op controller (e.g.
91
+ * `{ setAttributes() {} }`) when no span is being created.
92
+ *
93
+ * Implementations must either invoke `fn` synchronously or not invoke it at
94
+ * all before throwing/rejecting. Deferred invocation, such as scheduling `fn`
95
+ * with `setImmediate`, is unsupported because the fallback may run `fn`
96
+ * itself to preserve renderer behavior when an implementation fails before
97
+ * invocation.
98
+ */
99
+ export type SubSpanFn = <T>(opts: SubSpanOptions, fn: (controller: SubSpanController) => Promise<T>) => Promise<T>;
100
+ /**
101
+ * Install a sub-span implementation. Integrations call this from their `init()`
102
+ * to start receiving sub-span events. If never called, sub-spans are no-ops.
103
+ * @returns true when this call installed the sub-span integration.
104
+ */
105
+ export declare function setupSubSpan(impl: SubSpanFn): boolean;
106
+ /**
107
+ * Wrap an async function in a named sub-span. Safe to call even when no
108
+ * integration is installed — defaults to passing through to `fn`.
109
+ *
110
+ * The wrapped function receives a {@link SubSpanController} it can use to
111
+ * attach attributes that are only known after the work runs (e.g., response
112
+ * byte counts). With no integration installed, attribute updates are dropped.
113
+ *
114
+ * If the installed implementation throws or rejects before invoking `fn`, the
115
+ * caller is shielded: `fn` is still executed outside any sub-span (with the
116
+ * no-op controller) and its result returned. If the implementation fails
117
+ * after invoking `fn`, the error is rethrown so `fn` is never run twice.
118
+ */
119
+ export declare function subSpan<T>(opts: SubSpanOptions, fn: (controller: SubSpanController) => Promise<T>): Promise<T>;
120
+ /**
121
+ * Resets the installed sub-span implementation back to the default pass-through.
122
+ * Internal integrations use this during lifecycle teardown.
123
+ */
124
+ export declare function resetSubSpan(): void;
125
+ /**
126
+ * Test-only: reset the installed sub-span implementation back to the default
127
+ * pass-through. Not part of the public api — do not re-export from
128
+ * `integrations/api.ts`.
129
+ */
130
+ export declare function __resetSubSpanForTest(): void;
52
131
  export {};
53
132
  //# sourceMappingURL=tracing.d.ts.map
@@ -1,8 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.startSsrRequestOptions = void 0;
4
7
  exports.setupTracing = setupTracing;
5
8
  exports.trace = trace;
9
+ exports.resetTracing = resetTracing;
10
+ exports.__resetTracingForTest = __resetTracingForTest;
11
+ exports.setupSubSpan = setupSubSpan;
12
+ exports.subSpan = subSpan;
13
+ exports.resetSubSpan = resetSubSpan;
14
+ exports.__resetSubSpanForTest = __resetSubSpanForTest;
15
+ const log_js_1 = __importDefault(require("./log.js"));
6
16
  const errorReporter_js_1 = require("./errorReporter.js");
7
17
  /* eslint-enable @typescript-eslint/no-empty-object-type */
8
18
  let setupRun = false;
@@ -18,17 +28,19 @@ exports.startSsrRequestOptions = startSsrRequestOptions;
18
28
  * @param options.startSsrRequestOptions - Options used to start a new unit of work for an SSR request.
19
29
  * Should be an object with your integration name as the only property.
20
30
  * It will be passed to the executor.
31
+ * @returns true when this call installed the tracing integration.
21
32
  */
22
33
  function setupTracing(options) {
23
34
  if (setupRun) {
24
35
  (0, errorReporter_js_1.message)('setupTracing called more than once. Currently only one tracing integration can be enabled.');
25
- return;
36
+ return false;
26
37
  }
27
38
  executor = options.executor;
28
39
  if (options.startSsrRequestOptions) {
29
40
  mutableStartSsrRequestOptions = options.startSsrRequestOptions;
30
41
  }
31
42
  setupRun = true;
43
+ return true;
32
44
  }
33
45
  /**
34
46
  * Reports a unit of work to the tracing service, if any.
@@ -36,4 +48,100 @@ function setupTracing(options) {
36
48
  function trace(fn, unitOfWorkOptions) {
37
49
  return executor(fn, unitOfWorkOptions);
38
50
  }
51
+ /**
52
+ * Resets the installed tracing executor + startSsrRequestOptions back to
53
+ * defaults. Internal integrations use this during lifecycle teardown.
54
+ */
55
+ function resetTracing() {
56
+ executor = (fn) => fn();
57
+ mutableStartSsrRequestOptions = () => ({});
58
+ setupRun = false;
59
+ }
60
+ /**
61
+ * Test-only: reset the installed tracing executor + startSsrRequestOptions back
62
+ * to defaults. Not part of the public api — do not re-export from
63
+ * `integrations/api.ts`.
64
+ */
65
+ // eslint-disable-next-line no-underscore-dangle
66
+ function __resetTracingForTest() {
67
+ resetTracing();
68
+ }
69
+ const noOpSubSpanController = {
70
+ setAttributes() { },
71
+ };
72
+ const defaultSubSpan = (_opts, fn) => fn(noOpSubSpanController);
73
+ let subSpanImpl = defaultSubSpan;
74
+ let subSpanSetupRun = false;
75
+ /**
76
+ * Install a sub-span implementation. Integrations call this from their `init()`
77
+ * to start receiving sub-span events. If never called, sub-spans are no-ops.
78
+ * @returns true when this call installed the sub-span integration.
79
+ */
80
+ function setupSubSpan(impl) {
81
+ if (subSpanSetupRun) {
82
+ (0, errorReporter_js_1.message)('setupSubSpan called more than once. Only one sub-span integration can be enabled.');
83
+ return false;
84
+ }
85
+ subSpanImpl = impl;
86
+ subSpanSetupRun = true;
87
+ return true;
88
+ }
89
+ /**
90
+ * Wrap an async function in a named sub-span. Safe to call even when no
91
+ * integration is installed — defaults to passing through to `fn`.
92
+ *
93
+ * The wrapped function receives a {@link SubSpanController} it can use to
94
+ * attach attributes that are only known after the work runs (e.g., response
95
+ * byte counts). With no integration installed, attribute updates are dropped.
96
+ *
97
+ * If the installed implementation throws or rejects before invoking `fn`, the
98
+ * caller is shielded: `fn` is still executed outside any sub-span (with the
99
+ * no-op controller) and its result returned. If the implementation fails
100
+ * after invoking `fn`, the error is rethrown so `fn` is never run twice.
101
+ */
102
+ function subSpan(opts, fn) {
103
+ let invoked = false;
104
+ const wrappedFn = (controller) => {
105
+ invoked = true;
106
+ return fn(controller);
107
+ };
108
+ try {
109
+ return Promise.resolve(subSpanImpl(opts, wrappedFn)).catch((err) => {
110
+ if (invoked) {
111
+ return Promise.reject(err instanceof Error ? err : new Error(String(err)));
112
+ }
113
+ // log.warn (not message) for the silent-fallback path. message() goes to
114
+ // log.error + external notifiers (Sentry/Bugsnag/etc.) on every request,
115
+ // which is too noisy for a per-request recoverable failure where fn() is
116
+ // still executed. log.warn surfaces the broken integration without
117
+ // paging the on-call team for every render.
118
+ log_js_1.default.warn({ err }, 'subSpan implementation rejected before invoking fn(); running fn() without a span');
119
+ return wrappedFn(noOpSubSpanController);
120
+ });
121
+ }
122
+ catch (err) {
123
+ if (invoked) {
124
+ return Promise.reject(err instanceof Error ? err : new Error(String(err)));
125
+ }
126
+ log_js_1.default.warn({ err }, 'subSpan implementation threw before invoking fn(); running fn() without a span');
127
+ return wrappedFn(noOpSubSpanController);
128
+ }
129
+ }
130
+ /**
131
+ * Resets the installed sub-span implementation back to the default pass-through.
132
+ * Internal integrations use this during lifecycle teardown.
133
+ */
134
+ function resetSubSpan() {
135
+ subSpanImpl = defaultSubSpan;
136
+ subSpanSetupRun = false;
137
+ }
138
+ /**
139
+ * Test-only: reset the installed sub-span implementation back to the default
140
+ * pass-through. Not part of the public api — do not re-export from
141
+ * `integrations/api.ts`.
142
+ */
143
+ // eslint-disable-next-line no-underscore-dangle
144
+ function __resetSubSpanForTest() {
145
+ resetSubSpan();
146
+ }
39
147
  //# sourceMappingURL=tracing.js.map
@@ -0,0 +1,2 @@
1
+ export declare function resetOpenTelemetryForTest(): Promise<void>;
2
+ //# sourceMappingURL=opentelemetry.d.ts.map