Skip to content

Commit 68762f1

Browse files
committed
v0:feat: add request-scoped services context enhance logging across processors
1 parent 225c219 commit 68762f1

9 files changed

Lines changed: 146 additions & 80 deletions

File tree

apps/workers/v0/src/app.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ app.get('/', (c) => {
2525
runtime: getRuntimeKey(),
2626
nodeEnv: c.env.WORKER_NODE_ENV,
2727
connInfo: info,
28+
services: {
29+
scrapeService: !!c.var.scrapeService,
30+
linkService: !!c.var.linkService,
31+
markdownConverter: !!c.var.markdownConverter,
32+
},
2833
});
2934
});
3035

apps/workers/v0/src/lib/context.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import type { ResponseHeadersPluginContext } from '@orpc/server/plugins';
33
import type { Ratelimit } from '@upstash/ratelimit';
44
import type { Context as HonoContext } from 'hono';
55
import type { RequestIdVariables } from 'hono/request-id';
6+
import type { NodeHtmlMarkdown } from 'node-html-markdown';
67
import type { getAuthClient } from '@/middlewares/client.auth';
8+
import type { LinkService } from '@/services/link/link.service';
9+
import type { ScrapeService } from '@/services/scrape/scrape.service';
710

811
export interface AppVariables extends RequestIdVariables {
912
/** Auth Instance */
@@ -20,6 +23,10 @@ export interface AppVariables extends RequestIdVariables {
2023
userIP: string | null;
2124
/** Rate Limiter */
2225
rateLimiter: Ratelimit;
26+
/** Shared Services - Created at app level for performance */
27+
scrapeService: ScrapeService;
28+
linkService: LinkService;
29+
markdownConverter: NodeHtmlMarkdown;
2330
}
2431

2532
export interface AppBindings {

apps/workers/v0/src/lib/hono/create-hono-app.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { apiKeyAuthMiddleware } from '@/middlewares/api-key-auth.hono';
1010
import { connInfoMiddleware } from '@/middlewares/connInfo.hono';
1111
import { cookieAuthMiddleware } from '@/middlewares/cookie-auth.hono';
1212
import deepCrawlCors from '@/middlewares/cors.hono';
13+
import { servicesAppMiddleware } from '@/middlewares/serivces.app';
1314
import { serviceFetcherMiddleware } from '@/middlewares/service-fetchers.hono';
1415

1516
export default function createHonoApp() {
@@ -31,6 +32,7 @@ export default function createHonoApp() {
3132
.use('*', cookieAuthMiddleware)
3233
.use('*', connInfoMiddleware)
3334

35+
.use('*', servicesAppMiddleware)
3436
.use('*', prettyJSON());
3537

3638
// Register check-auth route
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { createMiddleware } from 'hono/factory';
2+
import { NodeHtmlMarkdown } from 'node-html-markdown';
3+
import type { AppBindings } from '@/lib/context';
4+
import { LinkService } from '@/services/link/link.service';
5+
import { ScrapeService } from '@/services/scrape/scrape.service';
6+
import { logDebug } from '@/utils/loggers';
7+
import { nhmCustomTranslators, nhmTranslators } from '@/utils/markdown';
8+
9+
// Request-scoped service factory to prevent resource leaks in workerd
10+
// This ensures proper IoContext lifecycle management while maintaining performance
11+
function createRequestScopedServices() {
12+
const serviceCreationStart = Date.now();
13+
14+
const services = {
15+
scrapeService: new ScrapeService(),
16+
linkService: new LinkService(),
17+
markdownConverter: new NodeHtmlMarkdown(
18+
{}, // options
19+
nhmCustomTranslators, // customTransformers
20+
nhmTranslators, // customCodeBlockTranslators
21+
),
22+
};
23+
24+
const serviceCreationTime = Date.now() - serviceCreationStart;
25+
26+
logDebug('[PERF] Request-scoped services created:', {
27+
timestamp: new Date().toISOString(),
28+
services: Object.keys(services),
29+
serviceCreationTime,
30+
});
31+
32+
return services;
33+
}
34+
35+
export const servicesAppMiddleware = createMiddleware<AppBindings>(
36+
async (c, next) => {
37+
// Create services per request to respect IoContext boundaries
38+
// This prevents resource leaks in production workerd
39+
const services = createRequestScopedServices();
40+
41+
c.set('scrapeService', services.scrapeService);
42+
c.set('linkService', services.linkService);
43+
c.set('markdownConverter', services.markdownConverter);
44+
45+
return next();
46+
},
47+
);

apps/workers/v0/src/routers/links/links.processor.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,14 @@ import {
2222
} from '@/config/constants';
2323
import { DEFAULT_LINK_OPTIONS } from '@/config/default-options';
2424
import type { ORPCContext } from '@/lib/context';
25-
import { type _linksSets, LinkService } from '@/services/link/link.service';
26-
import { ScrapeService } from '@/services/scrape/scrape.service';
25+
import type { _linksSets } from '@/services/link/link.service';
2726
import { formatDuration } from '@/utils/formater';
2827
import { kvPutWithRetry } from '@/utils/kv/retry';
2928
import * as helpers from '@/utils/links/helpers';
29+
import { logDebug, logError, logWarn } from '@/utils/loggers';
3030
import { cleanEmptyValues } from '@/utils/response/clean-empty-values';
3131
import { targetUrlHelper } from '@/utils/url/target-url-helper';
3232

33-
// Create service instances at module level for reuse across requests
34-
const scrapeService = new ScrapeService();
35-
const linkService = new LinkService();
36-
3733
// Helper function to check if a URL exists in the Set of visited URLs
3834
function isUrlInVisitedSet(
3935
urlSet: Set<VisitedUrl>,
@@ -90,6 +86,21 @@ export async function processLinksRequest(
9086
const timestamp = new Date().toISOString();
9187
const startRequestTime = performance.now();
9288

89+
// Use app-level services from context for optimal performance
90+
const serviceAccessStart = Date.now();
91+
const scrapeService = c.var.scrapeService;
92+
const linkService = c.var.linkService;
93+
const serviceAccessTime = Date.now() - serviceAccessStart;
94+
95+
logDebug('[PERF] Links processor using request-scoped services:', {
96+
url,
97+
serviceAccessTime,
98+
hasScrapeService: !!scrapeService,
99+
hasLinkService: !!linkService,
100+
timestamp: new Date().toISOString(),
101+
requestId: c.var.requestId,
102+
});
103+
93104
// config
94105
const withTree = isTree !== false; // True by default, false only if explicitly set to false
95106
const linkExtractionOptions: LinkExtractionOptions = {
@@ -311,7 +322,7 @@ export async function processLinksRequest(
311322
linkService.mergeLinks(extractedKinLinks, _linksSets);
312323
} catch (error) {
313324
// Log the error and add to skipped URLs
314-
console.error(`Error processing path ${kin}:`, error);
325+
logError(`Error processing path ${kin}:`, error);
315326
const errorMessage =
316327
error instanceof Error ? error.message : String(error);
317328
skippedUrls.set(kin, `Failed to process: ${errorMessage}`);
@@ -367,7 +378,7 @@ export async function processLinksRequest(
367378
}
368379
} catch (error) {
369380
// Log the error and add to skipped URLs
370-
console.error(`Error processing root URL ${rootUrl}:`, error);
381+
logError(`Error processing root URL ${rootUrl}:`, error);
371382
const errorMessage =
372383
error instanceof Error ? error.message : String(error);
373384
skippedUrls.set(rootUrl, `Failed to process: ${errorMessage}`);
@@ -407,7 +418,7 @@ export async function processLinksRequest(
407418
}
408419
}
409420
} catch (error) {
410-
console.error(
421+
logError(
411422
`Error reading from DEEPCRAWL_V0_LINKS_STORE for ${rootUrl}:`,
412423
error,
413424
);
@@ -572,7 +583,7 @@ export async function processLinksRequest(
572583
},
573584
);
574585
} catch (error) {
575-
console.warn(
586+
logWarn(
576587
`[LINKS Endpoint] Failed to store sitemap in KV for ${rootUrl}. Error: ${error instanceof Error ? error.message : String(error)}`,
577588
);
578589
// Skip KV put on error, allowing the function to continue
@@ -667,7 +678,7 @@ export async function processLinksRequest(
667678

668679
return linksPostResponse as LinksSuccessResponse;
669680
} catch (error) {
670-
console.error('❌ [LINKS PROCESSOR] error:', error);
681+
logError('❌ [LINKS PROCESSOR] error:', error);
671682

672683
const err =
673684
error instanceof Error

apps/workers/v0/src/routers/read/read.processor.ts

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,22 @@ import type {
66
ReadSuccessResponse,
77
ScrapedData,
88
} from '@deepcrawl/types/index';
9-
10-
import { NodeHtmlMarkdown } from 'node-html-markdown';
11-
9+
import type { NodeHtmlMarkdown } from 'node-html-markdown';
1210
import { KV_CACHE_EXPIRATION_TTL } from '@/config/constants';
1311
import { ENABLE_READ_CACHE } from '@/config/default-options';
1412
import type { ORPCContext } from '@/lib/context';
15-
import { ScrapeService } from '@/services/scrape/scrape.service';
1613
import { formatDuration } from '@/utils/formater';
1714
import { getReadCacheKey } from '@/utils/kv/read-kv-key';
1815
import { kvPutWithRetry } from '@/utils/kv/retry';
16+
import { logDebug, logError } from '@/utils/loggers';
1917
import {
2018
fixCodeBlockFormatting,
21-
nhmCustomTranslators,
22-
nhmTranslators,
2319
processMultiLineLinks,
2420
removeNavigationAidLinks,
2521
} from '@/utils/markdown';
2622
import { cleanEmptyValues } from '@/utils/response/clean-empty-values';
2723
import { targetUrlHelper } from '@/utils/url/target-url-helper';
2824

29-
// Create service instance at module level for reuse across requests
30-
const scrapeService = new ScrapeService();
31-
32-
// Pre-configure NodeHtmlMarkdown at module level to avoid per-request setup
33-
const markdownConverter = new NodeHtmlMarkdown(
34-
/* options */ {},
35-
/* customTransformers */ nhmCustomTranslators,
36-
/* customCodeBlockTranslators */ nhmTranslators,
37-
);
38-
3925
/**
4026
* Calculates performance metrics for the read operation.
4127
* @param startTime - Timestamp in milliseconds when the operation started.
@@ -109,15 +95,19 @@ function hasMeaningfulMarkdown(markdown: string): boolean {
10995
}
11096

11197
/**
112-
* Convert HTML to Markdown
113-
* @param param0 - HTML content to convert
98+
* Convert HTML to Markdown using app-level converter
99+
* @param param0 - HTML content and markdown converter
114100
* @returns Markdown content
115101
*/
116-
function getMarkdown({ html }: { html: string }): string {
102+
function getMarkdown({
103+
html,
104+
markdownConverter,
105+
}: {
106+
html: string;
107+
markdownConverter: NodeHtmlMarkdown;
108+
}): string {
117109
try {
118-
const nhm = markdownConverter;
119-
120-
let nhmMarkdown = nhm.translate(html);
110+
let nhmMarkdown = markdownConverter.translate(html);
121111
nhmMarkdown = processMultiLineLinks(nhmMarkdown);
122112
nhmMarkdown = removeNavigationAidLinks(nhmMarkdown);
123113
nhmMarkdown = fixCodeBlockFormatting(nhmMarkdown);
@@ -303,6 +293,19 @@ export async function processReadRequest(
303293
requestId: c.var.requestId,
304294
});
305295

296+
// Use app-level service instance for optimal performance
297+
const serviceAccessStart = Date.now();
298+
const scrapeService = c.var.scrapeService;
299+
const serviceAccessTime = Date.now() - serviceAccessStart;
300+
301+
console.log('[PERF] Read processor using request-scoped service:', {
302+
url: targetUrl,
303+
serviceAccessTime,
304+
hasService: !!scrapeService,
305+
timestamp: new Date().toISOString(),
306+
requestId: c.var.requestId,
307+
});
308+
306309
const {
307310
title,
308311
rawHtml,
@@ -342,17 +345,20 @@ export async function processReadRequest(
342345
const markdownStart = Date.now();
343346

344347
if (cleanedHtml) {
345-
console.log('[PERF] Read processor markdown conversion started:', {
348+
logDebug('[PERF] Read processor markdown conversion started:', {
346349
url: targetUrl,
347350
htmlSize: cleanedHtml.length,
348351
timestamp: new Date().toISOString(),
349352
requestId: c.var.requestId,
350353
});
351354

352-
const convertedMarkdown = getMarkdown({ html: cleanedHtml });
355+
const convertedMarkdown = getMarkdown({
356+
html: cleanedHtml,
357+
markdownConverter: c.var.markdownConverter,
358+
});
353359
const markdownTime = Date.now() - markdownStart;
354360

355-
console.log('[PERF] Read processor markdown conversion completed:', {
361+
logDebug('[PERF] Read processor markdown conversion completed:', {
356362
url: targetUrl,
357363
markdownTime,
358364
markdownSize: convertedMarkdown.length,
@@ -368,7 +374,7 @@ export async function processReadRequest(
368374
// For POST requests with markdown=true but no meaningful content,
369375
// provide informative default markdown instead of undefined
370376
markdown = getDefaultMarkdown(title, targetUrl, description);
371-
console.log(
377+
logDebug(
372378
'[PERF] Read processor using default markdown (no meaningful content):',
373379
{
374380
url: targetUrl,
@@ -381,7 +387,7 @@ export async function processReadRequest(
381387
// For POST requests with markdown=true but no extractable content,
382388
// provide informative default markdown instead of undefined
383389
markdown = getDefaultMarkdown(title, targetUrl, description);
384-
console.log(
390+
logDebug(
385391
'[PERF] Read processor using default markdown (no cleaned HTML):',
386392
{
387393
url: targetUrl,
@@ -418,7 +424,7 @@ export async function processReadRequest(
418424
});
419425
const responseTime = Date.now() - responseStart;
420426

421-
console.log('[PERF] Read processor response prepared:', {
427+
logDebug('[PERF] Read processor response prepared:', {
422428
url: targetUrl,
423429
responseTime,
424430
hasResponse: !!readResponse,
@@ -443,7 +449,7 @@ export async function processReadRequest(
443449
)
444450
: JSON.stringify(readResponse);
445451

446-
console.log('[PERF] Read processor cache write started:', {
452+
logDebug('[PERF] Read processor cache write started:', {
447453
url: targetUrl,
448454
cacheValueSize: valueToCache.length,
449455
isGETRequest,
@@ -466,15 +472,15 @@ export async function processReadRequest(
466472
);
467473

468474
const cacheWriteTime = Date.now() - cacheWriteStart;
469-
console.log('[PERF] Read processor cache write completed:', {
475+
logDebug('[PERF] Read processor cache write completed:', {
470476
url: targetUrl,
471477
cacheWriteTime,
472478
timestamp: new Date().toISOString(),
473479
requestId: c.var.requestId,
474480
});
475481
} catch (error) {
476482
const cacheWriteErrorTime = Date.now() - processorStart;
477-
console.error('[PERF] Read processor cache write error:', {
483+
logError('[PERF] Read processor cache write error:', {
478484
url: targetUrl,
479485
cacheWriteErrorTime,
480486
error: error instanceof Error ? error.message : String(error),
@@ -487,7 +493,7 @@ export async function processReadRequest(
487493

488494
const totalProcessorTime = Date.now() - processorStart;
489495

490-
console.log('[PERF] Read processor completed:', {
496+
logDebug('[PERF] Read processor completed:', {
491497
url: targetUrl,
492498
totalProcessorTime,
493499
scrapeTime,

0 commit comments

Comments
 (0)