react-on-rails-pro-node-renderer 16.7.0-rc.2 → 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.
@@ -9,6 +9,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  return (mod && mod.__esModule) ? mod : { "default": mod };
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.sumUploadedBytes = sumUploadedBytes;
12
13
  exports.handleNewBundlesProvided = handleNewBundlesProvided;
13
14
  exports.handleRenderRequest = handleRenderRequest;
14
15
  const cluster_1 = __importDefault(require("cluster"));
@@ -19,38 +20,62 @@ const fileExistsAsync_js_1 = __importDefault(require("../shared/fileExistsAsync.
19
20
  const log_js_1 = __importDefault(require("../shared/log.js"));
20
21
  const utils_js_1 = require("../shared/utils.js");
21
22
  const configBuilder_js_1 = require("../shared/configBuilder.js");
23
+ const tracing_js_1 = require("../shared/tracing.js");
22
24
  const vm_js_1 = require("./vm.js");
23
- async function prepareResult(renderingRequest, bundleFilePathPerTimestamp, executionContext) {
24
- try {
25
- const result = await executionContext.runInVM(renderingRequest, bundleFilePathPerTimestamp, cluster_1.default);
26
- let exceptionMessage = null;
27
- if (!result) {
28
- const error = new Error('INVALID NIL or NULL result for rendering. Ensure renderingRequest is a valid string and returns a value.');
29
- exceptionMessage = (0, utils_js_1.formatExceptionMessage)({ renderingRequest }, error, 'INVALID result for prepareResult');
25
+ async function sumUploadedBytes(assets) {
26
+ const sizes = await Promise.all(assets.map(async (asset) => {
27
+ try {
28
+ return (await (0, promises_1.stat)(asset.savedFilePath)).size;
30
29
  }
31
- else if ((0, utils_js_1.isErrorRenderResult)(result)) {
32
- ({ exceptionMessage } = result);
30
+ catch {
31
+ // Best-effort: a missing source means we record what we can rather than
32
+ // failing the request just to get a span attribute.
33
+ return 0;
33
34
  }
34
- if (exceptionMessage) {
35
- return (0, utils_js_1.errorResponseResult)(exceptionMessage);
36
- }
37
- if ((0, utils_js_1.isReadableStream)(result)) {
35
+ }));
36
+ return sizes.reduce((total, size) => total + size, 0);
37
+ }
38
+ async function prepareResult(renderingRequest, bundleTimestamp, bundleFilePathPerTimestamp, executionContext) {
39
+ return (0, tracing_js_1.subSpan)({ name: 'ror.result.prepare' }, async (resultSpan) => {
40
+ try {
41
+ const result = await (0, tracing_js_1.subSpan)({
42
+ name: 'ror.vm.execute',
43
+ attributes: { 'bundle.timestamp': String(bundleTimestamp) },
44
+ }, (_controller) => executionContext.runInVM(renderingRequest, bundleFilePathPerTimestamp, cluster_1.default));
45
+ let exceptionMessage = null;
46
+ if (!result) {
47
+ const error = new Error('INVALID NIL or NULL result for rendering. Ensure renderingRequest is a valid string and returns a value.');
48
+ exceptionMessage = (0, utils_js_1.formatExceptionMessage)({ renderingRequest }, error, 'INVALID result for prepareResult');
49
+ }
50
+ else if ((0, utils_js_1.isErrorRenderResult)(result)) {
51
+ ({ exceptionMessage } = result);
52
+ }
53
+ if (exceptionMessage) {
54
+ return (0, utils_js_1.errorResponseResult)(exceptionMessage);
55
+ }
56
+ if ((0, utils_js_1.isReadableStream)(result)) {
57
+ // Stream length is unknown until consumed; omit response.bytes rather
58
+ // than buffer the stream to compute it.
59
+ return {
60
+ headers: { 'Cache-Control': 'public, max-age=31536000' },
61
+ status: 200,
62
+ stream: result,
63
+ };
64
+ }
65
+ if (typeof result === 'string') {
66
+ resultSpan.setAttributes({ 'response.bytes': Buffer.byteLength(result, 'utf8') });
67
+ }
38
68
  return {
39
69
  headers: { 'Cache-Control': 'public, max-age=31536000' },
40
70
  status: 200,
41
- stream: result,
71
+ data: result,
42
72
  };
43
73
  }
44
- return {
45
- headers: { 'Cache-Control': 'public, max-age=31536000' },
46
- status: 200,
47
- data: result,
48
- };
49
- }
50
- catch (err) {
51
- const exceptionMessage = (0, utils_js_1.formatExceptionMessage)({ renderingRequest }, err, 'Unknown error calling runInVM');
52
- return (0, utils_js_1.errorResponseResult)(exceptionMessage);
53
- }
74
+ catch (err) {
75
+ const exceptionMessage = (0, utils_js_1.formatExceptionMessage)({ renderingRequest }, err, 'Unknown error calling runInVM');
76
+ return (0, utils_js_1.errorResponseResult)(exceptionMessage);
77
+ }
78
+ });
54
79
  }
55
80
  /**
56
81
  * @param bundleFilePathPerTimestamp
@@ -150,9 +175,16 @@ async function handleRenderRequest({ renderingRequest, bundleTimestamp, dependen
150
175
  };
151
176
  }
152
177
  try {
153
- const executionContext = await (0, vm_js_1.buildExecutionContext)(allBundleFilePaths, /* buildVmsIfNeeded */ false);
178
+ const executionContext = await (0, tracing_js_1.subSpan)({
179
+ name: 'ror.bundle.build_execution_context',
180
+ attributes: {
181
+ 'bundle.timestamp': String(bundleTimestamp),
182
+ 'bundle.paths.count': allBundleFilePaths.length,
183
+ 'cache.strategy': 'cache-first',
184
+ },
185
+ }, () => (0, vm_js_1.buildExecutionContext)(allBundleFilePaths, /* buildVmsIfNeeded */ false));
154
186
  return {
155
- response: await prepareResult(renderingRequest, entryBundleFilePath, executionContext),
187
+ response: await prepareResult(renderingRequest, bundleTimestamp, entryBundleFilePath, executionContext),
156
188
  executionContext,
157
189
  };
158
190
  }
@@ -165,7 +197,21 @@ async function handleRenderRequest({ renderingRequest, bundleTimestamp, dependen
165
197
  }
166
198
  // If gem has posted updated bundle:
167
199
  if (providedNewBundles && providedNewBundles.length > 0) {
168
- const result = await handleNewBundlesProvided({ renderingRequest }, providedNewBundles, assetsToCopy);
200
+ // Stat upload sources before they're moved by handleNewBundlesProvided.
201
+ // bytes.total reflects source file sizes at upload time, not compressed
202
+ // wire bytes.
203
+ const bytesTotal = await sumUploadedBytes([
204
+ ...providedNewBundles.map((b) => b.bundle),
205
+ ...(assetsToCopy ?? []),
206
+ ]);
207
+ const result = await (0, tracing_js_1.subSpan)({
208
+ name: 'ror.bundle.upload',
209
+ attributes: {
210
+ 'bundle.count': providedNewBundles.length,
211
+ 'assets.count': assetsToCopy?.length ?? 0,
212
+ 'bytes.total': bytesTotal,
213
+ },
214
+ }, () => handleNewBundlesProvided({ renderingRequest }, providedNewBundles, assetsToCopy));
169
215
  if (result) {
170
216
  return { response: result };
171
217
  }
@@ -178,9 +224,16 @@ async function handleRenderRequest({ renderingRequest, bundleTimestamp, dependen
178
224
  // The bundle exists, but the VM has not yet been created.
179
225
  // Another worker must have written it or it was saved during deployment.
180
226
  log_js_1.default.info('Bundle %s exists. Building ExecutionContext for worker %s.', entryBundleFilePath, (0, utils_js_1.workerIdLabel)());
181
- const executionContext = await (0, vm_js_1.buildExecutionContext)(allBundleFilePaths, /* buildVmsIfNeeded */ true);
227
+ const executionContext = await (0, tracing_js_1.subSpan)({
228
+ name: 'ror.bundle.build_execution_context',
229
+ attributes: {
230
+ 'bundle.timestamp': String(bundleTimestamp),
231
+ 'bundle.paths.count': allBundleFilePaths.length,
232
+ 'cache.strategy': 'cache-miss',
233
+ },
234
+ }, () => (0, vm_js_1.buildExecutionContext)(allBundleFilePaths, /* buildVmsIfNeeded */ true));
182
235
  return {
183
- response: await prepareResult(renderingRequest, entryBundleFilePath, executionContext),
236
+ response: await prepareResult(renderingRequest, bundleTimestamp, entryBundleFilePath, executionContext),
184
237
  executionContext,
185
238
  };
186
239
  }
@@ -0,0 +1,12 @@
1
+ export type WorkerShutdownHook = () => void | Promise<void>;
2
+ /**
3
+ * Upper bound on how long the worker waits for all registered shutdown hooks
4
+ * to settle before forcibly destroying the worker. Integration shutdown
5
+ * timeouts (e.g. OpenTelemetry `shutdownTimeoutMs`) must stay below this so
6
+ * the worker doesn't kill an in-flight hook before it finishes flushing.
7
+ */
8
+ export declare const WORKER_SHUTDOWN_HOOKS_TIMEOUT_MS = 10000;
9
+ export declare function registerWorkerShutdownHook(hook: WorkerShutdownHook): () => void;
10
+ export declare function runWorkerShutdownHooks(): Promise<void>;
11
+ export declare function __resetWorkerShutdownHooksForTest(): void;
12
+ //# sourceMappingURL=shutdownHooks.d.ts.map
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WORKER_SHUTDOWN_HOOKS_TIMEOUT_MS = void 0;
4
+ exports.registerWorkerShutdownHook = registerWorkerShutdownHook;
5
+ exports.runWorkerShutdownHooks = runWorkerShutdownHooks;
6
+ exports.__resetWorkerShutdownHooksForTest = __resetWorkerShutdownHooksForTest;
7
+ /**
8
+ * Upper bound on how long the worker waits for all registered shutdown hooks
9
+ * to settle before forcibly destroying the worker. Integration shutdown
10
+ * timeouts (e.g. OpenTelemetry `shutdownTimeoutMs`) must stay below this so
11
+ * the worker doesn't kill an in-flight hook before it finishes flushing.
12
+ */
13
+ exports.WORKER_SHUTDOWN_HOOKS_TIMEOUT_MS = 10000;
14
+ const workerShutdownHooks = [];
15
+ function registerWorkerShutdownHook(hook) {
16
+ workerShutdownHooks.push(hook);
17
+ return () => {
18
+ const index = workerShutdownHooks.indexOf(hook);
19
+ if (index >= 0) {
20
+ workerShutdownHooks.splice(index, 1);
21
+ }
22
+ };
23
+ }
24
+ async function runWorkerShutdownHooks() {
25
+ const results = await Promise.allSettled(workerShutdownHooks.map(async (hook) => {
26
+ await hook();
27
+ }));
28
+ const rejectedResults = results.filter((result) => result.status === 'rejected');
29
+ const firstRejectedResult = rejectedResults[0];
30
+ if (rejectedResults.length === 1 && firstRejectedResult) {
31
+ throw firstRejectedResult.reason;
32
+ }
33
+ if (rejectedResults.length > 1) {
34
+ throw new AggregateError(rejectedResults.map((result) => result.reason), 'Multiple worker shutdown hooks failed');
35
+ }
36
+ }
37
+ // eslint-disable-next-line no-underscore-dangle
38
+ function __resetWorkerShutdownHooksForTest() {
39
+ workerShutdownHooks.length = 0;
40
+ }
41
+ //# sourceMappingURL=shutdownHooks.js.map
package/lib/worker.d.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  * @module worker
4
4
  */
5
5
  import { Config } from './shared/configBuilder.js';
6
- import type { FastifyInstance } from './worker/types.js';
7
6
  import { Asset } from './shared/utils.js';
7
+ export { configureFastify, type FastifyConfigFunction } from './worker/fastifyConfig.js';
8
8
  declare module '@fastify/multipart' {
9
9
  interface MultipartFile {
10
10
  value: Asset;
@@ -15,14 +15,6 @@ declare module 'fastify' {
15
15
  uploadDir: string;
16
16
  }
17
17
  }
18
- export type FastifyConfigFunction = (app: FastifyInstance) => void;
19
- /**
20
- * Configures Fastify instance before starting the server.
21
- * @param configFunction The configuring function. Normally it will be something like `(app) => { app.register(...); }`
22
- * or `(app) => { app.addHook(...); }` to report data from Fastify to an external service.
23
- * Note that we call `await app.ready()` in our code, so you don't need to `await` the results.
24
- */
25
- export declare function configureFastify(configFunction: FastifyConfigFunction): void;
26
18
  export declare const disableHttp2: () => void;
27
19
  export default function run(config: Partial<Config>): import("fastify").FastifyInstance<import("http2").Http2Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse, typeof import("http2").Http2ServerRequest, typeof import("http2").Http2ServerResponse>, import("http2").Http2ServerRequest, import("http2").Http2ServerResponse<import("http2").Http2ServerRequest>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault> & PromiseLike<import("fastify").FastifyInstance<import("http2").Http2Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse, typeof import("http2").Http2ServerRequest, typeof import("http2").Http2ServerResponse>, import("http2").Http2ServerRequest, import("http2").Http2ServerResponse<import("http2").Http2ServerRequest>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>> & {
28
20
  __linterBrands: "SafePromiseLike";
package/lib/worker.js CHANGED
@@ -40,8 +40,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
40
40
  return (mod && mod.__esModule) ? mod : { "default": mod };
41
41
  };
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
- exports.disableHttp2 = void 0;
44
- exports.configureFastify = configureFastify;
43
+ exports.disableHttp2 = exports.configureFastify = void 0;
45
44
  exports.default = run;
46
45
  const path_1 = __importDefault(require("path"));
47
46
  const cluster_1 = __importDefault(require("cluster"));
@@ -64,16 +63,9 @@ const handleIncrementalRenderStream_js_1 = require("./worker/handleIncrementalRe
64
63
  const constants_js_1 = require("./shared/constants.js");
65
64
  const utils_js_1 = require("./shared/utils.js");
66
65
  const tracing_js_1 = require("./shared/tracing.js");
67
- const fastifyConfigFunctions = [];
68
- /**
69
- * Configures Fastify instance before starting the server.
70
- * @param configFunction The configuring function. Normally it will be something like `(app) => { app.register(...); }`
71
- * or `(app) => { app.addHook(...); }` to report data from Fastify to an external service.
72
- * Note that we call `await app.ready()` in our code, so you don't need to `await` the results.
73
- */
74
- function configureFastify(configFunction) {
75
- fastifyConfigFunctions.push(configFunction);
76
- }
66
+ const fastifyConfig_js_1 = require("./worker/fastifyConfig.js");
67
+ var fastifyConfig_js_2 = require("./worker/fastifyConfig.js");
68
+ Object.defineProperty(exports, "configureFastify", { enumerable: true, get: function () { return fastifyConfig_js_2.configureFastify; } });
77
69
  function setHeaders(headers, res) {
78
70
  // eslint-disable-next-line @typescript-eslint/no-misused-promises -- fixing it with `void` just violates no-void
79
71
  Object.entries(headers).forEach(([key, header]) => res.header(key, header));
@@ -346,68 +338,72 @@ function run(config) {
346
338
  // Track whether we've already started sending a response (streaming or otherwise)
347
339
  // If true, we can't send an error response on failure - headers are already sent
348
340
  let responseStarted = false;
341
+ let incrementalTracingContext;
349
342
  try {
350
- // Handle the incremental render stream
351
- await (0, handleIncrementalRenderStream_js_1.handleIncrementalRenderStream)({
352
- request: req,
353
- onRenderRequestReceived: async (obj) => {
354
- // Build a temporary FastifyRequest shape for protocol/auth check
355
- const tempReqBody = typeof obj === 'object' && obj !== null ? obj : {};
356
- // Perform request prechecks
357
- const precheckResult = (0, requestPrechecks_js_1.performRequestPrechecks)(tempReqBody);
358
- if (precheckResult) {
359
- return {
360
- response: precheckResult,
361
- shouldContinue: false,
362
- };
363
- }
364
- // Extract data for incremental render request
365
- const dependencyBundleTimestamps = extractBodyArrayField(tempReqBody, 'dependencyBundleTimestamps');
366
- const initial = {
367
- firstRequestChunk: obj,
368
- bundleTimestamp,
369
- dependencyBundleTimestamps,
370
- };
371
- try {
372
- const { response, sink } = await (0, handleIncrementalRenderRequest_js_1.handleIncrementalRenderRequest)(initial);
373
- incrementalSink = sink;
374
- return {
375
- response,
376
- shouldContinue: !!incrementalSink,
377
- };
378
- }
379
- catch (err) {
380
- const errorResponse = (0, utils_js_1.errorResponseResult)((0, utils_js_1.formatExceptionMessage)({ label: 'IncrementalRender', content: '' }, err, 'Error while handling incremental render request'));
381
- return {
382
- response: errorResponse,
383
- shouldContinue: false,
343
+ await (0, tracing_js_1.trace)(async (context) => {
344
+ incrementalTracingContext = context;
345
+ // Handle the incremental render stream
346
+ await (0, handleIncrementalRenderStream_js_1.handleIncrementalRenderStream)({
347
+ request: req,
348
+ onRenderRequestReceived: async (obj) => {
349
+ // Build a temporary FastifyRequest shape for protocol/auth check
350
+ const tempReqBody = typeof obj === 'object' && obj !== null ? obj : {};
351
+ // Perform request prechecks
352
+ const precheckResult = (0, requestPrechecks_js_1.performRequestPrechecks)(tempReqBody);
353
+ if (precheckResult) {
354
+ return {
355
+ response: precheckResult,
356
+ shouldContinue: false,
357
+ };
358
+ }
359
+ // Extract data for incremental render request
360
+ const dependencyBundleTimestamps = extractBodyArrayField(tempReqBody, 'dependencyBundleTimestamps');
361
+ const initial = {
362
+ firstRequestChunk: obj,
363
+ bundleTimestamp,
364
+ dependencyBundleTimestamps,
384
365
  };
385
- }
386
- },
387
- onUpdateReceived: async (obj) => {
388
- if (!incrementalSink) {
389
- log_js_1.default.error({ msg: 'Unexpected update chunk received after rendering was aborted', obj });
390
- return;
391
- }
392
- try {
393
- await incrementalSink.add(obj);
394
- }
395
- catch (err) {
396
- // Log error but don't stop processing
397
- log_js_1.default.error({ err, msg: 'Error processing update chunk' });
398
- }
399
- },
400
- onResponseStart: async (response) => {
401
- responseStarted = true;
402
- await setResponse(response, res);
403
- },
404
- onRequestEnded: () => {
405
- if (!incrementalSink) {
406
- return;
407
- }
408
- incrementalSink.handleRequestClosed();
409
- },
410
- });
366
+ try {
367
+ const { response, sink } = await (0, handleIncrementalRenderRequest_js_1.handleIncrementalRenderRequest)(initial);
368
+ incrementalSink = sink;
369
+ return {
370
+ response,
371
+ shouldContinue: !!incrementalSink,
372
+ };
373
+ }
374
+ catch (err) {
375
+ const errorResponse = (0, utils_js_1.errorResponseResult)((0, utils_js_1.formatExceptionMessage)({ label: 'IncrementalRender', content: '' }, err, 'Error while handling incremental render request'), context);
376
+ return {
377
+ response: errorResponse,
378
+ shouldContinue: false,
379
+ };
380
+ }
381
+ },
382
+ onUpdateReceived: async (obj) => {
383
+ if (!incrementalSink) {
384
+ log_js_1.default.error({ msg: 'Unexpected update chunk received after rendering was aborted', obj });
385
+ return;
386
+ }
387
+ try {
388
+ await incrementalSink.add(obj);
389
+ }
390
+ catch (err) {
391
+ // Log error but don't stop processing
392
+ log_js_1.default.error({ err, msg: 'Error processing update chunk' });
393
+ }
394
+ },
395
+ onResponseStart: async (response) => {
396
+ responseStarted = true;
397
+ await setResponse(response, res);
398
+ },
399
+ onRequestEnded: () => {
400
+ if (!incrementalSink) {
401
+ return;
402
+ }
403
+ incrementalSink.handleRequestClosed();
404
+ },
405
+ });
406
+ }, (0, tracing_js_1.startSsrRequestOptions)({ renderingRequest: 'ReactOnRails.incrementalRender' }));
411
407
  }
412
408
  catch (err) {
413
409
  // If an error occurred during stream processing, send error response
@@ -437,7 +433,7 @@ function run(config) {
437
433
  }
438
434
  else {
439
435
  // Response hasn't started yet, we can send an error response
440
- const errorResponse = (0, utils_js_1.errorResponseResult)(errorMessage);
436
+ const errorResponse = (0, utils_js_1.errorResponseResult)(errorMessage, incrementalTracingContext);
441
437
  await setResponse(errorResponse, res);
442
438
  }
443
439
  }
@@ -469,7 +465,18 @@ function run(config) {
469
465
  // endpoint so that concurrent /upload-assets and render requests
470
466
  // targeting the same bundle directory are mutually exclusive.
471
467
  // See https://github.com/shakacode/react_on_rails/issues/2463
472
- const result = await (0, handleRenderRequest_js_1.handleNewBundlesProvided)({ label: 'Request:', content: taskDescription }, providedNewBundles, assetsToCopy);
468
+ const bytesTotal = await (0, handleRenderRequest_js_1.sumUploadedBytes)([
469
+ ...providedNewBundles.map((b) => b.bundle),
470
+ ...(assetsToCopy ?? []),
471
+ ]);
472
+ const result = await (0, tracing_js_1.subSpan)({
473
+ name: 'ror.bundle.upload',
474
+ attributes: {
475
+ 'bundle.count': providedNewBundles.length,
476
+ 'assets.count': assetsToCopy.length,
477
+ 'bytes.total': bytesTotal,
478
+ },
479
+ }, () => (0, handleRenderRequest_js_1.handleNewBundlesProvided)({ label: 'Request:', content: taskDescription }, providedNewBundles, assetsToCopy));
473
480
  if (result) {
474
481
  await setResponse(result, res);
475
482
  return;
@@ -546,9 +553,9 @@ function run(config) {
546
553
  log_js_1.default.info({ workerName, address }, 'Node renderer listening');
547
554
  });
548
555
  }
549
- fastifyConfigFunctions.forEach((configFunction) => {
550
- configFunction(app);
551
- });
556
+ // Integration hooks registered before the worker loads are applied here, immediately after
557
+ // listen() is scheduled and before Fastify finishes booting.
558
+ (0, fastifyConfig_js_1.applyFastifyConfigFunctions)(app);
552
559
  return app;
553
560
  }
554
561
  //# sourceMappingURL=worker.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-on-rails-pro-node-renderer",
3
- "version": "16.7.0-rc.2",
3
+ "version": "16.7.0-rc.3",
4
4
  "protocolVersion": "2.0.0",
5
5
  "description": "React on Rails Pro Node Renderer for server-side rendering",
6
6
  "main": "lib/ReactOnRailsProNodeRenderer.js",
@@ -38,7 +38,7 @@
38
38
  "fs-extra": "^11.2.0",
39
39
  "jsonwebtoken": "^9.0.3",
40
40
  "lockfile": "^1.0.4",
41
- "pino": "^9.0.0"
41
+ "pino": "^9.14.0 || ^10.1.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@babel/core": "^7.26.10",
@@ -47,6 +47,15 @@
47
47
  "@babel/preset-react": "^7.26.3",
48
48
  "@babel/preset-typescript": "^7.27.1",
49
49
  "@honeybadger-io/js": "^6.10.1",
50
+ "@opentelemetry/api": "^1.9.0",
51
+ "@opentelemetry/exporter-trace-otlp-http": "^0.214.0",
52
+ "@fastify/otel": "^0.18.1",
53
+ "@opentelemetry/instrumentation": "^0.214.0",
54
+ "@opentelemetry/instrumentation-http": "^0.214.0",
55
+ "@opentelemetry/resources": "^2.0.1",
56
+ "@opentelemetry/sdk-trace-base": "^2.0.1",
57
+ "@opentelemetry/sdk-trace-node": "^2.0.1",
58
+ "@opentelemetry/semantic-conventions": "^1.36.0",
50
59
  "@sentry/node": "^7.120.0",
51
60
  "@types/fs-extra": "^11.0.4",
52
61
  "@types/jest": "^29.5.12",
@@ -65,10 +74,19 @@
65
74
  "sentry-testkit": "^5.0.6",
66
75
  "touch": "^3.1.0",
67
76
  "typescript": "^5.4.3",
68
- "react-on-rails": "16.7.0-rc.2"
77
+ "react-on-rails": "16.7.0-rc.3"
69
78
  },
70
79
  "peerDependencies": {
71
80
  "@honeybadger-io/js": ">=4.0.0",
81
+ "@opentelemetry/api": ">=1.9.0 <2.0.0",
82
+ "@opentelemetry/exporter-trace-otlp-http": ">=0.214.0 <1.0.0",
83
+ "@fastify/otel": ">=0.18.0 <1.0.0",
84
+ "@opentelemetry/instrumentation": ">=0.214.0 <1.0.0",
85
+ "@opentelemetry/instrumentation-http": ">=0.214.0 <1.0.0",
86
+ "@opentelemetry/resources": ">=2.0.0 <3.0.0",
87
+ "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0",
88
+ "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0",
89
+ "@opentelemetry/semantic-conventions": ">=1.36.0 <2.0.0",
72
90
  "@sentry/node": ">=5.0.0 <11.0.0",
73
91
  "@sentry/tracing": ">=5.0.0"
74
92
  },
@@ -76,6 +94,33 @@
76
94
  "@honeybadger-io/js": {
77
95
  "optional": true
78
96
  },
97
+ "@opentelemetry/api": {
98
+ "optional": true
99
+ },
100
+ "@opentelemetry/exporter-trace-otlp-http": {
101
+ "optional": true
102
+ },
103
+ "@fastify/otel": {
104
+ "optional": true
105
+ },
106
+ "@opentelemetry/instrumentation": {
107
+ "optional": true
108
+ },
109
+ "@opentelemetry/instrumentation-http": {
110
+ "optional": true
111
+ },
112
+ "@opentelemetry/resources": {
113
+ "optional": true
114
+ },
115
+ "@opentelemetry/sdk-trace-base": {
116
+ "optional": true
117
+ },
118
+ "@opentelemetry/sdk-trace-node": {
119
+ "optional": true
120
+ },
121
+ "@opentelemetry/semantic-conventions": {
122
+ "optional": true
123
+ },
79
124
  "@sentry/node": {
80
125
  "optional": true
81
126
  },