@crawlee/core 3.17.1-beta.53 → 3.17.1-beta.55
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.
- package/errors.d.ts +20 -0
- package/errors.js +32 -1
- package/index.mjs +3 -0
- package/package.json +6 -5
- package/router.d.ts +62 -0
- package/router.js +110 -10
package/errors.d.ts
CHANGED
|
@@ -14,6 +14,26 @@ export declare class CriticalError extends NonRetryableError {
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class MissingRouteError extends CriticalError {
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown when a request's `userData` does not match the {@link RouteSchemas|Standard Schema} registered for its label.
|
|
19
|
+
*
|
|
20
|
+
* As the `userData` does not change between attempts, this error is non-retryable.
|
|
21
|
+
*/
|
|
22
|
+
export declare class RequestValidationError extends NonRetryableError {
|
|
23
|
+
readonly label: string | symbol;
|
|
24
|
+
readonly issues: readonly {
|
|
25
|
+
readonly message: string;
|
|
26
|
+
readonly path?: readonly (PropertyKey | {
|
|
27
|
+
key: PropertyKey;
|
|
28
|
+
})[];
|
|
29
|
+
}[];
|
|
30
|
+
constructor(label: string | symbol, issues: readonly {
|
|
31
|
+
readonly message: string;
|
|
32
|
+
readonly path?: readonly (PropertyKey | {
|
|
33
|
+
key: PropertyKey;
|
|
34
|
+
})[];
|
|
35
|
+
}[]);
|
|
36
|
+
}
|
|
17
37
|
/**
|
|
18
38
|
* Errors of `RetryRequestError` type will always be retried by the crawler.
|
|
19
39
|
*
|
package/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SessionError = exports.RetryRequestError = exports.MissingRouteError = exports.CriticalError = exports.NonRetryableError = void 0;
|
|
3
|
+
exports.SessionError = exports.RetryRequestError = exports.RequestValidationError = exports.MissingRouteError = exports.CriticalError = exports.NonRetryableError = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Errors of `NonRetryableError` type will never be retried by the crawler.
|
|
6
6
|
*/
|
|
@@ -20,6 +20,37 @@ exports.CriticalError = CriticalError;
|
|
|
20
20
|
class MissingRouteError extends CriticalError {
|
|
21
21
|
}
|
|
22
22
|
exports.MissingRouteError = MissingRouteError;
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when a request's `userData` does not match the {@link RouteSchemas|Standard Schema} registered for its label.
|
|
25
|
+
*
|
|
26
|
+
* As the `userData` does not change between attempts, this error is non-retryable.
|
|
27
|
+
*/
|
|
28
|
+
class RequestValidationError extends NonRetryableError {
|
|
29
|
+
constructor(label, issues) {
|
|
30
|
+
const details = issues
|
|
31
|
+
.map((issue) => {
|
|
32
|
+
const path = (issue.path ?? [])
|
|
33
|
+
.map((segment) => (typeof segment === 'object' ? segment.key : segment))
|
|
34
|
+
.join('.');
|
|
35
|
+
return `- ${path ? `${path}: ` : ''}${issue.message}`;
|
|
36
|
+
})
|
|
37
|
+
.join('\n');
|
|
38
|
+
super(`Request userData for label '${String(label)}' failed schema validation:\n${details}`);
|
|
39
|
+
Object.defineProperty(this, "label", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
value: label
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(this, "issues", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: issues
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.RequestValidationError = RequestValidationError;
|
|
23
54
|
/**
|
|
24
55
|
* Errors of `RetryRequestError` type will always be retried by the crawler.
|
|
25
56
|
*
|
package/index.mjs
CHANGED
|
@@ -46,6 +46,7 @@ export const RequestQueue = mod.RequestQueue;
|
|
|
46
46
|
export const RequestQueueV1 = mod.RequestQueueV1;
|
|
47
47
|
export const RequestQueueV2 = mod.RequestQueueV2;
|
|
48
48
|
export const RequestState = mod.RequestState;
|
|
49
|
+
export const RequestValidationError = mod.RequestValidationError;
|
|
49
50
|
export const RetryRequestError = mod.RetryRequestError;
|
|
50
51
|
export const Router = mod.Router;
|
|
51
52
|
export const STATE_PERSISTENCE_KEY = mod.STATE_PERSISTENCE_KEY;
|
|
@@ -73,6 +74,7 @@ export const createDeserialize = mod.createDeserialize;
|
|
|
73
74
|
export const createEventLoopLoadSignal = mod.createEventLoopLoadSignal;
|
|
74
75
|
export const createRequestOptions = mod.createRequestOptions;
|
|
75
76
|
export const createRequests = mod.createRequests;
|
|
77
|
+
export const defaultRoute = mod.defaultRoute;
|
|
76
78
|
export const deserializeArray = mod.deserializeArray;
|
|
77
79
|
export const enqueueLinks = mod.enqueueLinks;
|
|
78
80
|
export const evaluateLoadSignalSample = mod.evaluateLoadSignalSample;
|
|
@@ -93,5 +95,6 @@ export const tryAbsoluteURL = mod.tryAbsoluteURL;
|
|
|
93
95
|
export const updateEnqueueLinksPatternCache = mod.updateEnqueueLinksPatternCache;
|
|
94
96
|
export const useState = mod.useState;
|
|
95
97
|
export const validateGlobPattern = mod.validateGlobPattern;
|
|
98
|
+
export const validateUserData = mod.validateUserData;
|
|
96
99
|
export const validators = mod.validators;
|
|
97
100
|
export const withCheckedStorageAccess = mod.withCheckedStorageAccess;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/core",
|
|
3
|
-
"version": "3.17.1-beta.
|
|
3
|
+
"version": "3.17.1-beta.55",
|
|
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": ">=16.0.0"
|
|
@@ -59,10 +59,11 @@
|
|
|
59
59
|
"@apify/pseudo_url": "^2.0.30",
|
|
60
60
|
"@apify/timeout": "^0.3.0",
|
|
61
61
|
"@apify/utilities": "^2.7.10",
|
|
62
|
-
"@crawlee/memory-storage": "3.17.1-beta.
|
|
63
|
-
"@crawlee/types": "3.17.1-beta.
|
|
64
|
-
"@crawlee/utils": "3.17.1-beta.
|
|
62
|
+
"@crawlee/memory-storage": "3.17.1-beta.55",
|
|
63
|
+
"@crawlee/types": "3.17.1-beta.55",
|
|
64
|
+
"@crawlee/utils": "3.17.1-beta.55",
|
|
65
65
|
"@sapphire/async-queue": "^1.5.1",
|
|
66
|
+
"@standard-schema/spec": "^1.0.0",
|
|
66
67
|
"@vladfrangu/async_event_emitter": "^2.2.2",
|
|
67
68
|
"csv-stringify": "^6.2.0",
|
|
68
69
|
"fs-extra": "^11.0.0",
|
|
@@ -83,5 +84,5 @@
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
},
|
|
86
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "93d29e305f01ca6ca9bb79264a5ca553b19766ef"
|
|
87
88
|
}
|
package/router.d.ts
CHANGED
|
@@ -1,13 +1,43 @@
|
|
|
1
1
|
import type { Dictionary } from '@crawlee/types';
|
|
2
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
3
|
import type { CrawlingContext, LoadedRequest, RestrictedCrawlingContext } from './crawlers/crawler_commons';
|
|
3
4
|
import type { Request } from './request';
|
|
4
5
|
import type { Awaitable } from './typedefs';
|
|
6
|
+
/**
|
|
7
|
+
* The key of the default route — the fallback handler registered via {@link Router.addDefaultHandler}.
|
|
8
|
+
* Use it in a {@link RouteSchemas} map to register a schema that validates the `userData` of every request
|
|
9
|
+
* that falls through to the default handler (i.e. whose label has no route of its own).
|
|
10
|
+
*/
|
|
11
|
+
export declare const defaultRoute: unique symbol;
|
|
5
12
|
/**
|
|
6
13
|
* The crawling context received by a route handler, with `request.userData` narrowed to `UserData`.
|
|
7
14
|
*/
|
|
8
15
|
export type RouterHandlerContext<Context, UserData extends Dictionary> = Omit<Context, 'request'> & {
|
|
9
16
|
request: LoadedRequest<Request<UserData>>;
|
|
10
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* A map of request labels to a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType, …)
|
|
20
|
+
* validating that label's `request.userData`. Pass it to {@link Router.create} or a `createXRouter`
|
|
21
|
+
* factory to derive the per-label `request.userData` types *and* validate them at runtime. The optional
|
|
22
|
+
* {@link defaultRoute} key registers a schema for requests handled by the default route.
|
|
23
|
+
*/
|
|
24
|
+
export type RouteSchemas = Record<string, StandardSchemaV1> & {
|
|
25
|
+
[defaultRoute]?: StandardSchemaV1;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
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.
|
|
31
|
+
*/
|
|
32
|
+
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
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Validates `userData` against a {@link RouteSchemas|Standard Schema}, returning the parsed (and coerced)
|
|
37
|
+
* value. Throws a {@link RequestValidationError} when validation fails.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare function validateUserData(label: string | symbol, schema: StandardSchemaV1, userData: unknown): Promise<Dictionary>;
|
|
11
41
|
/**
|
|
12
42
|
* The set of labels accepted by {@link Router.addHandler}. When the router declares a concrete
|
|
13
43
|
* route map (e.g. `{ PRODUCT: ...; CATEGORY: ... }`), only those labels (plus symbols) are
|
|
@@ -110,9 +140,29 @@ export type RouterRoutes<Context, Routes extends Record<keyof Routes, Dictionary
|
|
|
110
140
|
*
|
|
111
141
|
* router.addHandler('TYPO', async () => {}); // compile error: not a known label
|
|
112
142
|
* ```
|
|
143
|
+
*
|
|
144
|
+
* Passing a [Standard Schema](https://standardschema.dev) per label instead of a plain type both infers the
|
|
145
|
+
* `request.userData` types *and* validates them at runtime — when the request is handled, and when it is
|
|
146
|
+
* added to the crawler (`crawler.addRequests`, `context.addRequests`, `enqueueLinks`). A failing request
|
|
147
|
+
* throws a {@link RequestValidationError}.
|
|
148
|
+
*
|
|
149
|
+
* ```ts
|
|
150
|
+
* import { z } from 'zod';
|
|
151
|
+
* import { createCheerioRouter } from 'crawlee';
|
|
152
|
+
*
|
|
153
|
+
* const router = createCheerioRouter({
|
|
154
|
+
* PRODUCT: z.object({ sku: z.string(), price: z.number() }),
|
|
155
|
+
* CATEGORY: z.object({ categoryId: z.string() }),
|
|
156
|
+
* });
|
|
157
|
+
*
|
|
158
|
+
* router.addHandler('PRODUCT', async ({ request }) => {
|
|
159
|
+
* request.userData.price; // number, inferred from the schema and validated at runtime
|
|
160
|
+
* });
|
|
161
|
+
* ```
|
|
113
162
|
*/
|
|
114
163
|
export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'>, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> {
|
|
115
164
|
private readonly routes;
|
|
165
|
+
private readonly schemas;
|
|
116
166
|
private readonly middlewares;
|
|
117
167
|
/**
|
|
118
168
|
* use Router.create() instead!
|
|
@@ -136,6 +186,12 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
|
|
|
136
186
|
* (loosely typed by default). Pass an explicit `UserData` type argument to narrow it.
|
|
137
187
|
*/
|
|
138
188
|
addDefaultHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(handler: (ctx: RouterHandlerContext<Context, UserData>) => Awaitable<void>): void;
|
|
189
|
+
/**
|
|
190
|
+
* Returns the {@link RouteSchemas|Standard Schema} registered for a label, if any. Used by the crawler
|
|
191
|
+
* to validate `request.userData` when requests are added.
|
|
192
|
+
* @internal
|
|
193
|
+
*/
|
|
194
|
+
getSchema(label?: string | symbol): StandardSchemaV1 | undefined;
|
|
139
195
|
/**
|
|
140
196
|
* Registers a middleware that will be fired before the matching route handler.
|
|
141
197
|
* Multiple middlewares can be registered, they will be fired in the same order.
|
|
@@ -145,6 +201,11 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
|
|
|
145
201
|
* Returns route handler for given label. If no label is provided, the default request handler will be returned.
|
|
146
202
|
*/
|
|
147
203
|
getHandler(label?: string | symbol): (ctx: Context) => Awaitable<void>;
|
|
204
|
+
/**
|
|
205
|
+
* Validates `request.userData` against the schema registered for its label (if any), replacing it with
|
|
206
|
+
* the parsed value. Throws a {@link RequestValidationError} when validation fails.
|
|
207
|
+
*/
|
|
208
|
+
private validateRequest;
|
|
148
209
|
/**
|
|
149
210
|
* Throws when the label already exists in our registry.
|
|
150
211
|
*/
|
|
@@ -171,4 +232,5 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
|
|
|
171
232
|
*/
|
|
172
233
|
static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>>(routes?: RouterRoutes<Context, Routes>): RouterHandler<Context, Routes>;
|
|
173
234
|
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
|
+
static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, const Schemas extends RouteSchemas = RouteSchemas>(schemas: Schemas): RouterHandler<Context, RoutesFromSchemas<Schemas>>;
|
|
174
236
|
}
|
package/router.js
CHANGED
|
@@ -1,8 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Router = void 0;
|
|
3
|
+
exports.Router = exports.defaultRoute = void 0;
|
|
4
|
+
exports.validateUserData = validateUserData;
|
|
4
5
|
const errors_1 = require("./errors");
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The key of the default route — the fallback handler registered via {@link Router.addDefaultHandler}.
|
|
8
|
+
* Use it in a {@link RouteSchemas} map to register a schema that validates the `userData` of every request
|
|
9
|
+
* that falls through to the default handler (i.e. whose label has no route of its own).
|
|
10
|
+
*/
|
|
11
|
+
exports.defaultRoute = Symbol('default-route');
|
|
12
|
+
/** Whether a validation issue points at the top-level `label` key. */
|
|
13
|
+
function isLabelIssue(issue) {
|
|
14
|
+
if (issue.path?.length !== 1) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const [segment] = issue.path;
|
|
18
|
+
return (typeof segment === 'object' ? segment.key : segment) === 'label';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validates `userData` against a {@link RouteSchemas|Standard Schema}, returning the parsed (and coerced)
|
|
22
|
+
* value. Throws a {@link RequestValidationError} when validation fails.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
async function validateUserData(label, schema, userData) {
|
|
26
|
+
const { label: _label, ...rest } = (userData ?? {});
|
|
27
|
+
// `label` is a Crawlee-managed key that lives inside `userData`, so validating it is opt-in: we validate
|
|
28
|
+
// without it first, letting schemas that don't describe it pass (including `.strict()` ones). A schema that
|
|
29
|
+
// *does* declare `label` reports an issue for the now-missing key — so we re-validate with it included,
|
|
30
|
+
// honouring the declaration. Unlike `userData.__crawlee`, `label` is enumerable, so schemas do see it.
|
|
31
|
+
let result = await schema['~standard'].validate(rest);
|
|
32
|
+
if (result.issues?.some(isLabelIssue)) {
|
|
33
|
+
result = await schema['~standard'].validate({ ...rest, label });
|
|
34
|
+
}
|
|
35
|
+
if (result.issues) {
|
|
36
|
+
throw new errors_1.RequestValidationError(label, result.issues);
|
|
37
|
+
}
|
|
38
|
+
// Restore the label so it survives schemas that strip undeclared keys.
|
|
39
|
+
return { ...result.value, label };
|
|
40
|
+
}
|
|
6
41
|
/**
|
|
7
42
|
* Simple router that works based on request labels. This instance can then serve as a `requestHandler` of your crawler.
|
|
8
43
|
*
|
|
@@ -89,6 +124,25 @@ const defaultRoute = Symbol('default-route');
|
|
|
89
124
|
*
|
|
90
125
|
* router.addHandler('TYPO', async () => {}); // compile error: not a known label
|
|
91
126
|
* ```
|
|
127
|
+
*
|
|
128
|
+
* Passing a [Standard Schema](https://standardschema.dev) per label instead of a plain type both infers the
|
|
129
|
+
* `request.userData` types *and* validates them at runtime — when the request is handled, and when it is
|
|
130
|
+
* added to the crawler (`crawler.addRequests`, `context.addRequests`, `enqueueLinks`). A failing request
|
|
131
|
+
* throws a {@link RequestValidationError}.
|
|
132
|
+
*
|
|
133
|
+
* ```ts
|
|
134
|
+
* import { z } from 'zod';
|
|
135
|
+
* import { createCheerioRouter } from 'crawlee';
|
|
136
|
+
*
|
|
137
|
+
* const router = createCheerioRouter({
|
|
138
|
+
* PRODUCT: z.object({ sku: z.string(), price: z.number() }),
|
|
139
|
+
* CATEGORY: z.object({ categoryId: z.string() }),
|
|
140
|
+
* });
|
|
141
|
+
*
|
|
142
|
+
* router.addHandler('PRODUCT', async ({ request }) => {
|
|
143
|
+
* request.userData.price; // number, inferred from the schema and validated at runtime
|
|
144
|
+
* });
|
|
145
|
+
* ```
|
|
92
146
|
*/
|
|
93
147
|
class Router {
|
|
94
148
|
/**
|
|
@@ -102,6 +156,12 @@ class Router {
|
|
|
102
156
|
writable: true,
|
|
103
157
|
value: new Map()
|
|
104
158
|
});
|
|
159
|
+
Object.defineProperty(this, "schemas", {
|
|
160
|
+
enumerable: true,
|
|
161
|
+
configurable: true,
|
|
162
|
+
writable: true,
|
|
163
|
+
value: new Map()
|
|
164
|
+
});
|
|
105
165
|
Object.defineProperty(this, "middlewares", {
|
|
106
166
|
enumerable: true,
|
|
107
167
|
configurable: true,
|
|
@@ -119,8 +179,28 @@ class Router {
|
|
|
119
179
|
* (loosely typed by default). Pass an explicit `UserData` type argument to narrow it.
|
|
120
180
|
*/
|
|
121
181
|
addDefaultHandler(handler) {
|
|
122
|
-
this.validate(defaultRoute);
|
|
123
|
-
this.routes.set(defaultRoute, handler);
|
|
182
|
+
this.validate(exports.defaultRoute);
|
|
183
|
+
this.routes.set(exports.defaultRoute, handler);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Returns the {@link RouteSchemas|Standard Schema} registered for a label, if any. Used by the crawler
|
|
187
|
+
* to validate `request.userData` when requests are added.
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
getSchema(label) {
|
|
191
|
+
if (label != null) {
|
|
192
|
+
const schema = this.schemas.get(label);
|
|
193
|
+
if (schema) {
|
|
194
|
+
return schema;
|
|
195
|
+
}
|
|
196
|
+
// A label with its own route is fully specified; don't fall back to the default-route schema.
|
|
197
|
+
if (this.routes.has(label)) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Requests with no route of their own fall through to the default handler, so validate their
|
|
202
|
+
// `userData` against the default-route schema, if one was registered.
|
|
203
|
+
return this.schemas.get(exports.defaultRoute);
|
|
124
204
|
}
|
|
125
205
|
/**
|
|
126
206
|
* Registers a middleware that will be fired before the matching route handler.
|
|
@@ -136,37 +216,57 @@ class Router {
|
|
|
136
216
|
if (label && this.routes.has(label)) {
|
|
137
217
|
return this.routes.get(label);
|
|
138
218
|
}
|
|
139
|
-
if (this.routes.has(defaultRoute)) {
|
|
140
|
-
return this.routes.get(defaultRoute);
|
|
219
|
+
if (this.routes.has(exports.defaultRoute)) {
|
|
220
|
+
return this.routes.get(exports.defaultRoute);
|
|
141
221
|
}
|
|
142
222
|
throw new errors_1.MissingRouteError(`Route not found for label '${String(label)}'.` +
|
|
143
223
|
' You must set up a route for this label or a default route.' +
|
|
144
224
|
' Use `requestHandler`, `router.addHandler` or `router.addDefaultHandler`.');
|
|
145
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Validates `request.userData` against the schema registered for its label (if any), replacing it with
|
|
228
|
+
* the parsed value. Throws a {@link RequestValidationError} when validation fails.
|
|
229
|
+
*/
|
|
230
|
+
async validateRequest(context) {
|
|
231
|
+
const label = context.request.label;
|
|
232
|
+
const schema = this.getSchema(label);
|
|
233
|
+
if (schema) {
|
|
234
|
+
context.request.userData = (await validateUserData(label, schema, context.request.userData));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
146
237
|
/**
|
|
147
238
|
* Throws when the label already exists in our registry.
|
|
148
239
|
*/
|
|
149
240
|
validate(label) {
|
|
150
241
|
if (this.routes.has(label)) {
|
|
151
|
-
const message = label === defaultRoute
|
|
242
|
+
const message = label === exports.defaultRoute
|
|
152
243
|
? `Default route is already defined!`
|
|
153
244
|
: `Route for label '${String(label)}' is already defined!`;
|
|
154
245
|
throw new Error(message);
|
|
155
246
|
}
|
|
156
247
|
}
|
|
157
|
-
static create(
|
|
248
|
+
static create(routesOrSchemas) {
|
|
158
249
|
const router = new Router();
|
|
159
250
|
const obj = Object.create(Function.prototype);
|
|
160
251
|
obj.addHandler = router.addHandler.bind(router);
|
|
161
252
|
obj.addDefaultHandler = router.addDefaultHandler.bind(router);
|
|
253
|
+
obj.getSchema = router.getSchema.bind(router);
|
|
162
254
|
obj.getHandler = router.getHandler.bind(router);
|
|
163
255
|
obj.use = router.use.bind(router);
|
|
164
|
-
|
|
165
|
-
|
|
256
|
+
// `Reflect.ownKeys` (unlike `Object.entries`) also yields the `defaultRoute` symbol key.
|
|
257
|
+
for (const label of Reflect.ownKeys(routesOrSchemas ?? {})) {
|
|
258
|
+
const value = routesOrSchemas[label];
|
|
259
|
+
if (typeof value === 'function') {
|
|
260
|
+
router.addHandler(label, value);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
router.schemas.set(label, value);
|
|
264
|
+
}
|
|
166
265
|
}
|
|
167
266
|
const func = async function (context) {
|
|
168
267
|
const { url, loadedUrl, label } = context.request;
|
|
169
268
|
context.log.debug('Page opened.', { label, url: loadedUrl ?? url });
|
|
269
|
+
await router.validateRequest(context);
|
|
170
270
|
for (const middleware of router.middlewares) {
|
|
171
271
|
await middleware(context);
|
|
172
272
|
}
|