@crawlee/core 4.0.0-beta.99 → 4.0.0-rc.1

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.
Files changed (133) hide show
  1. package/README.md +1 -1
  2. package/configuration.d.ts +16 -47
  3. package/configuration.js +13 -25
  4. package/debug.js +4 -4
  5. package/errors.d.ts +28 -38
  6. package/errors.js +33 -47
  7. package/events/event_manager.d.ts +2 -2
  8. package/events/event_manager.js +7 -6
  9. package/events/index.d.ts +1 -0
  10. package/events/local_event_manager.d.ts +1 -8
  11. package/events/local_event_manager.js +13 -13
  12. package/events/system_info.d.ts +38 -0
  13. package/index.d.ts +2 -8
  14. package/index.js +4 -8
  15. package/internal.d.ts +8 -0
  16. package/internal.js +9 -0
  17. package/log.d.ts +10 -11
  18. package/log.js +52 -20
  19. package/memory-storage/memory-storage.d.ts +15 -18
  20. package/memory-storage/memory-storage.js +80 -58
  21. package/memory-storage/resource-clients/dataset.d.ts +1 -6
  22. package/memory-storage/resource-clients/dataset.js +23 -31
  23. package/memory-storage/resource-clients/key-value-store.d.ts +1 -10
  24. package/memory-storage/resource-clients/key-value-store.js +43 -67
  25. package/memory-storage/resource-clients/request-queue.d.ts +1 -42
  26. package/memory-storage/resource-clients/request-queue.js +109 -117
  27. package/owned_or_injected.d.ts +1 -3
  28. package/owned_or_injected.js +17 -17
  29. package/package.json +17 -20
  30. package/proxy_configuration.d.ts +21 -26
  31. package/proxy_configuration.js +35 -25
  32. package/recoverable_state.d.ts +104 -47
  33. package/recoverable_state.js +199 -74
  34. package/request.d.ts +20 -107
  35. package/request.js +78 -244
  36. package/serialization.js +17 -16
  37. package/service_locator.d.ts +22 -10
  38. package/service_locator.js +59 -48
  39. package/storages/batched_adds.d.ts +37 -0
  40. package/storages/batched_adds.js +73 -0
  41. package/storages/dataset.d.ts +13 -8
  42. package/storages/dataset.js +149 -40
  43. package/storages/index.d.ts +4 -4
  44. package/storages/index.js +2 -4
  45. package/storages/key_value_store.d.ts +16 -35
  46. package/storages/key_value_store.js +223 -110
  47. package/storages/key_value_store_codec.js +6 -11
  48. package/storages/request_dedup_cache.d.ts +1 -4
  49. package/storages/request_dedup_cache.js +15 -15
  50. package/storages/request_list.d.ts +9 -104
  51. package/storages/request_list.js +236 -233
  52. package/storages/request_loader.d.ts +49 -18
  53. package/storages/request_loader.js +36 -1
  54. package/storages/request_manager.d.ts +86 -0
  55. package/storages/request_manager_tandem.d.ts +14 -38
  56. package/storages/request_manager_tandem.js +67 -64
  57. package/storages/request_queue.d.ts +23 -50
  58. package/storages/request_queue.js +371 -226
  59. package/storages/storage_instance_manager.d.ts +2 -4
  60. package/storages/storage_instance_manager.js +21 -21
  61. package/storages/storage_stats.d.ts +1 -1
  62. package/storages/storage_stats.js +4 -4
  63. package/storages/transaction.d.ts +270 -0
  64. package/storages/transaction.js +296 -0
  65. package/storages/utils.d.ts +6 -3
  66. package/storages/utils.js +11 -2
  67. package/system-info/runtime.js +7 -7
  68. package/url.d.ts +9 -0
  69. package/url.js +11 -0
  70. package/validators.d.ts +23 -25
  71. package/validators.js +14 -25
  72. package/autoscaling/autoscaled_pool.d.ts +0 -213
  73. package/autoscaling/autoscaled_pool.js +0 -378
  74. package/autoscaling/client_load_signal.d.ts +0 -59
  75. package/autoscaling/client_load_signal.js +0 -73
  76. package/autoscaling/concurrency_system.d.ts +0 -283
  77. package/autoscaling/concurrency_system.js +0 -350
  78. package/autoscaling/cpu_load_signal.d.ts +0 -44
  79. package/autoscaling/cpu_load_signal.js +0 -46
  80. package/autoscaling/event_loop_load_signal.d.ts +0 -54
  81. package/autoscaling/event_loop_load_signal.js +0 -60
  82. package/autoscaling/index.d.ts +0 -9
  83. package/autoscaling/index.js +0 -9
  84. package/autoscaling/load_signal.d.ts +0 -99
  85. package/autoscaling/load_signal.js +0 -103
  86. package/autoscaling/memory_load_signal.d.ts +0 -56
  87. package/autoscaling/memory_load_signal.js +0 -106
  88. package/autoscaling/snapshotter.d.ts +0 -87
  89. package/autoscaling/snapshotter.js +0 -67
  90. package/autoscaling/system_status.d.ts +0 -161
  91. package/autoscaling/system_status.js +0 -139
  92. package/autoscaling/weighted_avg.d.ts +0 -5
  93. package/autoscaling/weighted_avg.js +0 -14
  94. package/cookie_utils.d.ts +0 -44
  95. package/cookie_utils.js +0 -122
  96. package/crawlers/context_pipeline.d.ts +0 -70
  97. package/crawlers/context_pipeline.js +0 -122
  98. package/crawlers/crawler_commons.d.ts +0 -257
  99. package/crawlers/crawler_commons.js +0 -107
  100. package/crawlers/error_snapshotter.d.ts +0 -59
  101. package/crawlers/error_snapshotter.js +0 -117
  102. package/crawlers/error_tracker.d.ts +0 -54
  103. package/crawlers/error_tracker.js +0 -308
  104. package/crawlers/index.d.ts +0 -5
  105. package/crawlers/index.js +0 -5
  106. package/crawlers/internals/types.d.ts +0 -7
  107. package/crawlers/statistics.d.ts +0 -209
  108. package/crawlers/statistics.js +0 -350
  109. package/enqueue_links/enqueue_links.d.ts +0 -264
  110. package/enqueue_links/enqueue_links.js +0 -271
  111. package/enqueue_links/index.d.ts +0 -2
  112. package/enqueue_links/index.js +0 -2
  113. package/enqueue_links/shared.d.ts +0 -83
  114. package/enqueue_links/shared.js +0 -221
  115. package/router.d.ts +0 -309
  116. package/router.js +0 -309
  117. package/session_pool/consts.d.ts +0 -3
  118. package/session_pool/consts.js +0 -3
  119. package/session_pool/errors.d.ts +0 -7
  120. package/session_pool/errors.js +0 -11
  121. package/session_pool/fingerprint.d.ts +0 -9
  122. package/session_pool/fingerprint.js +0 -30
  123. package/session_pool/index.d.ts +0 -4
  124. package/session_pool/index.js +0 -4
  125. package/session_pool/session.d.ts +0 -161
  126. package/session_pool/session.js +0 -218
  127. package/session_pool/session_pool.d.ts +0 -246
  128. package/session_pool/session_pool.js +0 -386
  129. package/storages/access_checking.d.ts +0 -12
  130. package/storages/access_checking.js +0 -17
  131. package/storages/sitemap_request_loader.d.ts +0 -249
  132. package/storages/sitemap_request_loader.js +0 -432
  133. /package/{crawlers/internals/types.js → events/system_info.js} +0 -0
@@ -1,139 +0,0 @@
1
- import { evaluateLoadSignalSample } from './load_signal.js';
2
- /**
3
- * How far back the *current* system status looks by default — the window that gates task dispatch.
4
- * @internal
5
- */
6
- export const DEFAULT_CURRENT_HISTORY_SECS = 5;
7
- /**
8
- * How far back the *historical* system status looks by default — the window autoscaling decisions are based on, and
9
- * therefore how much history the signals are asked to retain.
10
- * @internal
11
- */
12
- export const DEFAULT_SNAPSHOT_HISTORY_SECS = 30;
13
- /** The four built-in signal names that map to typed `SystemInfo` fields, and the option that switches each off. */
14
- const BUILTIN_SIGNAL_OPTION_KEYS = {
15
- memInfo: 'memory',
16
- eventLoopInfo: 'eventLoop',
17
- cpuInfo: 'cpu',
18
- clientInfo: 'client',
19
- };
20
- const BUILTIN_SIGNAL_NAMES = new Set(Object.keys(BUILTIN_SIGNAL_OPTION_KEYS));
21
- /**
22
- * Reads the overload verdict of every signal — the {@link Snapshotter}'s built-in four plus any custom ones — and
23
- * combines them into a {@link SystemInfo}: each signal is a time-weighted average of its snapshots, and the system
24
- * is overloaded whenever at least one of them is.
25
- *
26
- * Evaluated over two windows, both requested explicitly from every signal so that a signal's private retention cannot
27
- * widen what it contributes: a short `currentHistorySecs` one ({@link SystemStatus.getCurrentStatus}, gating task
28
- * dispatch) and a longer `historySecs` one ({@link SystemStatus.getHistoricalStatus}, driving autoscaling).
29
- *
30
- * An implementation detail of the {@link ConcurrencySystem}, configured through
31
- * {@link ConcurrencySystemOptions}.
32
- * @internal
33
- */
34
- export class SystemStatus {
35
- currentHistoryMillis;
36
- historyMillis;
37
- signals;
38
- constructor(options) {
39
- const { currentHistorySecs = DEFAULT_CURRENT_HISTORY_SECS, historySecs = DEFAULT_SNAPSHOT_HISTORY_SECS, snapshotter, loadSignals = [], } = options;
40
- this.currentHistoryMillis = currentHistorySecs * 1000;
41
- this.historyMillis = historySecs * 1000;
42
- this.signals = [...snapshotter.getLoadSignals(), ...loadSignals];
43
- this.assertUniqueSignalNames();
44
- }
45
- /**
46
- * The widest window any signal will be queried with, and therefore exactly how much history the signals are asked
47
- * to retain when they start. Derived here, where the windows are resolved, so nothing has to reapply their
48
- * defaults.
49
- */
50
- get maxSampleWindowMillis() {
51
- return Math.max(this.currentHistoryMillis, this.historyMillis);
52
- }
53
- /**
54
- * Signal names are the keys of the reported {@link SystemInfo}, so a duplicate would leave a status object that
55
- * contradicts actual behavior: both signals are still evaluated (any overloaded one holds concurrency down), but
56
- * only the last is reported.
57
- */
58
- assertUniqueSignalNames() {
59
- const seen = new Set();
60
- for (const { name } of this.signals) {
61
- if (!seen.has(name)) {
62
- seen.add(name);
63
- continue;
64
- }
65
- const hint = BUILTIN_SIGNAL_NAMES.has(name)
66
- ? `it is the name of a built-in signal. To replace that signal, switch it off with \`loadSignals: { ${BUILTIN_SIGNAL_OPTION_KEYS[name]}: false }\` and keep your implementation in \`loadSignals.custom\`; to run yours alongside it, give it a different name.`
67
- : 'two custom signals cannot share a name - rename one of them.';
68
- throw new Error(`Duplicate load signal name ${JSON.stringify(name)}: ${hint}`);
69
- }
70
- }
71
- /**
72
- * Returns an {@link SystemInfo} object with the following structure:
73
- *
74
- * ```javascript
75
- * {
76
- * isSystemIdle: Boolean,
77
- * memInfo: Object,
78
- * eventLoopInfo: Object,
79
- * cpuInfo: Object
80
- * }
81
- * ```
82
- *
83
- * Where the `isSystemIdle` property is set to `false` if the system
84
- * has been overloaded in the last `options.currentHistorySecs` seconds,
85
- * and `true` otherwise.
86
- */
87
- getCurrentStatus() {
88
- return this.isSystemIdle(this.currentHistoryMillis);
89
- }
90
- /**
91
- * Returns an {@link SystemInfo} object with the following structure:
92
- *
93
- * ```javascript
94
- * {
95
- * isSystemIdle: Boolean,
96
- * memInfo: Object,
97
- * eventLoopInfo: Object,
98
- * cpuInfo: Object
99
- * }
100
- * ```
101
- *
102
- * Where the `isSystemIdle` property is set to `false` if the system has been overloaded within the last
103
- * `historySecs` seconds and `true` otherwise.
104
- */
105
- getHistoricalStatus() {
106
- return this.isSystemIdle(this.historyMillis);
107
- }
108
- /**
109
- * Returns a system status object.
110
- */
111
- isSystemIdle(sampleDurationMillis) {
112
- const result = {
113
- isSystemIdle: true,
114
- memInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
115
- eventLoopInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
116
- cpuInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
117
- clientInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
118
- };
119
- let loadSignalInfo;
120
- for (const signal of this.signals) {
121
- const sample = signal.getSample(sampleDurationMillis);
122
- const info = evaluateLoadSignalSample(sample, signal.overloadedRatio);
123
- if (info.isOverloaded) {
124
- result.isSystemIdle = false;
125
- }
126
- if (BUILTIN_SIGNAL_NAMES.has(signal.name)) {
127
- result[signal.name] = info;
128
- }
129
- else {
130
- loadSignalInfo ??= {};
131
- loadSignalInfo[signal.name] = info;
132
- }
133
- }
134
- if (loadSignalInfo) {
135
- result.loadSignalInfo = loadSignalInfo;
136
- }
137
- return result;
138
- }
139
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Computes a weighted average of an array of numbers, complemented by an array of weights.
3
- * @ignore
4
- */
5
- export declare function weightedAvg(arrValues: number[], arrWeights: number[]): number;
@@ -1,14 +0,0 @@
1
- /**
2
- * Computes a weighted average of an array of numbers, complemented by an array of weights.
3
- * @ignore
4
- */
5
- export function weightedAvg(arrValues, arrWeights) {
6
- const result = arrValues
7
- .map((value, i) => {
8
- const weight = arrWeights[i];
9
- const sum = value * weight;
10
- return [sum, weight];
11
- })
12
- .reduce((p, c) => [p[0] + c[0], p[1] + c[1]], [0, 0]);
13
- return result[0] / result[1];
14
- }
package/cookie_utils.d.ts DELETED
@@ -1,44 +0,0 @@
1
- import type { Cookie as CookieObject } from '@crawlee/types';
2
- import { Cookie } from 'tough-cookie';
3
- export interface ResponseLike {
4
- url?: string | (() => string);
5
- headers?: Record<string, string | string[] | undefined> | (() => Record<string, string | string[] | undefined>);
6
- }
7
- /**
8
- * @internal
9
- */
10
- export declare function getCookiesFromResponse(response: Response): Cookie[];
11
- /**
12
- * Calculate cookie expiration date
13
- * @param maxAgeSecs
14
- * @returns Calculated date by session max age seconds.
15
- * @internal
16
- */
17
- export declare function getDefaultCookieExpirationDate(maxAgeSecs: number): Date;
18
- /**
19
- * Transforms tough-cookie to puppeteer cookie.
20
- * @param toughCookie Cookie from CookieJar
21
- * @return Cookie compatible with browser pool
22
- * @internal
23
- */
24
- export declare function toughCookieToBrowserPoolCookie(toughCookie: Cookie): CookieObject;
25
- /**
26
- * Transforms browser-pool cookie to tough-cookie.
27
- * @param cookieObject Cookie object (for instance from the `page.cookies` method).
28
- * @param maxAgeSecs Fallback expiration in seconds when the cookie itself has no `expires`.
29
- * When omitted, such a cookie is stored as a session cookie (no automatic expiration).
30
- * @internal
31
- */
32
- export declare function browserPoolCookieToToughCookie(cookieObject: CookieObject, maxAgeSecs?: number): Cookie;
33
- /**
34
- * @internal
35
- * @param cookieString The cookie string to attempt parsing
36
- * @returns Browser pool compatible cookie, or null if cookie cannot be parsed
37
- */
38
- export declare function cookieStringToToughCookie(cookieString: string): CookieObject | null;
39
- /**
40
- * Merges multiple cookie strings. Keys are compared case-sensitively, warning will be logged
41
- * if we see two cookies with same keys but different casing.
42
- * @internal
43
- */
44
- export declare function mergeCookies(url: string, sourceCookies: string[]): string;
package/cookie_utils.js DELETED
@@ -1,122 +0,0 @@
1
- import { Cookie, CookieJar } from 'tough-cookie';
2
- import { serviceLocator } from './service_locator.js';
3
- import { CookieParseError } from './session_pool/errors.js';
4
- /**
5
- * @internal
6
- */
7
- export function getCookiesFromResponse(response) {
8
- const headers = response.headers;
9
- const cookieHeaders = headers.getSetCookie();
10
- try {
11
- return cookieHeaders.map((cookie) => Cookie.parse(cookie));
12
- }
13
- catch (e) {
14
- throw new CookieParseError(cookieHeaders);
15
- }
16
- }
17
- /**
18
- * Calculate cookie expiration date
19
- * @param maxAgeSecs
20
- * @returns Calculated date by session max age seconds.
21
- * @internal
22
- */
23
- export function getDefaultCookieExpirationDate(maxAgeSecs) {
24
- return new Date(Date.now() + maxAgeSecs * 1000);
25
- }
26
- /**
27
- * Transforms tough-cookie to puppeteer cookie.
28
- * @param toughCookie Cookie from CookieJar
29
- * @return Cookie compatible with browser pool
30
- * @internal
31
- */
32
- export function toughCookieToBrowserPoolCookie(toughCookie) {
33
- return {
34
- name: toughCookie.key,
35
- value: toughCookie.value,
36
- // Puppeteer and Playwright expect 'expires' to be 'Unix time in seconds', not ms
37
- // If there is no expires date (so defaults to Infinity), we don't provide it to the browsers
38
- expires: toughCookie.expires == null || toughCookie.expires === 'Infinity'
39
- ? undefined
40
- : toughCookie.expires.getTime() / 1000,
41
- domain: toughCookie.domain ? `${toughCookie.hostOnly ? '' : '.'}${toughCookie.domain}` : undefined,
42
- path: toughCookie.path ?? undefined,
43
- secure: toughCookie.secure,
44
- httpOnly: toughCookie.httpOnly,
45
- };
46
- }
47
- /**
48
- * Transforms browser-pool cookie to tough-cookie.
49
- * @param cookieObject Cookie object (for instance from the `page.cookies` method).
50
- * @param maxAgeSecs Fallback expiration in seconds when the cookie itself has no `expires`.
51
- * When omitted, such a cookie is stored as a session cookie (no automatic expiration).
52
- * @internal
53
- */
54
- export function browserPoolCookieToToughCookie(cookieObject, maxAgeSecs) {
55
- const isExpiresValid = cookieObject.expires && typeof cookieObject.expires === 'number' && cookieObject.expires > 0;
56
- let expires;
57
- if (isExpiresValid) {
58
- expires = new Date(cookieObject.expires * 1000);
59
- }
60
- else if (maxAgeSecs != null) {
61
- expires = getDefaultCookieExpirationDate(maxAgeSecs);
62
- }
63
- const domainHasLeadingDot = cookieObject.domain?.startsWith?.('.');
64
- const domain = domainHasLeadingDot ? cookieObject.domain?.slice?.(1) : cookieObject.domain;
65
- return new Cookie({
66
- key: cookieObject.name,
67
- value: cookieObject.value,
68
- ...(expires !== undefined && { expires }),
69
- domain,
70
- path: cookieObject.path,
71
- secure: cookieObject.secure,
72
- httpOnly: cookieObject.httpOnly,
73
- hostOnly: !domainHasLeadingDot,
74
- });
75
- }
76
- /**
77
- * @internal
78
- * @param cookieString The cookie string to attempt parsing
79
- * @returns Browser pool compatible cookie, or null if cookie cannot be parsed
80
- */
81
- export function cookieStringToToughCookie(cookieString) {
82
- const parsed = Cookie.parse(cookieString);
83
- if (parsed) {
84
- return toughCookieToBrowserPoolCookie(parsed);
85
- }
86
- return null;
87
- }
88
- /**
89
- * Merges multiple cookie strings. Keys are compared case-sensitively, warning will be logged
90
- * if we see two cookies with same keys but different casing.
91
- * @internal
92
- */
93
- export function mergeCookies(url, sourceCookies) {
94
- const jar = new CookieJar();
95
- // ignore empty cookies
96
- for (const sourceCookieString of sourceCookies) {
97
- // ignore empty cookies
98
- if (!sourceCookieString)
99
- continue;
100
- const cookies = sourceCookieString.split(/ *; */);
101
- for (const cookieString of cookies) {
102
- // ignore extra spaces
103
- if (!cookieString)
104
- continue;
105
- const cookie = Cookie.parse(cookieString);
106
- if (!cookie) {
107
- serviceLocator.getLogger().warning(`Skipping malformed cookie fragment: '${cookieString}'`);
108
- continue;
109
- }
110
- const similarKeyCookie = jar.getCookiesSync(url).find((c) => {
111
- return cookie.key !== c.key && cookie.key.toLowerCase() === c.key.toLowerCase();
112
- });
113
- if (similarKeyCookie) {
114
- serviceLocator
115
- .getLogger()
116
- .warningOnce(`Found cookies with similar name during cookie merging: '${cookie.key}' and '${similarKeyCookie.key}'`);
117
- }
118
- jar.setCookieSync(cookie, url);
119
- }
120
- }
121
- return jar.getCookieStringSync(url);
122
- }
@@ -1,70 +0,0 @@
1
- import type { Awaitable } from '@crawlee/types';
2
- /**
3
- * Represents a middleware step in the context pipeline.
4
- *
5
- * @template TCrawlingContext - The input context type for this middleware
6
- * @template TCrawlingContextExtension - The enhanced output context type
7
- */
8
- export interface ContextMiddleware<TCrawlingContext, TCrawlingContextExtension> {
9
- /** The main middleware function that enhances the context */
10
- action: (context: TCrawlingContext) => Awaitable<TCrawlingContextExtension>;
11
- /** Optional cleanup function called after the consumer finishes or fails */
12
- cleanup?: (context: TCrawlingContext & TCrawlingContextExtension, error?: unknown) => Awaitable<void>;
13
- }
14
- /**
15
- * Encapsulates the logic of gradually enhancing the crawling context with additional information and utilities.
16
- *
17
- * The enhancement is done by a chain of middlewares that are added to the pipeline after its creation.
18
- * This class provides a type-safe way to build a pipeline of context transformations where each step
19
- * can enhance the context with additional properties or utilities.
20
- *
21
- * @template TContextBase - The base context type that serves as the starting point
22
- * @template TCrawlingContext - The final context type after all middleware transformations
23
- */
24
- export declare abstract class ContextPipeline<TContextBase, TCrawlingContext extends TContextBase> {
25
- /**
26
- * Creates a new empty context pipeline.
27
- *
28
- * @template TContextBase - The base context type for the pipeline
29
- * @returns A new ContextPipeline instance with no transformations
30
- */
31
- static create<TContextBase>(): ContextPipeline<TContextBase, TContextBase>;
32
- /**
33
- * Adds a middleware to the pipeline, creating a new pipeline instance.
34
- *
35
- * This method provides a fluent interface for building context transformation pipelines.
36
- * Each middleware can enhance the context with additional properties or utilities.
37
- *
38
- * @template TCrawlingContextExtension - The enhanced context type produced by this middleware
39
- * @param middleware - The middleware to add to the pipeline
40
- * @returns A new ContextPipeline instance with the added middleware
41
- */
42
- abstract compose<TCrawlingContextExtension>(middleware: ContextMiddleware<TCrawlingContext, TCrawlingContextExtension>): ContextPipeline<TContextBase, TCrawlingContext & TCrawlingContextExtension>;
43
- /**
44
- * Chains another pipeline onto this one. The other pipeline's base context must match
45
- * this pipeline's output context. Returns a new pipeline that runs this pipeline's
46
- * middlewares first, then the other pipeline's middlewares.
47
- *
48
- * @template TFinalContext - The final context type after the chained pipeline's transformations
49
- * @param other - The pipeline to append after this one
50
- * @returns A new ContextPipeline combining both pipelines' middlewares
51
- */
52
- abstract chain<TFinalContext extends TCrawlingContext>(other: ContextPipeline<TCrawlingContext, TFinalContext>): ContextPipeline<TContextBase, TFinalContext>;
53
- /**
54
- * Executes the middleware pipeline and passes the final context to a consumer function.
55
- *
56
- * This method runs the crawling context through the entire middleware chain, enhancing it
57
- * at each step, and then passes the final enhanced context to the provided consumer function.
58
- * Proper cleanup is performed even if exceptions occur during processing.
59
- *
60
- * @param crawlingContext - The initial context to process through the pipeline
61
- * @param finalContextConsumer - The function that will receive the final enhanced context
62
- *
63
- * @throws {ContextPipelineInitializationError} When a middleware fails during initialization
64
- * @throws {ContextPipelineInterruptedError} When the pipeline is intentionally interrupted during initialization
65
- * @throws {RequestHandlerError} When the final context consumer throws an exception
66
- * @throws {ContextPipelineCleanupError} When cleanup operations fail
67
- * @throws {SessionError} Session errors are re-thrown as-is for special handling
68
- */
69
- abstract call(crawlingContext: TContextBase, finalContextConsumer: (finalContext: TCrawlingContext) => Awaitable<unknown>): Promise<void>;
70
- }
@@ -1,122 +0,0 @@
1
- import { ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, RequestHandlerError, SessionError, } from '../errors.js';
2
- import { serviceLocator } from '../service_locator.js';
3
- /**
4
- * Encapsulates the logic of gradually enhancing the crawling context with additional information and utilities.
5
- *
6
- * The enhancement is done by a chain of middlewares that are added to the pipeline after its creation.
7
- * This class provides a type-safe way to build a pipeline of context transformations where each step
8
- * can enhance the context with additional properties or utilities.
9
- *
10
- * @template TContextBase - The base context type that serves as the starting point
11
- * @template TCrawlingContext - The final context type after all middleware transformations
12
- */
13
- export class ContextPipeline {
14
- /**
15
- * Creates a new empty context pipeline.
16
- *
17
- * @template TContextBase - The base context type for the pipeline
18
- * @returns A new ContextPipeline instance with no transformations
19
- */
20
- static create() {
21
- return new ContextPipelineImpl({ action: async (context) => context });
22
- }
23
- }
24
- /**
25
- * Implementation of the `ContextPipeline` logic. This hides implementation details such as the `middleware` and `parent`
26
- * properties from the `ContextPipeline` interface, making type checking more reliable.
27
- */
28
- class ContextPipelineImpl extends ContextPipeline {
29
- middleware;
30
- parent;
31
- constructor(middleware, parent) {
32
- super();
33
- this.middleware = middleware;
34
- this.parent = parent;
35
- }
36
- /**
37
- * @inheritdoc
38
- */
39
- compose(middleware) {
40
- return new ContextPipelineImpl(middleware, this);
41
- }
42
- chain(other) {
43
- const otherMiddlewares = Array.from(other.middlewareChain()).reverse();
44
- let result = this;
45
- for (const middleware of otherMiddlewares) {
46
- result = result.compose(middleware);
47
- }
48
- return result;
49
- }
50
- *middlewareChain() {
51
- let step = this;
52
- while (step !== undefined) {
53
- yield step.middleware;
54
- step = step.parent;
55
- }
56
- }
57
- /**
58
- * @inheritdoc
59
- */
60
- async call(crawlingContext, finalContextConsumer) {
61
- const middlewares = Array.from(this.middlewareChain()).reverse();
62
- const cleanupStack = [];
63
- let consumerException;
64
- try {
65
- for (const { action, cleanup } of middlewares) {
66
- try {
67
- const contextExtension = await action(crawlingContext);
68
- const extensionNames = [
69
- ...Object.getOwnPropertyNames(contextExtension),
70
- ...Object.getOwnPropertySymbols(contextExtension),
71
- ];
72
- for (const key of extensionNames) {
73
- try {
74
- if (Object.getOwnPropertyDescriptor(crawlingContext, key)?.configurable !== false) {
75
- Object.defineProperty(crawlingContext, key, Object.getOwnPropertyDescriptor(contextExtension, key));
76
- }
77
- }
78
- catch (error) {
79
- serviceLocator
80
- .getLogger()
81
- .debug(`Context pipeline failed to define property ${key.toString()}:`, error);
82
- }
83
- }
84
- if (cleanup) {
85
- cleanupStack.push(cleanup);
86
- }
87
- }
88
- catch (exception) {
89
- if (exception instanceof SessionError) {
90
- throw exception; // Session errors are re-thrown as-is
91
- }
92
- if (exception instanceof ContextPipelineInterruptedError) {
93
- throw exception;
94
- }
95
- throw new ContextPipelineInitializationError(exception);
96
- }
97
- }
98
- try {
99
- await finalContextConsumer(crawlingContext);
100
- }
101
- catch (exception) {
102
- if (exception instanceof SessionError) {
103
- consumerException = exception;
104
- throw exception; // Session errors are re-thrown as-is
105
- }
106
- consumerException = exception;
107
- throw new RequestHandlerError(exception);
108
- }
109
- }
110
- finally {
111
- try {
112
- for (const cleanup of cleanupStack.reverse()) {
113
- await cleanup(crawlingContext, consumerException);
114
- }
115
- }
116
- catch (exception) {
117
- // eslint-disable-next-line no-unsafe-finally
118
- throw new ContextPipelineCleanupError(exception);
119
- }
120
- }
121
- }
122
- }