Skip to content

Commit d9125df

Browse files
Refactor incremental render request handling and improve error management
- Removed unnecessary bundle validation checks from the incremental render request flow. - Enhanced the `handleIncrementalRenderRequest` function to directly call `handleRenderRequest`, streamlining the rendering process. - Updated the `IncrementalRenderInitialRequest` type to support a more flexible structure for dependency timestamps. - Improved error handling to capture unexpected errors during the rendering process, ensuring robust responses. - Added cleanup logic in tests to restore mocks after each test case.
1 parent bf55456 commit d9125df

File tree

3 files changed

+69
-44
lines changed

3 files changed

+69
-44
lines changed

react_on_rails_pro/packages/node-renderer/src/worker.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import {
3434
getAssetPath,
3535
getBundleDirectory,
3636
deleteUploadedAssets,
37-
validateBundlesExist,
3837
} from './shared/utils';
3938
import * as errorReporter from './shared/errorReporter';
4039
import { lock, unlock } from './shared/locks';
@@ -290,10 +289,6 @@ export default function run(config: Partial<Config>) {
290289
}>('/bundles/:bundleTimestamp/incremental-render/:renderRequestDigest', async (req, res) => {
291290
const { bundleTimestamp } = req.params;
292291

293-
// Perform protocol + auth checks as early as possible. For protocol check,
294-
// we need the first NDJSON object; thus defer protocol/auth until first chunk is parsed.
295-
// Headers and status will be set after validation passes to avoid premature 200 status.
296-
297292
// Stream parser state
298293
let renderResult: Awaited<ReturnType<typeof handleIncrementalRenderRequest>> | null = null;
299294

@@ -306,7 +301,10 @@ export default function run(config: Partial<Config>) {
306301
const tempReqBody = typeof obj === 'object' && obj !== null ? (obj as Record<string, unknown>) : {};
307302

308303
// Protocol check
309-
const protoResult = checkProtocolVersion({ ...req, body: tempReqBody } as unknown as FastifyRequest);
304+
const protoResult = checkProtocolVersion({
305+
...req,
306+
body: tempReqBody,
307+
} as unknown as FastifyRequest);
310308
if (typeof protoResult === 'object') {
311309
return {
312310
response: protoResult,
@@ -315,28 +313,23 @@ export default function run(config: Partial<Config>) {
315313
}
316314

317315
// Auth check
318-
const authResult = authenticate({ ...req, body: tempReqBody } as unknown as FastifyRequest);
316+
const authResult = authenticate({
317+
...req,
318+
body: tempReqBody,
319+
} as unknown as FastifyRequest);
319320
if (typeof authResult === 'object') {
320321
return {
321322
response: authResult,
322323
shouldContinue: false,
323324
};
324325
}
325326

326-
// Bundle validation
327+
// Extract data for incremental render request
327328
const dependencyBundleTimestamps = extractBodyArrayField(
328329
tempReqBody as WithBodyArrayField<Record<string, unknown>, 'dependencyBundleTimestamps'>,
329330
'dependencyBundleTimestamps',
330331
);
331-
const missingBundleError = await validateBundlesExist(bundleTimestamp, dependencyBundleTimestamps);
332-
if (missingBundleError) {
333-
return {
334-
response: missingBundleError,
335-
shouldContinue: false,
336-
};
337-
}
338332

339-
// All validation passed - get response stream
340333
const initial: IncrementalRenderInitialRequest = {
341334
renderingRequest: String((tempReqBody as { renderingRequest?: string }).renderingRequest ?? ''),
342335
bundleTimestamp,
Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Readable } from 'stream';
21
import type { ResponseResult } from '../shared/utils';
2+
import { handleRenderRequest } from './handleRenderRequest';
33

44
export type IncrementalRenderSink = {
55
/** Called for every subsequent NDJSON object after the first one */
@@ -13,7 +13,7 @@ export type IncrementalRenderSink = {
1313
export type IncrementalRenderInitialRequest = {
1414
renderingRequest: string;
1515
bundleTimestamp: string | number;
16-
dependencyBundleTimestamps?: Array<string | number>;
16+
dependencyBundleTimestamps?: string[] | number[];
1717
};
1818

1919
export type IncrementalRenderResult = {
@@ -22,36 +22,64 @@ export type IncrementalRenderResult = {
2222
};
2323

2424
/**
25-
* Starts handling an incremental render request. This function is intended to:
26-
* - Initialize any resources needed to process the render
27-
* - Return both a stream that will be sent to the client and a sink for incoming chunks
28-
*
29-
* NOTE: This is intentionally left unimplemented. Tests should mock this.
25+
* Starts handling an incremental render request. This function:
26+
* - Calls handleRenderRequest internally to handle all validation and VM execution
27+
* - Returns the result from handleRenderRequest directly
28+
* - Provides a sink for future incremental updates (to be implemented in next commit)
3029
*/
31-
export function handleIncrementalRenderRequest(initial: IncrementalRenderInitialRequest): Promise<IncrementalRenderResult> {
32-
// Empty placeholder implementation. Real logic will be added later.
33-
return Promise.resolve({
34-
response: {
35-
status: 200,
36-
headers: { 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate' },
37-
stream: new Readable({
38-
read() {
39-
// No-op for now
30+
export async function handleIncrementalRenderRequest(
31+
initial: IncrementalRenderInitialRequest,
32+
): Promise<IncrementalRenderResult> {
33+
const { renderingRequest, bundleTimestamp, dependencyBundleTimestamps } = initial;
34+
35+
try {
36+
// Call handleRenderRequest internally to handle all validation and VM execution
37+
const renderResult = await handleRenderRequest({
38+
renderingRequest,
39+
bundleTimestamp,
40+
dependencyBundleTimestamps,
41+
providedNewBundles: undefined,
42+
assetsToCopy: undefined,
43+
});
44+
45+
// Return the result directly with a placeholder sink
46+
return {
47+
response: renderResult,
48+
sink: {
49+
add: () => {
50+
/* no-op - will be implemented in next commit */
51+
},
52+
end: () => {
53+
/* no-op - will be implemented in next commit */
54+
},
55+
abort: () => {
56+
/* no-op - will be implemented in next commit */
4057
},
41-
}),
42-
} as ResponseResult,
43-
sink: {
44-
add: () => {
45-
/* no-op */
4658
},
47-
end: () => {
48-
/* no-op */
59+
};
60+
} catch (error) {
61+
// Handle any unexpected errors
62+
const errorMessage = error instanceof Error ? error.message : String(error);
63+
64+
return {
65+
response: {
66+
status: 500,
67+
headers: { 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate' },
68+
data: errorMessage,
4969
},
50-
abort: () => {
51-
/* no-op */
70+
sink: {
71+
add: () => {
72+
/* no-op */
73+
},
74+
end: () => {
75+
/* no-op */
76+
},
77+
abort: () => {
78+
/* no-op */
79+
},
5280
},
53-
},
54-
});
81+
};
82+
}
5583
}
5684

5785
export type { ResponseResult };

react_on_rails_pro/packages/node-renderer/tests/incrementalRender.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ describe('incremental render NDJSON endpoint', () => {
211211
*/
212212
const createStreamingResponsePromise = (req: http.ClientRequest) => {
213213
const receivedChunks: string[] = [];
214-
214+
215215
const promise = new Promise<{ statusCode: number; streamedData: string[] }>((resolve, reject) => {
216216
req.on('response', (res) => {
217217
res.on('data', (chunk: Buffer) => {
@@ -236,6 +236,10 @@ describe('incremental render NDJSON endpoint', () => {
236236
return { promise, receivedChunks };
237237
};
238238

239+
afterEach(() => {
240+
jest.restoreAllMocks();
241+
});
242+
239243
beforeAll(async () => {
240244
await app.ready();
241245
await app.listen({ port: 0 });

0 commit comments

Comments
 (0)