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.
- package/lib/integrations/api.d.ts +2 -2
- package/lib/integrations/api.js +5 -3
- package/lib/integrations/opentelemetry.d.ts +34 -0
- package/lib/integrations/opentelemetry.js +305 -0
- package/lib/shared/opentelemetryState.d.ts +4 -0
- package/lib/shared/opentelemetryState.js +12 -0
- package/lib/shared/tracing.d.ts +80 -1
- package/lib/shared/tracing.js +109 -1
- package/lib/testUtils/opentelemetry.d.ts +2 -0
- package/lib/testUtils/opentelemetry.js +66 -0
- package/lib/worker/fastifyConfig.d.ts +19 -0
- package/lib/worker/fastifyConfig.js +41 -0
- package/lib/worker/handleGracefulShutdown.js +50 -3
- package/lib/worker/handleIncrementalRenderRequest.js +22 -5
- package/lib/worker/handleIncrementalRenderStream.js +104 -101
- package/lib/worker/handleRenderRequest.d.ts +2 -1
- package/lib/worker/handleRenderRequest.js +82 -29
- package/lib/worker/shutdownHooks.d.ts +12 -0
- package/lib/worker/shutdownHooks.js +41 -0
- package/lib/worker.d.ts +1 -9
- package/lib/worker.js +84 -77
- package/package.json +48 -3
|
@@ -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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
71
|
+
data: result,
|
|
42
72
|
};
|
|
43
73
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
|
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
|
-
|
|
550
|
-
|
|
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.
|
|
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.
|
|
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
|
},
|