@crawlee/core 4.0.0-beta.85 → 4.0.0-beta.86

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.
@@ -3,13 +3,65 @@ import type { ReadonlyDeep, SetRequired } from 'type-fest';
3
3
  import type { Configuration } from '../configuration.js';
4
4
  import type { EnqueueLinksOptions } from '../enqueue_links/enqueue_links.js';
5
5
  import type { CrawleeLogger } from '../log.js';
6
- import type { Request, Source } from '../request.js';
6
+ import type { Request, RequestOptions, Source } from '../request.js';
7
7
  import type { Dataset } from '../storages/dataset.js';
8
8
  import { KeyValueStore, type RecordOptions } from '../storages/key_value_store.js';
9
9
  import type { RequestQueueOperationOptions } from '../storages/request_queue.js';
10
10
  import type { StorageIdentifier } from '../storages/storage_instance_manager.js';
11
11
  /** @internal */
12
12
  export type IsAny<T> = 0 extends 1 & T ? true : false;
13
+ /**
14
+ * A request input (URL string, request-options object, or {@link Request}) whose `userData` is typed
15
+ * according to its `label`, based on a router's route map.
16
+ *
17
+ * When the route map is open (the default `Record<string, ...>`), this is just the regular loose
18
+ * {@link Source} input. When the map declares concrete labels, providing a `label` requires the matching
19
+ * `userData` shape and rejects labels not present in the map; unlabeled requests keep loose `userData`.
20
+ * @internal
21
+ */
22
+ export type LabeledSource<Routes extends Record<keyof Routes, Dictionary>> = string extends keyof Routes ? string | Source : string | Request | ({
23
+ requestsFromUrl?: string;
24
+ regex?: RegExp;
25
+ } & ({
26
+ [Label in keyof Routes & string]: Omit<Partial<RequestOptions<Routes[Label]>>, 'label'> & {
27
+ label: Label;
28
+ };
29
+ }[keyof Routes & string] | (Omit<Partial<RequestOptions>, 'label'> & {
30
+ label?: undefined;
31
+ })));
32
+ /**
33
+ * The iterable/array of {@link LabeledSource} inputs accepted by the label-aware `addRequests`/`run`
34
+ * methods of a crawler bound to a typed router.
35
+ * @internal
36
+ */
37
+ export type TypedRequestsLike<Routes extends Record<keyof Routes, Dictionary>> = AsyncIterable<LabeledSource<Routes>> | Iterable<LabeledSource<Routes>> | LabeledSource<Routes>[];
38
+ /**
39
+ * The label-aware `addRequests` method signature exposed on a request handler's context when the crawler is
40
+ * bound to a typed router. Mirrors {@link RestrictedCrawlingContext.addRequests} with typed sources.
41
+ * @internal
42
+ */
43
+ export type TypedContextAddRequests<Routes extends Record<keyof Routes, Dictionary>> = (requestsLike: ReadonlyDeep<LabeledSource<Routes>[]>, options?: ReadonlyDeep<RequestQueueOperationOptions>) => Promise<void>;
44
+ /**
45
+ * An `enqueueLinks`-options object with its `label`/`userData` retyped according to a router's route map: a
46
+ * declared `label` requires the matching `userData` shape (unknown labels are rejected), while unlabeled
47
+ * calls keep loose `userData`. Returns the options unchanged when the route map is open (the default).
48
+ */
49
+ type TypedEnqueueLinksOptions<Options, Routes extends Record<keyof Routes, Dictionary>> = string extends keyof Routes ? Options : Omit<Options, 'label' | 'userData'> & ({
50
+ [Label in keyof Routes & string]: {
51
+ label: Label;
52
+ userData?: Routes[Label];
53
+ };
54
+ }[keyof Routes & string] | {
55
+ label?: undefined;
56
+ userData?: Dictionary;
57
+ });
58
+ /**
59
+ * Transforms a context's existing `enqueueLinks` method so that the `label`/`userData` in its options follow
60
+ * the router's route map, while preserving everything else about the signature (argument optionality and
61
+ * return type, which differ between crawler types).
62
+ * @internal
63
+ */
64
+ export type TypedContextEnqueueLinks<EnqueueLinks, Routes extends Record<keyof Routes, Dictionary>> = EnqueueLinks extends (options?: infer Options) => infer Result ? (options?: TypedEnqueueLinksOptions<Options, Routes>) => Result : EnqueueLinks extends (options: infer Options) => infer Result ? (options: TypedEnqueueLinksOptions<Options, Routes>) => Result : EnqueueLinks;
13
65
  /** @internal */
14
66
  export type WithRequired<T, K extends keyof T> = T & {
15
67
  [P in K]-?: T[P];
@@ -187,3 +239,4 @@ export declare class RequestHandlerResult {
187
239
  private getKeyValueStoreChangedValue;
188
240
  private setKeyValueStoreChangedValue;
189
241
  }
242
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.85",
3
+ "version": "4.0.0-beta.86",
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"
@@ -53,9 +53,9 @@
53
53
  "@apify/pseudo_url": "^2.0.59",
54
54
  "@apify/timeout": "^0.3.2",
55
55
  "@apify/utilities": "^2.15.5",
56
- "@crawlee/fs-storage": "4.0.0-beta.85",
57
- "@crawlee/types": "4.0.0-beta.85",
58
- "@crawlee/utils": "4.0.0-beta.85",
56
+ "@crawlee/fs-storage": "4.0.0-beta.86",
57
+ "@crawlee/types": "4.0.0-beta.86",
58
+ "@crawlee/utils": "4.0.0-beta.86",
59
59
  "@sapphire/async-queue": "^1.5.5",
60
60
  "@sapphire/shapeshift": "^4.0.0",
61
61
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -79,5 +79,5 @@
79
79
  }
80
80
  }
81
81
  },
82
- "gitHead": "8d6d3cd9da3dc9cb0b7822b28eb317019b6806bc"
82
+ "gitHead": "c2f251426f3ccfcc6efc920e6d492be57beac43b"
83
83
  }
package/router.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Dictionary } from '@crawlee/types';
2
2
  import type { StandardSchemaV1 } from '@standard-schema/spec';
3
- import type { CrawlingContext, LoadedRequest, RestrictedCrawlingContext } from './crawlers/crawler_commons.js';
3
+ import type { CrawlingContext, LoadedRequest, RestrictedCrawlingContext, TypedContextAddRequests, TypedContextEnqueueLinks } from './crawlers/crawler_commons.js';
4
4
  import type { Request } from './request.js';
5
5
  import type { Awaitable } from './typedefs.js';
6
6
  /**
@@ -10,11 +10,18 @@ import type { Awaitable } from './typedefs.js';
10
10
  */
11
11
  export declare const defaultRoute: unique symbol;
12
12
  /**
13
- * The crawling context received by a route handler, with `request.userData` narrowed to `UserData`.
13
+ * The crawling context received by a route handler, with `request.userData` narrowed to `UserData`, and
14
+ * `addRequests`/`enqueueLinks` typed according to the router's route map (`Routes`) so that enqueuing a
15
+ * request under a declared label requires the matching `userData` shape.
14
16
  */
15
- export type RouterHandlerContext<Context, UserData extends Dictionary> = Omit<Context, 'request'> & {
17
+ export type RouterHandlerContext<Context, UserData extends Dictionary, Routes extends Record<keyof Routes, Dictionary>> = Omit<Context, 'request' | 'addRequests' | 'enqueueLinks'> & {
16
18
  request: LoadedRequest<Request<UserData>>;
17
- };
19
+ addRequests: TypedContextAddRequests<Routes>;
20
+ } & (Context extends {
21
+ enqueueLinks: infer EnqueueLinks;
22
+ } ? {
23
+ enqueueLinks: TypedContextEnqueueLinks<EnqueueLinks, Routes>;
24
+ } : {});
18
25
  /**
19
26
  * A map of request labels to a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType, …)
20
27
  * validating that label's `request.userData`. Pass it to {@link Router.create} or a `createXRouter`
@@ -24,14 +31,29 @@ export type RouterHandlerContext<Context, UserData extends Dictionary> = Omit<Co
24
31
  export type RouteSchemas = Record<string, StandardSchemaV1> & {
25
32
  [defaultRoute]?: StandardSchemaV1;
26
33
  };
34
+ /** Infers a label's `userData` type from its schema, falling back to a plain {@link Dictionary}. */
35
+ type SchemaUserData<Schema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<Schema> extends Dictionary ? StandardSchemaV1.InferOutput<Schema> : Dictionary;
27
36
  /**
28
- * Derives a route map (label → `userData` type) from a {@link RouteSchemas} map by inferring each
29
- * schema's output type. Outputs that are not object-shaped fall back to a plain {@link Dictionary}. The
30
- * {@link defaultRoute} schema drives runtime validation only, so it is excluded from the typed route map.
37
+ * Derives a route map (label → `userData` type) from a {@link RouteSchemas} map by inferring each schema's
38
+ * output type. Outputs that are not object-shaped fall back to a plain {@link Dictionary}. The
39
+ * {@link defaultRoute} schema is kept under its symbol key so {@link Router.addDefaultHandler} can pick it
40
+ * up; string labels (the ones {@link Router.addHandler} and the crawler-level typing accept) ignore it.
31
41
  */
32
42
  export type RoutesFromSchemas<Schemas extends RouteSchemas> = {
33
- [Label in Extract<keyof Schemas, string>]: StandardSchemaV1.InferOutput<Schemas[Label]> extends Dictionary ? StandardSchemaV1.InferOutput<Schemas[Label]> : Dictionary;
34
- };
43
+ [Label in Extract<keyof Schemas, string>]: SchemaUserData<Schemas[Label]>;
44
+ } & (Schemas extends {
45
+ [defaultRoute]: StandardSchemaV1;
46
+ } ? {
47
+ [defaultRoute]: SchemaUserData<Schemas[typeof defaultRoute]>;
48
+ } : {});
49
+ /**
50
+ * The `userData` type of the default route: inferred from the {@link defaultRoute} schema when the route map
51
+ * carries one, otherwise the provided `Fallback`.
52
+ * @internal
53
+ */
54
+ export type DefaultRouteUserData<Routes, Fallback extends Dictionary> = Routes extends {
55
+ [defaultRoute]: infer DefaultUserData extends Dictionary;
56
+ } ? DefaultUserData : Fallback;
35
57
  /**
36
58
  * Validates `userData` against a {@link RouteSchemas|Standard Schema}, returning the parsed (and coerced)
37
59
  * value. Throws a {@link RequestValidationError} when validation fails.
@@ -173,19 +195,20 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
173
195
  * Registers new route handler for given label. When the router declares a route map, the
174
196
  * `label` is restricted to the declared labels and `request.userData` is typed accordingly.
175
197
  */
176
- addHandler<Label extends keyof Routes & string>(label: Label, handler: (ctx: RouterHandlerContext<Context, Routes[Label]>) => Awaitable<void>): void;
198
+ addHandler<Label extends keyof Routes & string>(label: Label, handler: (ctx: RouterHandlerContext<Context, Routes[Label], Routes>) => Awaitable<void>): void;
177
199
  /**
178
200
  * Registers new route handler for given label, explicitly typing `request.userData` via the
179
201
  * `UserData` type argument. Useful when the router has no declared route map (the open default)
180
202
  * and you want to type a single handler, or to register a handler under a `symbol` label.
181
203
  */
182
- addHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(label: RouterLabel<Routes>, handler: (ctx: RouterHandlerContext<Context, UserData>) => Awaitable<void>): void;
204
+ addHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(label: RouterLabel<Routes>, handler: (ctx: RouterHandlerContext<Context, UserData, Routes>) => Awaitable<void>): void;
183
205
  /**
184
206
  * Registers default route handler. As a fallback it can receive any request (including labels not
185
- * declared in the route map), so `request.userData` defaults to the context's `userData` type
186
- * (loosely typed by default). Pass an explicit `UserData` type argument to narrow it.
207
+ * declared in the route map). When the router was created with a {@link defaultRoute} schema,
208
+ * `request.userData` is typed from it; otherwise it defaults to the context's (loosely typed) `userData`.
209
+ * Pass an explicit `UserData` type argument to narrow it.
187
210
  */
188
- addDefaultHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(handler: (ctx: RouterHandlerContext<Context, UserData>) => Awaitable<void>): void;
211
+ addDefaultHandler<UserData extends Dictionary = DefaultRouteUserData<Routes, GetUserDataFromRequest<Context['request']>>>(handler: (ctx: RouterHandlerContext<Context, UserData, Routes>) => Awaitable<void>): void;
189
212
  /**
190
213
  * Returns the {@link RouteSchemas|Standard Schema} registered for a label, if any. Used by the crawler
191
214
  * to validate `request.userData` when requests are added.
@@ -234,3 +257,4 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
234
257
  static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, Record<string, UserData>>): RouterHandler<Context, Record<string, UserData>>;
235
258
  static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, const Schemas extends RouteSchemas = RouteSchemas>(schemas: Schemas): RouterHandler<Context, RoutesFromSchemas<Schemas>>;
236
259
  }
260
+ export {};
package/router.js CHANGED
@@ -155,8 +155,9 @@ export class Router {
155
155
  }
156
156
  /**
157
157
  * Registers default route handler. As a fallback it can receive any request (including labels not
158
- * declared in the route map), so `request.userData` defaults to the context's `userData` type
159
- * (loosely typed by default). Pass an explicit `UserData` type argument to narrow it.
158
+ * declared in the route map). When the router was created with a {@link defaultRoute} schema,
159
+ * `request.userData` is typed from it; otherwise it defaults to the context's (loosely typed) `userData`.
160
+ * Pass an explicit `UserData` type argument to narrow it.
160
161
  */
161
162
  addDefaultHandler(handler) {
162
163
  this.validate(defaultRoute);