@crawlee/http 4.0.0-beta.99 → 4.0.0-rc.0

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.
@@ -2,8 +2,9 @@ import type { BasicCrawlerOptions, ConcurrencySystem, ConcurrencySystemOptions,
2
2
  import { BasicCrawler, ContextPipeline } from '@crawlee/basic';
3
3
  import { type LoadedRequest } from '@crawlee/core';
4
4
  import type { Awaitable, Dictionary } from '@crawlee/types';
5
- import { type CheerioRoot } from '@crawlee/utils';
5
+ import { type CheerioRoot } from '@crawlee/utils/internal';
6
6
  import type { JsonValue } from 'type-fest';
7
+ import { z } from 'zod';
7
8
  /**
8
9
  * A higher starting concurrency and a relaxed event loop signal, since HTTP-only crawling barely touches the event
9
10
  * loop. {@link HttpCrawler} folds these into the {@link ConcurrencySystem} it builds by default.
@@ -19,7 +20,7 @@ export declare const HTTP_OPTIMIZED_CONCURRENCY_SYSTEM_OPTIONS: ConcurrencySyste
19
20
  export type HttpErrorHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
20
21
  JSONData extends JsonValue = any, // with default to Dictionary we cant use a typed router in untyped crawler
21
22
  ContextExtension = Dictionary<never>> = ErrorHandler<CrawlingContext, HttpCrawlingContext<UserData, JSONData> & ContextExtension>;
22
- export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext = InternalHttpCrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> extends BasicCrawlerOptions<Context, ContextExtension, ExtendedContext, Routes> {
23
+ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext = InternalHttpCrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>, StatisticStateExtension extends object = {}> extends BasicCrawlerOptions<Context, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
23
24
  /**
24
25
  * Timeout for the whole navigation phase, given in seconds. A single window shared by the
25
26
  * `preNavigationHooks`, the navigation (the HTTP request to the resource), and the `postNavigationHooks` -
@@ -29,9 +30,14 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
29
30
  */
30
31
  navigationTimeoutSecs?: number;
31
32
  /**
32
- * If set to true, SSL certificate errors will be ignored.
33
+ * If set to `true`, TLS/SSL certificate errors are ignored. Forwarded to the HTTP client as
34
+ * {@link SendRequestOptions.ignoreTlsErrors|`ignoreTlsErrors`} on every navigation request, so custom
35
+ * {@link BaseHttpClient} implementations should honor that flag (the built-in impit and got-scraping
36
+ * clients do; the native fetch fallback cannot disable TLS verification and warns instead).
37
+ *
38
+ * @default true
33
39
  */
34
- ignoreSslErrors?: boolean;
40
+ ignoreTlsErrors?: boolean;
35
41
  /**
36
42
  * Async functions that are sequentially evaluated before the navigation. Good for setting additional cookies
37
43
  * or browser properties before navigation. The function accepts one parameter `crawlingContext`,
@@ -53,7 +59,7 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
53
59
  * ]
54
60
  * ```
55
61
  */
56
- preNavigationHooks?: InternalHttpHook<CrawlingContext, ContextExtension>[];
62
+ preNavigationHooks?: InternalHttpHook<CrawlingContext<any>, ContextExtension>[];
57
63
  /**
58
64
  * Async functions that are sequentially evaluated after the navigation. Good for checking if the navigation was successful.
59
65
  * The function accepts `crawlingContext` as the only parameter.
@@ -241,107 +247,122 @@ JSONData extends JsonValue = any> = RequestHandler<HttpCrawlingContext<UserData,
241
247
  * ```
242
248
  * @category Crawlers
243
249
  */
244
- export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any, any> = InternalHttpCrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> extends BasicCrawler<Context, ContextExtension, ExtendedContext, Routes> {
245
- private preNavigationHooks;
246
- private postNavigationHooks;
247
- private saveResponseCookies;
248
- private navigationTimeoutMillis;
249
- private ignoreSslErrors;
250
- private suggestResponseEncoding?;
251
- private forceResponseEncoding?;
252
- private readonly supportedMimeTypes;
250
+ export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any, any> = InternalHttpCrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>, StatisticStateExtension extends object = {}> extends BasicCrawler<Context, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
251
+ #private;
253
252
  protected static optionsShape: {
254
- // @ts-ignore optional peer dependency or compatibility with es2022
255
- navigationTimeoutSecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
256
- // @ts-ignore optional peer dependency or compatibility with es2022
257
- ignoreSslErrors: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
258
- // @ts-ignore optional peer dependency or compatibility with es2022
259
- additionalMimeTypes: import("ow").ArrayPredicate<string>;
260
- // @ts-ignore optional peer dependency or compatibility with es2022
261
- suggestResponseEncoding: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
262
- // @ts-ignore optional peer dependency or compatibility with es2022
263
- forceResponseEncoding: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
264
- // @ts-ignore optional peer dependency or compatibility with es2022
265
- saveResponseCookies: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
266
- // @ts-ignore optional peer dependency or compatibility with es2022
267
- preNavigationHooks: import("ow").ArrayPredicate<unknown> & import("ow").BasePredicate<unknown[] | undefined>;
268
- // @ts-ignore optional peer dependency or compatibility with es2022
269
- postNavigationHooks: import("ow").ArrayPredicate<unknown> & import("ow").BasePredicate<unknown[] | undefined>;
270
- // @ts-ignore optional peer dependency or compatibility with es2022
271
- contextPipelineBuilder: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
272
- // @ts-ignore optional peer dependency or compatibility with es2022
273
- extendContext: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
274
- // @ts-ignore optional peer dependency or compatibility with es2022
275
- requestList: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
276
- // @ts-ignore optional peer dependency or compatibility with es2022
277
- requestQueue: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
278
- // @ts-ignore optional peer dependency or compatibility with es2022
279
- requestHandler: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
280
- // @ts-ignore optional peer dependency or compatibility with es2022
281
- requestHandlerTimeoutSecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
282
- // @ts-ignore optional peer dependency or compatibility with es2022
283
- errorHandler: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
284
- // @ts-ignore optional peer dependency or compatibility with es2022
285
- failedRequestHandler: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
286
- // @ts-ignore optional peer dependency or compatibility with es2022
287
- maxRequestRetries: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
288
- // @ts-ignore optional peer dependency or compatibility with es2022
289
- sameDomainDelaySecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
290
- // @ts-ignore optional peer dependency or compatibility with es2022
291
- maxRequestsPerCrawl: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
292
- // @ts-ignore optional peer dependency or compatibility with es2022
293
- maxCrawlDepth: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
294
- // @ts-ignore optional peer dependency or compatibility with es2022
295
- taskLoopOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
296
- // @ts-ignore optional peer dependency or compatibility with es2022
297
- concurrencySystem: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
298
- // @ts-ignore optional peer dependency or compatibility with es2022
299
- sessionPool: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
300
- // @ts-ignore optional peer dependency or compatibility with es2022
301
- proxyConfiguration: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
302
- // @ts-ignore optional peer dependency or compatibility with es2022
303
- statusMessageLoggingInterval: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
304
- // @ts-ignore optional peer dependency or compatibility with es2022
305
- statusMessageCallback: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
306
- // @ts-ignore optional peer dependency or compatibility with es2022
307
- additionalHttpErrorStatusCodes: import("ow").ArrayPredicate<number>;
308
- // @ts-ignore optional peer dependency or compatibility with es2022
309
- ignoreHttpErrorStatusCodes: import("ow").ArrayPredicate<number>;
310
- // @ts-ignore optional peer dependency or compatibility with es2022
311
- blockedStatusCodes: import("ow").ArrayPredicate<number>;
312
- // @ts-ignore optional peer dependency or compatibility with es2022
313
- retryOnBlocked: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
314
- // @ts-ignore optional peer dependency or compatibility with es2022
315
- respectRobotsTxtFile: import("ow").AnyPredicate<boolean | object>;
316
- // @ts-ignore optional peer dependency or compatibility with es2022
317
- onSkippedRequest: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
318
- // @ts-ignore optional peer dependency or compatibility with es2022
319
- httpClient: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
320
- // @ts-ignore optional peer dependency or compatibility with es2022
321
- configuration: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
322
- // @ts-ignore optional peer dependency or compatibility with es2022
323
- storageBackend: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
324
- // @ts-ignore optional peer dependency or compatibility with es2022
325
- eventManager: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
326
- // @ts-ignore optional peer dependency or compatibility with es2022
327
- logger: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
328
- // @ts-ignore optional peer dependency or compatibility with es2022
329
- minConcurrency: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
330
- // @ts-ignore optional peer dependency or compatibility with es2022
331
- maxConcurrency: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
332
- // @ts-ignore optional peer dependency or compatibility with es2022
333
- maxRequestsPerMinute: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
334
- // @ts-ignore optional peer dependency or compatibility with es2022
335
- keepAlive: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
336
- // @ts-ignore optional peer dependency or compatibility with es2022
337
- statisticsOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
338
- // @ts-ignore optional peer dependency or compatibility with es2022
339
- id: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
253
+ navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
254
+ ignoreTlsErrors: z.ZodDefault<z.ZodBoolean>;
255
+ additionalMimeTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
256
+ suggestResponseEncoding: z.ZodOptional<z.ZodString>;
257
+ forceResponseEncoding: z.ZodOptional<z.ZodString>;
258
+ saveResponseCookies: z.ZodDefault<z.ZodBoolean>;
259
+ preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
260
+ postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
261
+ contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
262
+ extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
263
+ requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
264
+ requestQueue: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
265
+ requestManager: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
266
+ requestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
267
+ requestHandlerTimeoutSecs: z.ZodOptional<z.ZodCustom<number, number>>;
268
+ errorHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
269
+ failedRequestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
270
+ maxRequestRetries: z.ZodDefault<z.ZodCustom<number, number>>;
271
+ sameDomainDelaySecs: z.ZodDefault<z.ZodCustom<number, number>>;
272
+ maxRequestsPerCrawl: z.ZodOptional<z.ZodCustom<number, number>>;
273
+ maxCrawlDepth: z.ZodOptional<z.ZodCustom<number, number>>;
274
+ taskLoopOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
275
+ concurrencySystem: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
276
+ sessionPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
277
+ proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
278
+ statusMessageLoggingInterval: z.ZodDefault<z.ZodCustom<number, number>>;
279
+ statusMessageCallback: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
280
+ additionalHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
281
+ ignoreHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
282
+ blockedStatusCodes: z.ZodOptional<z.ZodArray<z.ZodCustom<number, number>>>;
283
+ retryOnBlocked: z.ZodDefault<z.ZodBoolean>;
284
+ respectRobotsTxtFile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<Dictionary, Dictionary>]>>;
285
+ transactionalStorage: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
286
+ requestQueue: z.ZodOptional<z.ZodEnum<{
287
+ deferred: "deferred";
288
+ writeThrough: "writeThrough";
289
+ }>>;
290
+ }, z.core.$strict>]>>;
291
+ onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
292
+ // @ts-ignore optional peer dependency or compatibility with es2022
293
+ httpClient: z.ZodOptional<z.ZodCustom<import("@crawlee/http-client").BaseHttpClient, import("@crawlee/http-client").BaseHttpClient>>;
294
+ // @ts-ignore optional peer dependency or compatibility with es2022
295
+ configuration: z.ZodOptional<z.ZodCustom<import("@crawlee/basic").Configuration, import("@crawlee/basic").Configuration>>;
296
+ storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
297
+ // @ts-ignore optional peer dependency or compatibility with es2022
298
+ eventManager: z.ZodOptional<z.ZodCustom<import("@crawlee/basic").EventManager, import("@crawlee/basic").EventManager>>;
299
+ logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
300
+ minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
301
+ maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
302
+ maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
303
+ keepAlive: z.ZodOptional<z.ZodBoolean>;
304
+ statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
305
+ id: z.ZodOptional<z.ZodString>;
340
306
  };
307
+ protected static optionsSchema: z.ZodObject<{
308
+ navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
309
+ ignoreTlsErrors: z.ZodDefault<z.ZodBoolean>;
310
+ additionalMimeTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
311
+ suggestResponseEncoding: z.ZodOptional<z.ZodString>;
312
+ forceResponseEncoding: z.ZodOptional<z.ZodString>;
313
+ saveResponseCookies: z.ZodDefault<z.ZodBoolean>;
314
+ preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
315
+ postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
316
+ contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
317
+ extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
318
+ requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
319
+ requestQueue: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
320
+ requestManager: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
321
+ requestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
322
+ requestHandlerTimeoutSecs: z.ZodOptional<z.ZodCustom<number, number>>;
323
+ errorHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
324
+ failedRequestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
325
+ maxRequestRetries: z.ZodDefault<z.ZodCustom<number, number>>;
326
+ sameDomainDelaySecs: z.ZodDefault<z.ZodCustom<number, number>>;
327
+ maxRequestsPerCrawl: z.ZodOptional<z.ZodCustom<number, number>>;
328
+ maxCrawlDepth: z.ZodOptional<z.ZodCustom<number, number>>;
329
+ taskLoopOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
330
+ concurrencySystem: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
331
+ sessionPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
332
+ proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
333
+ statusMessageLoggingInterval: z.ZodDefault<z.ZodCustom<number, number>>;
334
+ statusMessageCallback: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
335
+ additionalHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
336
+ ignoreHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
337
+ blockedStatusCodes: z.ZodOptional<z.ZodArray<z.ZodCustom<number, number>>>;
338
+ retryOnBlocked: z.ZodDefault<z.ZodBoolean>;
339
+ respectRobotsTxtFile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<Dictionary, Dictionary>]>>;
340
+ transactionalStorage: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
341
+ requestQueue: z.ZodOptional<z.ZodEnum<{
342
+ deferred: "deferred";
343
+ writeThrough: "writeThrough";
344
+ }>>;
345
+ }, z.core.$strict>]>>;
346
+ onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
347
+ // @ts-ignore optional peer dependency or compatibility with es2022
348
+ httpClient: z.ZodOptional<z.ZodCustom<import("@crawlee/http-client").BaseHttpClient, import("@crawlee/http-client").BaseHttpClient>>;
349
+ // @ts-ignore optional peer dependency or compatibility with es2022
350
+ configuration: z.ZodOptional<z.ZodCustom<import("@crawlee/basic").Configuration, import("@crawlee/basic").Configuration>>;
351
+ storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
352
+ // @ts-ignore optional peer dependency or compatibility with es2022
353
+ eventManager: z.ZodOptional<z.ZodCustom<import("@crawlee/basic").EventManager, import("@crawlee/basic").EventManager>>;
354
+ logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
355
+ minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
356
+ maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
357
+ maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
358
+ keepAlive: z.ZodOptional<z.ZodBoolean>;
359
+ statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
360
+ id: z.ZodOptional<z.ZodString>;
361
+ }, z.core.$strict>;
341
362
  /**
342
363
  * All `HttpCrawlerOptions` parameters are passed via an options object.
343
364
  */
344
- constructor(options?: HttpCrawlerOptions<Context, ContextExtension, ExtendedContext> & RequireContextPipeline<InternalHttpCrawlingContext, Context>);
365
+ constructor(options?: HttpCrawlerOptions<Context, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> & RequireContextPipeline<InternalHttpCrawlingContext, Context>);
345
366
  protected getNavigationTimeoutMillis(): number;
346
367
  /**
347
368
  * Folds {@link HTTP_OPTIMIZED_CONCURRENCY_SYSTEM_OPTIONS} into the default system, keeping the user's
@@ -379,11 +400,11 @@ export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any
379
400
  * Handles timeout request
380
401
  */
381
402
  private handleRequestTimeout;
382
- private _abortDownloadOfBody;
403
+ private abortDownloadOfBody;
383
404
  /**
384
405
  * @internal wraps public utility for mocking purposes
385
406
  */
386
- private _requestAsBrowser;
407
+ private requestAsBrowser;
387
408
  }
388
409
  /**
389
410
  * Creates new {@link Router} instance that works based on request labels.
@@ -1,12 +1,12 @@
1
1
  import { Readable } from 'node:stream';
2
2
  import util from 'node:util';
3
3
  import { BasicCrawler, ContextPipeline, NavigationSkippedError, remainingNavigationWindowMillis, RequestState, Router, SessionError, } from '@crawlee/basic';
4
- import { getCookiesFromResponse } from '@crawlee/core';
4
+ import { RequestThrottledError, getCookiesFromResponse, parseArgument, schemas, } from '@crawlee/core';
5
5
  import { ResponseWithUrl } from '@crawlee/http-client';
6
- import { RETRY_CSS_SELECTORS } from '@crawlee/utils';
6
+ import { RETRY_CSS_SELECTORS } from '@crawlee/utils/internal';
7
7
  import contentTypeParser from 'content-type';
8
8
  import iconv from 'iconv-lite';
9
- import ow from 'ow';
9
+ import { z } from 'zod';
10
10
  import { addTimeoutToPromise, storage, TimeoutError, tryCancel } from '@apify/timeout';
11
11
  import { extractCharsetFromHtmlBytes, parseContentTypeFromResponse, processHttpRequestOptions } from './utils.js';
12
12
  /**
@@ -109,60 +109,60 @@ export class HttpCrawler extends BasicCrawler {
109
109
  // extension-aware for consumer DX, but internally the pipeline composes hooks against the
110
110
  // concrete crawling context, which does not statically carry `ContextExtension`. The members
111
111
  // added by `extendContext` are present at runtime regardless.
112
- preNavigationHooks;
113
- postNavigationHooks;
114
- saveResponseCookies;
115
- navigationTimeoutMillis;
116
- ignoreSslErrors;
117
- suggestResponseEncoding;
118
- forceResponseEncoding;
119
- supportedMimeTypes;
112
+ #preNavigationHooks;
113
+ #postNavigationHooks;
114
+ #saveResponseCookies;
115
+ #navigationTimeoutMillis;
116
+ #ignoreTlsErrors;
117
+ #suggestResponseEncoding;
118
+ #forceResponseEncoding;
119
+ #supportedMimeTypes;
120
120
  static optionsShape = {
121
121
  ...BasicCrawler.optionsShape,
122
- navigationTimeoutSecs: ow.optional.number,
123
- ignoreSslErrors: ow.optional.boolean,
124
- additionalMimeTypes: ow.optional.array.ofType(ow.string),
125
- suggestResponseEncoding: ow.optional.string,
126
- forceResponseEncoding: ow.optional.string,
127
- saveResponseCookies: ow.optional.boolean,
128
- preNavigationHooks: ow.optional.array,
129
- postNavigationHooks: ow.optional.array,
122
+ navigationTimeoutSecs: schemas.anyNumber.default(30),
123
+ ignoreTlsErrors: z.boolean().default(true),
124
+ additionalMimeTypes: schemas.arrayOf(z.string(), 'strings').default(() => []),
125
+ suggestResponseEncoding: z.string().optional(),
126
+ forceResponseEncoding: z.string().optional(),
127
+ saveResponseCookies: z.boolean().default(true),
128
+ preNavigationHooks: schemas.anyArray.default(() => []),
129
+ postNavigationHooks: schemas.anyArray.default(() => []),
130
130
  };
131
+ static optionsSchema = z.strictObject(HttpCrawler.optionsShape);
131
132
  /**
132
133
  * All `HttpCrawlerOptions` parameters are passed via an options object.
133
134
  */
134
135
  constructor(options = {}) {
135
- ow(options, 'HttpCrawlerOptions', ow.object.exactShape(HttpCrawler.optionsShape));
136
- const { navigationTimeoutSecs = 30, ignoreSslErrors = true, additionalMimeTypes = [], suggestResponseEncoding, forceResponseEncoding, saveResponseCookies = true, preNavigationHooks = [], postNavigationHooks = [],
136
+ const { navigationTimeoutSecs, ignoreTlsErrors, additionalMimeTypes, suggestResponseEncoding, forceResponseEncoding, saveResponseCookies, preNavigationHooks, postNavigationHooks,
137
137
  // BasicCrawler
138
- contextPipelineBuilder, ...basicCrawlerOptions } = options;
138
+ contextPipelineBuilder, ...basicCrawlerOptions } = parseArgument(options, HttpCrawler.optionsSchema, 'HttpCrawlerOptions');
139
139
  super({
140
140
  ...basicCrawlerOptions,
141
141
  contextPipelineBuilder: contextPipelineBuilder ??
142
142
  (() => this.buildContextPipeline()),
143
143
  });
144
- this.supportedMimeTypes = new Set([...HTML_AND_XML_MIME_TYPES, APPLICATION_JSON_MIME_TYPE]);
144
+ this.#supportedMimeTypes = new Set([...HTML_AND_XML_MIME_TYPES, APPLICATION_JSON_MIME_TYPE]);
145
145
  if (additionalMimeTypes.length)
146
146
  this.extendSupportedMimeTypes(additionalMimeTypes);
147
147
  if (suggestResponseEncoding && forceResponseEncoding) {
148
148
  this.log.warning('Both forceResponseEncoding and suggestResponseEncoding options are set. Using forceResponseEncoding.');
149
149
  }
150
- this.navigationTimeoutMillis = navigationTimeoutSecs * 1000;
151
- this.ignoreSslErrors = ignoreSslErrors;
152
- this.suggestResponseEncoding = suggestResponseEncoding;
153
- this.forceResponseEncoding = forceResponseEncoding;
150
+ this.#navigationTimeoutMillis = navigationTimeoutSecs * 1000;
151
+ this.#ignoreTlsErrors = ignoreTlsErrors;
152
+ this.#suggestResponseEncoding = suggestResponseEncoding;
153
+ this.#forceResponseEncoding = forceResponseEncoding;
154
154
  // Cast away the extension-aware option types to the base internal storage types (see the field
155
155
  // declarations above). This is sound - the hooks only ever receive the base context plus the
156
156
  // members `extendContext` added at runtime.
157
- this.preNavigationHooks = preNavigationHooks;
158
- this.postNavigationHooks = [
159
- ({ request, response }) => this._abortDownloadOfBody(request, response),
157
+ this.#preNavigationHooks = preNavigationHooks;
158
+ this.#postNavigationHooks = [
159
+ ({ request, response }) => this.abortDownloadOfBody(request, response),
160
160
  ...postNavigationHooks,
161
161
  ];
162
- this.saveResponseCookies = saveResponseCookies;
162
+ this.#saveResponseCookies = saveResponseCookies;
163
163
  }
164
164
  getNavigationTimeoutMillis() {
165
- return this.navigationTimeoutMillis;
165
+ return this.#navigationTimeoutMillis;
166
166
  }
167
167
  /**
168
168
  * Folds {@link HTTP_OPTIMIZED_CONCURRENCY_SYSTEM_OPTIONS} into the default system, keeping the user's
@@ -185,9 +185,9 @@ export class HttpCrawler extends BasicCrawler {
185
185
  // A single navigation window covers the pre-navigation hooks, the navigation, and the post-navigation
186
186
  // hooks: the whole phase shares one `navigationTimeoutSecs` budget, so a slow hook eats into the same
187
187
  // window the navigation uses instead of each step being timed on its own.
188
- const navigationTimedOut = `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`;
188
+ const navigationTimedOut = `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`;
189
189
  const windowGuard = (step) => skipGuard(async (ctx) => {
190
- const remaining = remainingNavigationWindowMillis(ctx, this.navigationTimeoutMillis);
190
+ const remaining = remainingNavigationWindowMillis(ctx, this.#navigationTimeoutMillis);
191
191
  if (remaining <= 0) {
192
192
  throw new TimeoutError(navigationTimedOut);
193
193
  }
@@ -196,11 +196,11 @@ export class HttpCrawler extends BasicCrawler {
196
196
  let pipeline = ContextPipeline.create().compose({
197
197
  action: this.prepareHttpRequest.bind(this),
198
198
  });
199
- for (const hook of this.preNavigationHooks) {
199
+ for (const hook of this.#preNavigationHooks) {
200
200
  pipeline = pipeline.compose(windowGuard(hook));
201
201
  }
202
202
  let pipelineWithNavigation = pipeline.compose(skipGuard(this.makeHttpRequest.bind(this)));
203
- for (const hook of this.postNavigationHooks) {
203
+ for (const hook of this.#postNavigationHooks) {
204
204
  pipelineWithNavigation = pipelineWithNavigation.compose(windowGuard(hook));
205
205
  }
206
206
  return pipelineWithNavigation
@@ -234,7 +234,7 @@ export class HttpCrawler extends BasicCrawler {
234
234
  // Bound the request by whatever is left of the shared navigation window (the pre-navigation hooks may
235
235
  // have already spent part of it), so it produces a clean navigation-timeout error rather than the raw
236
236
  // client abort.
237
- const httpResponse = await addTimeoutToPromise(async () => this.requestFunction({ request, session, proxyUrl }), Math.max(1, remainingNavigationWindowMillis(crawlingContext, this.navigationTimeoutMillis)), `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
237
+ const httpResponse = await addTimeoutToPromise(async () => this.requestFunction({ request, session, proxyUrl }), Math.max(1, remainingNavigationWindowMillis(crawlingContext, this.#navigationTimeoutMillis)), `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
238
238
  tryCancel();
239
239
  request.loadedUrl = httpResponse?.url;
240
240
  request.state = RequestState.AFTER_NAV;
@@ -261,14 +261,25 @@ export class HttpCrawler extends BasicCrawler {
261
261
  };
262
262
  }
263
263
  tryCancel();
264
+ // Before `parseResponse`, which throws for error status codes - a 429 the user opted into treating as an
265
+ // error is still a rate limit the domain should back off from.
266
+ if (crawlingContext.response.status === 429) {
267
+ const retryAfter = crawlingContext.response.headers.get('retry-after');
268
+ if (this.recordDomainRateLimit(crawlingContext.request.url, retryAfter)) {
269
+ // This is the one path that never reads the body, so cancel it to release the connection
270
+ // rather than leaving it to the garbage collector.
271
+ await crawlingContext.response.body?.cancel().catch(() => { });
272
+ throw new RequestThrottledError(`${crawlingContext.request.url} responded with 429.`);
273
+ }
274
+ }
264
275
  // Reading the body is still part of the navigation, so it draws from the same shared window: on a server
265
276
  // that streams the body slowly the request completes (headers arrive) but the body read would otherwise
266
277
  // run unbounded. `extendTimeout` from a post-navigation hook has already pushed this deadline out if asked.
267
- const remaining = remainingNavigationWindowMillis(crawlingContext, this.navigationTimeoutMillis);
278
+ const remaining = remainingNavigationWindowMillis(crawlingContext, this.#navigationTimeoutMillis);
268
279
  if (remaining <= 0) {
269
- throw new TimeoutError(`Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
280
+ throw new TimeoutError(`Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
270
281
  }
271
- const parsed = await addTimeoutToPromise(async () => this.parseResponse(crawlingContext.request, crawlingContext.response), remaining, `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
282
+ const parsed = await addTimeoutToPromise(async () => this.parseResponse(crawlingContext.request, crawlingContext.response), remaining, `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
272
283
  tryCancel();
273
284
  const response = parsed.response;
274
285
  const contentType = parsed.contentType;
@@ -287,14 +298,16 @@ export class HttpCrawler extends BasicCrawler {
287
298
  }
288
299
  return $;
289
300
  };
290
- this._throwOnBlockedRequest(response.status);
291
- if (this.saveResponseCookies) {
301
+ this.throwOnBlockedRequest(response.status);
302
+ if (this.#saveResponseCookies) {
292
303
  try {
293
304
  for (const cookie of getCookiesFromResponse(response)) {
294
305
  if (!cookie)
295
306
  continue;
296
307
  try {
297
- crawlingContext.session.cookieJar.setCookieSync(cookie, response.url, { ignoreError: false });
308
+ await crawlingContext.session.cookieJar.setCookie(cookie, response.url, {
309
+ ignoreError: false,
310
+ });
298
311
  }
299
312
  catch (e) {
300
313
  this.log.debug(`Could not set cookie: ${e.message}`);
@@ -347,7 +360,7 @@ export class HttpCrawler extends BasicCrawler {
347
360
  async requestFunction({ request, session, proxyUrl }) {
348
361
  const opts = this.getRequestOptions(request, session, proxyUrl);
349
362
  try {
350
- return await this._requestAsBrowser(opts, session);
363
+ return await this.requestAsBrowser(opts, session);
351
364
  }
352
365
  catch (e) {
353
366
  if (e instanceof Error && e.constructor.name === 'TimeoutError') {
@@ -355,7 +368,7 @@ export class HttpCrawler extends BasicCrawler {
355
368
  return new Response(); // this will never happen, as handleRequestTimeout always throws
356
369
  }
357
370
  if (this.isProxyError(e)) {
358
- throw new SessionError(this._getMessageFromError(e));
371
+ throw new SessionError(this.getMessageFromError(e));
359
372
  }
360
373
  else {
361
374
  throw e;
@@ -371,7 +384,7 @@ export class HttpCrawler extends BasicCrawler {
371
384
  const { response: reencodedResponse, encoding } = this.encodeResponse(request, response, charset);
372
385
  const contentType = { type, encoding };
373
386
  if (status >= 400 && status <= 599) {
374
- this.stats.registerStatusCode(status);
387
+ this.statistics.registerStatusCode(status);
375
388
  }
376
389
  if (this.isErrorStatusCode(status)) {
377
390
  const body = await reencodedResponse.text(); // TODO - this always uses UTF-8 (see https://developer.mozilla.org/en-US/docs/Web/API/Request/text)
@@ -391,10 +404,10 @@ export class HttpCrawler extends BasicCrawler {
391
404
  throw new Error(`${status} - Internal Server Error: ${body.slice(0, 100)}`);
392
405
  }
393
406
  else if (HTML_AND_XML_MIME_TYPES.includes(type)) {
394
- if (!charset && !this.forceResponseEncoding) {
407
+ if (!charset && !this.#forceResponseEncoding) {
395
408
  const rawBytes = Buffer.from(await response.arrayBuffer());
396
409
  const metaCharset = extractCharsetFromHtmlBytes(rawBytes);
397
- const charsetToUse = metaCharset ?? this.suggestResponseEncoding ?? 'utf-8';
410
+ const charsetToUse = metaCharset ?? this.#suggestResponseEncoding ?? 'utf-8';
398
411
  const body = iconv.encodingExists(charsetToUse)
399
412
  ? iconv.decode(rawBytes, charsetToUse)
400
413
  : rawBytes.toString('utf8');
@@ -419,35 +432,25 @@ export class HttpCrawler extends BasicCrawler {
419
432
  url: request.url,
420
433
  method: request.method,
421
434
  proxyUrl,
422
- timeout: this.navigationTimeoutMillis,
435
+ timeout: this.#navigationTimeoutMillis,
423
436
  sessionToken: session,
424
437
  headers: request.headers,
425
- https: {
426
- rejectUnauthorized: !this.ignoreSslErrors,
427
- },
428
438
  body: undefined,
429
439
  };
430
440
  if (requestOptions.headers?.cookie || requestOptions.headers?.Cookie) {
431
- requestOptions.headers.Cookie = this._getCookieHeaderFromRequest(request);
441
+ requestOptions.headers.Cookie = this.getCookieHeaderFromRequest(request);
432
442
  delete requestOptions.headers.cookie;
433
443
  }
434
- // Disable SSL verification for MITM proxies
435
- if (session.proxyInfo?.ignoreTlsErrors) {
436
- requestOptions.https = {
437
- ...requestOptions.https,
438
- rejectUnauthorized: false,
439
- };
440
- }
441
444
  if (/PATCH|POST|PUT/.test(request.method))
442
445
  requestOptions.body = request.payload ?? '';
443
446
  return requestOptions;
444
447
  }
445
448
  encodeResponse(request, response, encoding) {
446
- if (this.forceResponseEncoding) {
447
- encoding = this.forceResponseEncoding;
449
+ if (this.#forceResponseEncoding) {
450
+ encoding = this.#forceResponseEncoding;
448
451
  }
449
- else if (!encoding && this.suggestResponseEncoding) {
450
- encoding = this.suggestResponseEncoding;
452
+ else if (!encoding && this.#suggestResponseEncoding) {
453
+ encoding = this.#suggestResponseEncoding;
451
454
  }
452
455
  // Fall back to utf-8 if we still don't have encoding.
453
456
  const utf8 = 'utf8';
@@ -481,12 +484,12 @@ export class HttpCrawler extends BasicCrawler {
481
484
  extendSupportedMimeTypes(additionalMimeTypes) {
482
485
  for (const mimeType of additionalMimeTypes) {
483
486
  if (mimeType === '*/*') {
484
- this.supportedMimeTypes.add(mimeType);
487
+ this.#supportedMimeTypes.add(mimeType);
485
488
  continue;
486
489
  }
487
490
  try {
488
491
  const parsedType = contentTypeParser.parse(mimeType);
489
- this.supportedMimeTypes.add(parsedType.type);
492
+ this.#supportedMimeTypes.add(parsedType.type);
490
493
  }
491
494
  catch (err) {
492
495
  throw new Error(`Can not parse mime type ${mimeType} from "options.additionalMimeTypes".`);
@@ -498,22 +501,22 @@ export class HttpCrawler extends BasicCrawler {
498
501
  */
499
502
  handleRequestTimeout(session) {
500
503
  session.markBad();
501
- throw new Error(`Request timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
504
+ throw new Error(`Request timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
502
505
  }
503
- _abortDownloadOfBody(request, response) {
506
+ abortDownloadOfBody(request, response) {
504
507
  const { status } = response;
505
508
  const { type } = parseContentTypeFromResponse(response);
506
509
  const isTransientContentType = status >= 500 || this.blockedStatusCodes.has(status);
507
- if (!this.supportedMimeTypes.has(type) && !this.supportedMimeTypes.has('*/*') && !isTransientContentType) {
510
+ if (!this.#supportedMimeTypes.has(type) && !this.#supportedMimeTypes.has('*/*') && !isTransientContentType) {
508
511
  request.noRetry = true;
509
512
  throw new Error(`Resource ${request.url} served Content-Type ${type}, ` +
510
- `but only ${Array.from(this.supportedMimeTypes).join(', ')} are allowed. Skipping resource.`);
513
+ `but only ${Array.from(this.#supportedMimeTypes).join(', ')} are allowed. Skipping resource.`);
511
514
  }
512
515
  }
513
516
  /**
514
517
  * @internal wraps public utility for mocking purposes
515
518
  */
516
- _requestAsBrowser = async (options, session) => {
519
+ requestAsBrowser = async (options, session) => {
517
520
  const opts = processHttpRequestOptions({
518
521
  ...options,
519
522
  responseType: 'text',
@@ -521,7 +524,7 @@ export class HttpCrawler extends BasicCrawler {
521
524
  // When saveResponseCookies is false, the response cookies must not mutate the
522
525
  // session jar. Reads still go through the session (so session.setCookie() in pre-nav
523
526
  // hooks keeps working) but a per-request clone is passed in so writes are discarded.
524
- const cookieJar = this.saveResponseCookies ? session.cookieJar : await session.cookieJar.clone();
527
+ const cookieJar = this.#saveResponseCookies ? session.cookieJar : await session.cookieJar.clone();
525
528
  // Bind the request to the shared navigation window instead of a fixed per-request timeout, so
526
529
  // `extendTimeout()` can push the deadline and a fixed `AbortSignal.timeout` won't fire on its own and
527
530
  // kill a lazily-read body mid-extension. This aborts the socket only during the header phase; the body
@@ -539,6 +542,7 @@ export class HttpCrawler extends BasicCrawler {
539
542
  cookieJar,
540
543
  signal: cancelSignal,
541
544
  timeoutMillis: cancelSignal ? undefined : opts.timeout,
545
+ ignoreTlsErrors: this.#ignoreTlsErrors,
542
546
  });
543
547
  return response;
544
548
  };
@@ -1,9 +1,10 @@
1
1
  import { extname } from 'node:path';
2
2
  import { Readable } from 'node:stream';
3
- import { applySearchParams } from '@crawlee/utils';
3
+ import { applySearchParams, parseArgument } from '@crawlee/utils/internal';
4
4
  import contentTypeParser from 'content-type';
5
5
  import mime from 'mime-types';
6
- import ow, { ObjectPredicate } from 'ow';
6
+ import { z } from 'zod';
7
+ const responseWithUrlSchema = z.looseObject({ url: z.url(), headers: z.looseObject({}) });
7
8
  /**
8
9
  * Converts {@link HttpRequestOptions} to a {@link HttpRequest}.
9
10
  */
@@ -54,10 +55,7 @@ export function extractCharsetFromHtmlBytes(bytes) {
54
55
  * @param response HTTP response object
55
56
  */
56
57
  export function parseContentTypeFromResponse(response) {
57
- ow(response, ow.object.partialShape({
58
- url: ow.string.url,
59
- headers: new ObjectPredicate(),
60
- }));
58
+ parseArgument(response, responseWithUrlSchema);
61
59
  const { url, headers } = response;
62
60
  let parsedContentType;
63
61
  if (headers.get('content-type')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/http",
3
- "version": "4.0.0-beta.99",
3
+ "version": "4.0.0-rc.0",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -49,19 +49,19 @@
49
49
  "dependencies": {
50
50
  "@apify/timeout": "^0.4.4",
51
51
  "@apify/utilities": "^2.15.5",
52
- "@crawlee/basic": "4.0.0-beta.99",
53
- "@crawlee/core": "4.0.0-beta.99",
54
- "@crawlee/http-client": "4.0.0-beta.99",
55
- "@crawlee/types": "4.0.0-beta.99",
56
- "@crawlee/utils": "4.0.0-beta.99",
52
+ "@crawlee/basic": "4.0.0-rc.0",
53
+ "@crawlee/core": "4.0.0-rc.0",
54
+ "@crawlee/http-client": "4.0.0-rc.0",
55
+ "@crawlee/types": "4.0.0-rc.0",
56
+ "@crawlee/utils": "4.0.0-rc.0",
57
57
  "@types/content-type": "^1.1.8",
58
58
  "cheerio": "^1.0.0",
59
59
  "content-type": "^1.0.5",
60
60
  "iconv-lite": "^0.7.2",
61
61
  "mime-types": "^3.0.1",
62
- "ow": "^2.0.0",
63
62
  "tslib": "^2.8.1",
64
- "type-fest": "^4.41.0"
63
+ "type-fest": "^4.41.0",
64
+ "zod": "^4.4.3"
65
65
  },
66
66
  "lerna": {
67
67
  "command": {
@@ -70,5 +70,5 @@
70
70
  }
71
71
  }
72
72
  },
73
- "gitHead": "ad2748380941842bb10cff100f4b4caad92049e3"
73
+ "gitHead": "79ab33dacdacb83e0197e6516d145f3aceef80c7"
74
74
  }