@terreno/api 0.13.2 → 0.14.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.
- package/dist/__tests__/versionCheckPlugin.test.js +53 -3
- package/dist/api.arrayOperations.test.js +1 -0
- package/dist/api.asyncHandler.test.d.ts +1 -0
- package/dist/api.asyncHandler.test.js +236 -0
- package/dist/api.d.ts +15 -4
- package/dist/api.errors.test.js +1 -0
- package/dist/api.hooks.test.js +1 -0
- package/dist/api.js +153 -104
- package/dist/api.query.test.js +1 -0
- package/dist/api.test.js +174 -0
- package/dist/auth.d.ts +10 -5
- package/dist/auth.js +163 -90
- package/dist/auth.test.js +159 -0
- package/dist/betterAuthApp.test.js +1 -0
- package/dist/betterAuthSetup.d.ts +5 -6
- package/dist/betterAuthSetup.js +17 -14
- package/dist/betterAuthSetup.test.js +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.js +248 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +328 -0
- package/dist/configuration.test.js +1 -0
- package/dist/configurationApp.d.ts +1 -1
- package/dist/configurationApp.js +17 -13
- package/dist/configurationPlugin.test.js +1 -0
- package/dist/consentApp.test.js +1 -0
- package/dist/envConfigurationPlugin.d.ts +2 -0
- package/dist/envConfigurationPlugin.js +173 -0
- package/dist/envConfigurationPlugin.test.d.ts +1 -0
- package/dist/envConfigurationPlugin.test.js +322 -0
- package/dist/errors.d.ts +18 -7
- package/dist/errors.js +106 -10
- package/dist/errors.test.js +16 -1
- package/dist/example.js +16 -7
- package/dist/expressServer.d.ts +10 -9
- package/dist/expressServer.js +62 -53
- package/dist/expressServer.test.js +53 -2
- package/dist/githubAuth.d.ts +2 -1
- package/dist/githubAuth.js +41 -26
- package/dist/githubAuth.test.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +42 -20
- package/dist/models/versionConfig.d.ts +2 -0
- package/dist/models/versionConfig.js +8 -0
- package/dist/notifiers/googleChatNotifier.js +14 -16
- package/dist/notifiers/googleChatNotifier.test.js +1 -0
- package/dist/notifiers/slackNotifier.js +16 -14
- package/dist/notifiers/slackNotifier.test.js +41 -3
- package/dist/notifiers/zoomNotifier.js +7 -10
- package/dist/notifiers/zoomNotifier.test.js +1 -0
- package/dist/openApi.d.ts +1 -1
- package/dist/openApi.test.js +1 -0
- package/dist/openApiBuilder.d.ts +39 -6
- package/dist/openApiBuilder.js +1 -31
- package/dist/openApiBuilder.test.js +1 -0
- package/dist/openApiValidator.js +1 -0
- package/dist/openApiValidator.test.js +65 -0
- package/dist/permissions.d.ts +4 -4
- package/dist/permissions.js +67 -65
- package/dist/permissions.middleware.test.js +1 -0
- package/dist/permissions.test.js +1 -0
- package/dist/plugins.d.ts +5 -5
- package/dist/plugins.js +18 -9
- package/dist/plugins.test.js +1 -1
- package/dist/populate.d.ts +15 -8
- package/dist/populate.js +23 -24
- package/dist/populate.test.js +1 -0
- package/dist/realtime/changeStreamWatcher.d.ts +73 -0
- package/dist/realtime/changeStreamWatcher.js +720 -0
- package/dist/realtime/index.d.ts +6 -0
- package/dist/realtime/index.js +27 -0
- package/dist/realtime/queryMatcher.d.ts +14 -0
- package/dist/realtime/queryMatcher.js +250 -0
- package/dist/realtime/queryStore.d.ts +37 -0
- package/dist/realtime/queryStore.js +195 -0
- package/dist/realtime/realtime.test.d.ts +10 -0
- package/dist/realtime/realtime.test.js +2158 -0
- package/dist/realtime/realtimeApp.d.ts +93 -0
- package/dist/realtime/realtimeApp.js +560 -0
- package/dist/realtime/registry.d.ts +40 -0
- package/dist/realtime/registry.js +38 -0
- package/dist/realtime/socketUser.d.ts +10 -0
- package/dist/realtime/socketUser.js +17 -0
- package/dist/realtime/types.d.ts +100 -0
- package/dist/realtime/types.js +2 -0
- package/dist/requestContext.d.ts +37 -0
- package/dist/requestContext.js +344 -0
- package/dist/requestContext.test.d.ts +1 -0
- package/dist/requestContext.test.js +241 -0
- package/dist/terrenoApp.d.ts +8 -0
- package/dist/terrenoApp.js +50 -13
- package/dist/terrenoApp.test.js +194 -21
- package/dist/terrenoPlugin.d.ts +11 -0
- package/dist/tests/bunSetup.js +1 -0
- package/dist/tests.js +1 -1
- package/dist/transformers.d.ts +2 -2
- package/dist/transformers.js +5 -3
- package/dist/transformers.test.js +90 -0
- package/dist/types/consentResponse.d.ts +6 -3
- package/dist/versionCheckPlugin.d.ts +2 -0
- package/dist/versionCheckPlugin.js +18 -12
- package/package.json +4 -2
- package/src/__tests__/versionCheckPlugin.test.ts +37 -3
- package/src/api.arrayOperations.test.ts +1 -0
- package/src/api.asyncHandler.test.ts +177 -0
- package/src/api.errors.test.ts +1 -0
- package/src/api.hooks.test.ts +1 -0
- package/src/api.query.test.ts +1 -0
- package/src/api.test.ts +132 -0
- package/src/api.ts +199 -84
- package/src/auth.test.ts +160 -0
- package/src/auth.ts +120 -50
- package/src/betterAuthApp.test.ts +1 -0
- package/src/betterAuthSetup.test.ts +1 -0
- package/src/betterAuthSetup.ts +46 -19
- package/src/config.test.ts +255 -0
- package/src/config.ts +206 -0
- package/src/configuration.test.ts +1 -0
- package/src/configurationApp.ts +59 -24
- package/src/configurationPlugin.test.ts +1 -0
- package/src/consentApp.test.ts +1 -0
- package/src/envConfigurationPlugin.test.ts +143 -0
- package/src/envConfigurationPlugin.ts +100 -0
- package/src/errors.test.ts +19 -1
- package/src/errors.ts +94 -20
- package/src/example.ts +46 -21
- package/src/express.d.ts +18 -1
- package/src/expressServer.test.ts +50 -2
- package/src/expressServer.ts +80 -50
- package/src/githubAuth.test.ts +1 -0
- package/src/githubAuth.ts +59 -38
- package/src/index.ts +4 -0
- package/src/logger.ts +47 -17
- package/src/models/versionConfig.ts +13 -2
- package/src/notifiers/googleChatNotifier.test.ts +1 -0
- package/src/notifiers/googleChatNotifier.ts +7 -9
- package/src/notifiers/slackNotifier.test.ts +29 -3
- package/src/notifiers/slackNotifier.ts +9 -7
- package/src/notifiers/zoomNotifier.test.ts +1 -0
- package/src/notifiers/zoomNotifier.ts +8 -11
- package/src/openApi.test.ts +1 -0
- package/src/openApi.ts +4 -4
- package/src/openApiBuilder.test.ts +1 -0
- package/src/openApiBuilder.ts +14 -11
- package/src/openApiValidator.test.ts +59 -0
- package/src/openApiValidator.ts +3 -2
- package/src/permissions.middleware.test.ts +1 -0
- package/src/permissions.test.ts +1 -0
- package/src/permissions.ts +30 -25
- package/src/plugins.test.ts +1 -1
- package/src/plugins.ts +21 -14
- package/src/populate.test.ts +1 -0
- package/src/populate.ts +44 -36
- package/src/realtime/changeStreamWatcher.ts +568 -0
- package/src/realtime/index.ts +34 -0
- package/src/realtime/queryMatcher.ts +179 -0
- package/src/realtime/queryStore.ts +132 -0
- package/src/realtime/realtime.test.ts +1755 -0
- package/src/realtime/realtimeApp.ts +478 -0
- package/src/realtime/registry.ts +64 -0
- package/src/realtime/socketUser.ts +25 -0
- package/src/realtime/types.ts +112 -0
- package/src/requestContext.test.ts +196 -0
- package/src/requestContext.ts +368 -0
- package/src/terrenoApp.test.ts +137 -11
- package/src/terrenoApp.ts +64 -17
- package/src/terrenoPlugin.ts +12 -0
- package/src/tests/bunSetup.ts +1 -0
- package/src/tests.ts +7 -2
- package/src/transformers.test.ts +70 -2
- package/src/transformers.ts +15 -7
- package/src/types/consentResponse.ts +8 -10
- package/src/versionCheckPlugin.ts +15 -7
package/src/api.ts
CHANGED
|
@@ -6,10 +6,18 @@
|
|
|
6
6
|
import * as Sentry from "@sentry/bun";
|
|
7
7
|
import express, {type NextFunction, type Request, type Response} from "express";
|
|
8
8
|
import cloneDeep from "lodash/cloneDeep";
|
|
9
|
+
import {DateTime} from "luxon";
|
|
9
10
|
import mongoose, {type Document, type Model} from "mongoose";
|
|
10
11
|
|
|
11
12
|
import {authenticateMiddleware, type User} from "./auth";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
APIError,
|
|
15
|
+
apiErrorMiddleware,
|
|
16
|
+
errorMessage,
|
|
17
|
+
errorStack,
|
|
18
|
+
getDisableExternalErrorTracking,
|
|
19
|
+
isAPIError,
|
|
20
|
+
} from "./errors";
|
|
13
21
|
import {logger} from "./logger";
|
|
14
22
|
import {
|
|
15
23
|
createOpenApiMiddleware,
|
|
@@ -26,6 +34,8 @@ import {
|
|
|
26
34
|
} from "./openApiValidator";
|
|
27
35
|
import {checkPermissions, permissionMiddleware, type RESTPermissions} from "./permissions";
|
|
28
36
|
import type {PopulatePath} from "./populate";
|
|
37
|
+
import {registerRealtime} from "./realtime/registry";
|
|
38
|
+
import type {RealtimeConfig} from "./realtime/types";
|
|
29
39
|
import {
|
|
30
40
|
defaultResponseHandler,
|
|
31
41
|
serialize,
|
|
@@ -42,6 +52,7 @@ export interface JSONObject {
|
|
|
42
52
|
export type JSONValue = JSONPrimitive | JSONObject | JSONArray;
|
|
43
53
|
|
|
44
54
|
export const addPopulateToQuery = (
|
|
55
|
+
// biome-ignore lint/suspicious/noExplicitAny: mongoose Query type parameters vary widely across populated/unpopulated documents — caller passes concrete types
|
|
45
56
|
builtQuery: mongoose.Query<any[], any, Record<string, never>, any>,
|
|
46
57
|
populatePaths?: PopulatePath[]
|
|
47
58
|
) => {
|
|
@@ -262,16 +273,16 @@ export interface ModelRouterOptions<T> {
|
|
|
262
273
|
* @deprecated: Use responseHandler instead.
|
|
263
274
|
*/
|
|
264
275
|
postList?: (
|
|
265
|
-
value: (Document<
|
|
276
|
+
value: (Document<unknown, unknown, unknown> & T)[],
|
|
266
277
|
request: express.Request
|
|
267
|
-
) => Promise<(Document<
|
|
278
|
+
) => Promise<(Document<unknown, unknown, unknown> & T)[]>;
|
|
268
279
|
/**
|
|
269
280
|
* Serialize an object or list of objects before returning to the client.
|
|
270
281
|
* This is a good spot to remove sensitive information from the object, such as passwords or API
|
|
271
282
|
* keys. Throw an APIError to return a 400 with an error message.
|
|
272
283
|
*/
|
|
273
284
|
responseHandler?: (
|
|
274
|
-
value: (Document<
|
|
285
|
+
value: (Document<unknown, unknown, unknown> & T) | (Document<unknown, unknown, unknown> & T)[],
|
|
275
286
|
method: "list" | "create" | "read" | "update" | "delete",
|
|
276
287
|
request: express.Request,
|
|
277
288
|
options: ModelRouterOptions<T>
|
|
@@ -311,19 +322,29 @@ export interface ModelRouterOptions<T> {
|
|
|
311
322
|
* This option overrides the global setting for this specific router.
|
|
312
323
|
*/
|
|
313
324
|
validation?: boolean | ModelRouterValidationOptions;
|
|
325
|
+
/**
|
|
326
|
+
* Enable real-time sync for this model via WebSocket events.
|
|
327
|
+
* When configured, CRUD operations will emit events to connected clients
|
|
328
|
+
* through the RealtimeApp plugin's change stream watcher.
|
|
329
|
+
*
|
|
330
|
+
* Requires the RealtimeApp plugin to be registered with TerrenoApp.
|
|
331
|
+
*/
|
|
332
|
+
realtime?: RealtimeConfig;
|
|
314
333
|
}
|
|
315
334
|
|
|
316
335
|
// Ensures query params are allowed. Also checks nested query params when using $and/$or.
|
|
317
336
|
const checkQueryParamAllowed = (
|
|
318
337
|
queryParam: string,
|
|
319
|
-
queryParamValue:
|
|
338
|
+
queryParamValue: unknown,
|
|
320
339
|
queryFields: string[] = []
|
|
321
340
|
) => {
|
|
341
|
+
// Cast for iteration through complex query values
|
|
342
|
+
const complexValue = queryParamValue as Array<Record<string, unknown>>;
|
|
322
343
|
// Check the values of each of the complex query params. We don't support recursive queries here,
|
|
323
344
|
// just one level of and/or
|
|
324
345
|
if (COMPLEX_QUERY_PARAMS.includes(queryParam)) {
|
|
325
346
|
// Complex query of the form `$and: [{key1: value1}, {key2: value2}]`
|
|
326
|
-
for (const subQuery of
|
|
347
|
+
for (const subQuery of complexValue) {
|
|
327
348
|
for (const subKey of Object.keys(subQuery)) {
|
|
328
349
|
checkQueryParamAllowed(subKey, subQuery[subKey], queryFields);
|
|
329
350
|
}
|
|
@@ -360,8 +381,8 @@ const checkQueryParamAllowed = (
|
|
|
360
381
|
// Helper to determine if validation should be enabled for a specific operation.
|
|
361
382
|
// When options.validation is not set, returns true — the middleware's own
|
|
362
383
|
// isConfigured check will decide whether to actually validate.
|
|
363
|
-
const shouldValidate = (
|
|
364
|
-
options: ModelRouterOptions<
|
|
384
|
+
const shouldValidate = <T>(
|
|
385
|
+
options: ModelRouterOptions<T>,
|
|
365
386
|
operation: "create" | "update" | "query"
|
|
366
387
|
): boolean => {
|
|
367
388
|
// Check route-specific validation option first
|
|
@@ -446,7 +467,7 @@ export interface ModelRouterRegistration {
|
|
|
446
467
|
/** The Express router containing CRUD endpoints */
|
|
447
468
|
router: express.Router;
|
|
448
469
|
/** @internal Rebuilds the router with the openApi instance injected into options */
|
|
449
|
-
_buildWithOpenApi: (openApi:
|
|
470
|
+
_buildWithOpenApi: (openApi: OpenApiMiddleware) => express.Router;
|
|
450
471
|
}
|
|
451
472
|
|
|
452
473
|
/**
|
|
@@ -490,13 +511,32 @@ export function modelRouter<T>(
|
|
|
490
511
|
const router = _buildModelRouter(model, options);
|
|
491
512
|
|
|
492
513
|
if (path !== undefined) {
|
|
514
|
+
// Register for real-time sync if configured
|
|
515
|
+
if (options.realtime) {
|
|
516
|
+
registerRealtime({
|
|
517
|
+
collectionName: model.collection.collectionName,
|
|
518
|
+
config: options.realtime,
|
|
519
|
+
modelName: model.modelName,
|
|
520
|
+
options,
|
|
521
|
+
routePath: path,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
493
524
|
return {
|
|
494
525
|
__type: "modelRouter",
|
|
495
|
-
_buildWithOpenApi: (openApi:
|
|
526
|
+
_buildWithOpenApi: (openApi: OpenApiMiddleware) =>
|
|
527
|
+
_buildModelRouter(model, {...options, openApi}),
|
|
496
528
|
path,
|
|
497
529
|
router,
|
|
498
530
|
};
|
|
499
531
|
}
|
|
532
|
+
|
|
533
|
+
if (options.realtime) {
|
|
534
|
+
logger.warn(
|
|
535
|
+
`modelRouter for ${model.modelName} has realtime config but was called without a path. ` +
|
|
536
|
+
"Realtime sync only works with the three-argument form: modelRouter('/path', Model, options)"
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
500
540
|
return router;
|
|
501
541
|
}
|
|
502
542
|
|
|
@@ -527,7 +567,7 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
527
567
|
let body: Partial<T> | (Partial<T> | undefined)[] | null | undefined;
|
|
528
568
|
try {
|
|
529
569
|
body = transform<T>(options, req.body, "create", req.user);
|
|
530
|
-
} catch (error:
|
|
570
|
+
} catch (error: unknown) {
|
|
531
571
|
if (isAPIError(error)) {
|
|
532
572
|
throw error;
|
|
533
573
|
}
|
|
@@ -535,13 +575,13 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
535
575
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
536
576
|
error,
|
|
537
577
|
status: 400,
|
|
538
|
-
title: error
|
|
578
|
+
title: errorMessage(error),
|
|
539
579
|
});
|
|
540
580
|
}
|
|
541
581
|
if (options.preCreate) {
|
|
542
582
|
try {
|
|
543
583
|
body = await options.preCreate(body, req);
|
|
544
|
-
} catch (error:
|
|
584
|
+
} catch (error: unknown) {
|
|
545
585
|
if (isAPIError(error)) {
|
|
546
586
|
throw error;
|
|
547
587
|
}
|
|
@@ -549,7 +589,7 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
549
589
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
550
590
|
error,
|
|
551
591
|
status: 400,
|
|
552
|
-
title: `preCreate hook error: ${error
|
|
592
|
+
title: `preCreate hook error: ${errorMessage(error)}`,
|
|
553
593
|
});
|
|
554
594
|
}
|
|
555
595
|
if (body === undefined) {
|
|
@@ -574,29 +614,30 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
574
614
|
title: "Invalid request body",
|
|
575
615
|
});
|
|
576
616
|
}
|
|
577
|
-
let data;
|
|
617
|
+
let data: Document<unknown, unknown, unknown> & T;
|
|
578
618
|
try {
|
|
579
|
-
data = await model.create(body as
|
|
580
|
-
} catch (error:
|
|
619
|
+
data = (await model.create(body as T)) as Document<unknown, unknown, unknown> & T;
|
|
620
|
+
} catch (error: unknown) {
|
|
581
621
|
throw new APIError({
|
|
582
622
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
583
623
|
error,
|
|
584
624
|
status: 400,
|
|
585
|
-
title: error
|
|
625
|
+
title: errorMessage(error),
|
|
586
626
|
});
|
|
587
627
|
}
|
|
588
628
|
|
|
589
629
|
if (options.populatePaths) {
|
|
590
630
|
try {
|
|
631
|
+
// biome-ignore lint/suspicious/noExplicitAny: mongoose Query type varies based on populatePaths
|
|
591
632
|
let populateQuery: any = model.findById(data._id);
|
|
592
633
|
populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
|
|
593
634
|
data = await populateQuery.exec();
|
|
594
|
-
} catch (error:
|
|
635
|
+
} catch (error: unknown) {
|
|
595
636
|
throw new APIError({
|
|
596
637
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
597
638
|
error,
|
|
598
639
|
status: 400,
|
|
599
|
-
title: `Populate error: ${error
|
|
640
|
+
title: `Populate error: ${errorMessage(error)}`,
|
|
600
641
|
});
|
|
601
642
|
}
|
|
602
643
|
}
|
|
@@ -604,23 +645,23 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
604
645
|
if (options.postCreate) {
|
|
605
646
|
try {
|
|
606
647
|
await options.postCreate(data, req);
|
|
607
|
-
} catch (error:
|
|
648
|
+
} catch (error: unknown) {
|
|
608
649
|
throw new APIError({
|
|
609
650
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
610
651
|
error,
|
|
611
652
|
status: 400,
|
|
612
|
-
title: `postCreate hook error: ${error
|
|
653
|
+
title: `postCreate hook error: ${errorMessage(error)}`,
|
|
613
654
|
});
|
|
614
655
|
}
|
|
615
656
|
}
|
|
616
657
|
try {
|
|
617
658
|
const serialized = await responseHandler(data, "create", req, options);
|
|
618
659
|
return res.status(201).json({data: serialized});
|
|
619
|
-
} catch (error:
|
|
660
|
+
} catch (error: unknown) {
|
|
620
661
|
throw new APIError({
|
|
621
662
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
622
663
|
error,
|
|
623
|
-
title: `responseHandler error: ${error
|
|
664
|
+
title: `responseHandler error: ${errorMessage(error)}`,
|
|
624
665
|
});
|
|
625
666
|
}
|
|
626
667
|
})
|
|
@@ -636,7 +677,7 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
636
677
|
queryValidation,
|
|
637
678
|
],
|
|
638
679
|
asyncHandler(async (req: Request, res: Response) => {
|
|
639
|
-
let query:
|
|
680
|
+
let query: Record<string, unknown> = {};
|
|
640
681
|
for (const queryParam of Object.keys(options.defaultQueryParams ?? [])) {
|
|
641
682
|
query[queryParam] = options.defaultQueryParams?.[queryParam];
|
|
642
683
|
}
|
|
@@ -668,10 +709,10 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
668
709
|
|
|
669
710
|
// Check if any of the keys in the query are not allowed by options.queryFilter
|
|
670
711
|
if (options.queryFilter) {
|
|
671
|
-
let queryFilter;
|
|
712
|
+
let queryFilter: Record<string, unknown> | null | undefined;
|
|
672
713
|
try {
|
|
673
714
|
queryFilter = await options.queryFilter(req.user, query);
|
|
674
|
-
} catch (error:
|
|
715
|
+
} catch (error: unknown) {
|
|
675
716
|
throw new APIError({
|
|
676
717
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
677
718
|
error,
|
|
@@ -720,30 +761,30 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
720
761
|
|
|
721
762
|
const populatedQuery = addPopulateToQuery(builtQuery, options.populatePaths);
|
|
722
763
|
|
|
723
|
-
let data: (Document<
|
|
764
|
+
let data: (Document<unknown, unknown, unknown> & T)[];
|
|
724
765
|
try {
|
|
725
|
-
data = await populatedQuery.exec();
|
|
726
|
-
} catch (error:
|
|
766
|
+
data = (await populatedQuery.exec()) as (Document<unknown, unknown, unknown> & T)[];
|
|
767
|
+
} catch (error: unknown) {
|
|
727
768
|
throw new APIError({
|
|
728
769
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
729
770
|
error,
|
|
730
|
-
title: `List error: ${error
|
|
771
|
+
title: `List error: ${errorStack(error)}`,
|
|
731
772
|
});
|
|
732
773
|
}
|
|
733
774
|
|
|
734
|
-
let serialized;
|
|
775
|
+
let serialized: JSONValue | Partial<T> | (Partial<T> | undefined)[] | undefined;
|
|
735
776
|
|
|
736
777
|
try {
|
|
737
778
|
serialized = await responseHandler(data, "list", req, options);
|
|
738
|
-
} catch (error:
|
|
779
|
+
} catch (error: unknown) {
|
|
739
780
|
throw new APIError({
|
|
740
781
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
741
782
|
error,
|
|
742
|
-
title: `responseHandler error: ${error
|
|
783
|
+
title: `responseHandler error: ${errorMessage(error)}`,
|
|
743
784
|
});
|
|
744
785
|
}
|
|
745
786
|
|
|
746
|
-
let more;
|
|
787
|
+
let more: boolean | undefined;
|
|
747
788
|
try {
|
|
748
789
|
if (serialized && Array.isArray(serialized)) {
|
|
749
790
|
more = serialized.length === limit + 1 && serialized.length > 0;
|
|
@@ -770,11 +811,11 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
770
811
|
});
|
|
771
812
|
}
|
|
772
813
|
return res.json({data: serialized});
|
|
773
|
-
} catch (error:
|
|
814
|
+
} catch (error: unknown) {
|
|
774
815
|
throw new APIError({
|
|
775
816
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
776
817
|
error,
|
|
777
|
-
title: `Serialization error: ${error
|
|
818
|
+
title: `Serialization error: ${errorMessage(error)}`,
|
|
778
819
|
});
|
|
779
820
|
}
|
|
780
821
|
})
|
|
@@ -788,16 +829,16 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
788
829
|
permissionMiddleware(model, options),
|
|
789
830
|
],
|
|
790
831
|
asyncHandler(async (req: Request, res: Response) => {
|
|
791
|
-
const data: mongoose.Document & T = (req as
|
|
832
|
+
const data: mongoose.Document & T = (req as Request & {obj: mongoose.Document & T}).obj;
|
|
792
833
|
|
|
793
834
|
try {
|
|
794
835
|
const serialized = await responseHandler(data, "read", req, options);
|
|
795
836
|
return res.json({data: serialized});
|
|
796
|
-
} catch (error:
|
|
837
|
+
} catch (error: unknown) {
|
|
797
838
|
throw new APIError({
|
|
798
839
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
799
840
|
error,
|
|
800
|
-
title: `responseHandler error: ${error
|
|
841
|
+
title: `responseHandler error: ${errorMessage(error)}`,
|
|
801
842
|
});
|
|
802
843
|
}
|
|
803
844
|
})
|
|
@@ -823,13 +864,13 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
823
864
|
updateValidation,
|
|
824
865
|
],
|
|
825
866
|
asyncHandler(async (req: Request, res: Response) => {
|
|
826
|
-
let doc: mongoose.Document & T = (req as
|
|
867
|
+
let doc: mongoose.Document & T = (req as Request & {obj: mongoose.Document & T}).obj;
|
|
827
868
|
|
|
828
|
-
let body;
|
|
869
|
+
let body: Partial<T> | T | null | undefined;
|
|
829
870
|
|
|
830
871
|
try {
|
|
831
|
-
body = transform<T>(options, req.body, "update", req.user)
|
|
832
|
-
} catch (error:
|
|
872
|
+
body = transform<T>(options, req.body, "update", req.user) as Partial<T>;
|
|
873
|
+
} catch (error: unknown) {
|
|
833
874
|
if (isAPIError(error)) {
|
|
834
875
|
throw error;
|
|
835
876
|
}
|
|
@@ -837,18 +878,21 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
837
878
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
838
879
|
error,
|
|
839
880
|
status: 403,
|
|
840
|
-
title: `PATCH failed on ${req.params.id} for user ${req.user?.id}: ${error
|
|
881
|
+
title: `PATCH failed on ${req.params.id} for user ${req.user?.id}: ${errorMessage(error)}`,
|
|
841
882
|
});
|
|
842
883
|
}
|
|
843
884
|
|
|
885
|
+
// Remove _updatedAt from body before preUpdate processes it
|
|
886
|
+
const bodyUpdatedAt = req.body._updatedAt;
|
|
887
|
+
delete req.body._updatedAt;
|
|
888
|
+
if (body && typeof body === "object") {
|
|
889
|
+
delete (body as Record<string, unknown>)._updatedAt;
|
|
890
|
+
}
|
|
891
|
+
|
|
844
892
|
if (options.preUpdate) {
|
|
845
893
|
try {
|
|
846
|
-
// TODO: Send flattened dot notation body to preUpdate, then merge the returned body
|
|
847
|
-
// with the original body, maintaining the dot notation. This way we don't have to write
|
|
848
|
-
// two preUpdate branches downstream, one looking at the dot notation style and
|
|
849
|
-
// one looking at normal object style.
|
|
850
894
|
body = await options.preUpdate(body, req);
|
|
851
|
-
} catch (error:
|
|
895
|
+
} catch (error: unknown) {
|
|
852
896
|
if (isAPIError(error)) {
|
|
853
897
|
throw error;
|
|
854
898
|
}
|
|
@@ -856,7 +900,7 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
856
900
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
857
901
|
error,
|
|
858
902
|
status: 400,
|
|
859
|
-
title: `preUpdate hook error on ${req.params.id}: ${error
|
|
903
|
+
title: `preUpdate hook error on ${req.params.id}: ${errorMessage(error)}`,
|
|
860
904
|
});
|
|
861
905
|
}
|
|
862
906
|
if (body === undefined) {
|
|
@@ -875,6 +919,64 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
875
919
|
}
|
|
876
920
|
}
|
|
877
921
|
|
|
922
|
+
// Conflict detection runs after preUpdate so that unauthorized mutations
|
|
923
|
+
// are rejected before we leak document data in a 409 response.
|
|
924
|
+
const preciseUnmodifiedSince = req.headers["x-unmodified-since-iso"];
|
|
925
|
+
const httpUnmodifiedSince = req.headers["if-unmodified-since"];
|
|
926
|
+
const timestampValue = Array.isArray(preciseUnmodifiedSince)
|
|
927
|
+
? preciseUnmodifiedSince[0]
|
|
928
|
+
: preciseUnmodifiedSince;
|
|
929
|
+
const httpTimestampValue = Array.isArray(httpUnmodifiedSince)
|
|
930
|
+
? httpUnmodifiedSince[0]
|
|
931
|
+
: httpUnmodifiedSince;
|
|
932
|
+
if (timestampValue || httpTimestampValue || bodyUpdatedAt) {
|
|
933
|
+
const usingPreciseHeader = Boolean(timestampValue);
|
|
934
|
+
const usingHttpHeader = !usingPreciseHeader && Boolean(httpTimestampValue);
|
|
935
|
+
const clientTimestamp = timestampValue
|
|
936
|
+
? DateTime.fromISO(timestampValue)
|
|
937
|
+
: httpTimestampValue
|
|
938
|
+
? DateTime.fromHTTP(httpTimestampValue)
|
|
939
|
+
: DateTime.fromISO(bodyUpdatedAt);
|
|
940
|
+
|
|
941
|
+
if (!clientTimestamp.isValid) {
|
|
942
|
+
throw new APIError({
|
|
943
|
+
detail: usingPreciseHeader
|
|
944
|
+
? "X-Unmodified-Since-ISO header could not be parsed as an ISO date"
|
|
945
|
+
: usingHttpHeader
|
|
946
|
+
? "If-Unmodified-Since header could not be parsed as an HTTP date"
|
|
947
|
+
: "_updatedAt body field could not be parsed as an ISO date",
|
|
948
|
+
status: 400,
|
|
949
|
+
title: "Invalid conflict-detection timestamp",
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const docRecord = doc as {created?: Date | string; updated?: Date | string};
|
|
954
|
+
let serverTimestamp: DateTime | null = null;
|
|
955
|
+
const serverTimestampValue = docRecord.updated ?? docRecord.created;
|
|
956
|
+
if (serverTimestampValue instanceof Date) {
|
|
957
|
+
serverTimestamp = DateTime.fromJSDate(serverTimestampValue);
|
|
958
|
+
} else if (typeof serverTimestampValue === "string") {
|
|
959
|
+
serverTimestamp = DateTime.fromISO(serverTimestampValue);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (serverTimestamp && !serverTimestamp.isValid) {
|
|
963
|
+
throw new APIError({
|
|
964
|
+
detail: "Document timestamp could not be parsed as a date",
|
|
965
|
+
status: 400,
|
|
966
|
+
title: "Invalid server timestamp",
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (serverTimestamp && clientTimestamp < serverTimestamp) {
|
|
971
|
+
const serialized = await responseHandler(doc, "update", req, options);
|
|
972
|
+
return res.status(409).json({
|
|
973
|
+
data: serialized,
|
|
974
|
+
error: "Conflict",
|
|
975
|
+
message: "Document was modified since your last read",
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
878
980
|
// Make a copy for passing pre-saved values to hooks.
|
|
879
981
|
const prevDoc = cloneDeep(doc);
|
|
880
982
|
|
|
@@ -883,16 +985,17 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
883
985
|
try {
|
|
884
986
|
doc.set(body);
|
|
885
987
|
await doc.save();
|
|
886
|
-
} catch (error:
|
|
988
|
+
} catch (error: unknown) {
|
|
887
989
|
throw new APIError({
|
|
888
990
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
889
991
|
error,
|
|
890
992
|
status: 400,
|
|
891
|
-
title: `preUpdate hook save error on ${req.params.id}: ${error
|
|
993
|
+
title: `preUpdate hook save error on ${req.params.id}: ${errorMessage(error)}`,
|
|
892
994
|
});
|
|
893
995
|
}
|
|
894
996
|
|
|
895
997
|
if (options.populatePaths) {
|
|
998
|
+
// biome-ignore lint/suspicious/noExplicitAny: mongoose Query type varies based on populatePaths
|
|
896
999
|
let populateQuery: any = model.findById(doc._id);
|
|
897
1000
|
populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
|
|
898
1001
|
doc = await populateQuery.exec();
|
|
@@ -901,12 +1004,12 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
901
1004
|
if (options.postUpdate) {
|
|
902
1005
|
try {
|
|
903
1006
|
await options.postUpdate(doc, body, req, prevDoc);
|
|
904
|
-
} catch (error:
|
|
1007
|
+
} catch (error: unknown) {
|
|
905
1008
|
throw new APIError({
|
|
906
1009
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
907
1010
|
error,
|
|
908
1011
|
status: 400,
|
|
909
|
-
title: `postUpdate hook error on ${req.params.id}: ${error
|
|
1012
|
+
title: `postUpdate hook error on ${req.params.id}: ${errorMessage(error)}`,
|
|
910
1013
|
});
|
|
911
1014
|
}
|
|
912
1015
|
}
|
|
@@ -914,11 +1017,11 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
914
1017
|
try {
|
|
915
1018
|
const serialized = await responseHandler(doc, "update", req, options);
|
|
916
1019
|
return res.json({data: serialized});
|
|
917
|
-
} catch (error:
|
|
1020
|
+
} catch (error: unknown) {
|
|
918
1021
|
throw new APIError({
|
|
919
1022
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
920
1023
|
error,
|
|
921
|
-
title: `responseHandler error: ${error
|
|
1024
|
+
title: `responseHandler error: ${errorMessage(error)}`,
|
|
922
1025
|
});
|
|
923
1026
|
}
|
|
924
1027
|
})
|
|
@@ -932,13 +1035,15 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
932
1035
|
permissionMiddleware(model, options),
|
|
933
1036
|
],
|
|
934
1037
|
asyncHandler(async (req: Request, res: Response) => {
|
|
935
|
-
const doc: mongoose.Document & T & {deleted?: boolean} = (
|
|
1038
|
+
const doc: mongoose.Document & T & {deleted?: boolean} = (
|
|
1039
|
+
req as Request & {obj: mongoose.Document & T & {deleted?: boolean}}
|
|
1040
|
+
).obj;
|
|
936
1041
|
|
|
937
1042
|
if (options.preDelete) {
|
|
938
|
-
let body;
|
|
1043
|
+
let body: T | null | undefined;
|
|
939
1044
|
try {
|
|
940
1045
|
body = await options.preDelete(doc, req);
|
|
941
|
-
} catch (error:
|
|
1046
|
+
} catch (error: unknown) {
|
|
942
1047
|
if (isAPIError(error)) {
|
|
943
1048
|
throw error;
|
|
944
1049
|
}
|
|
@@ -946,7 +1051,7 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
946
1051
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
947
1052
|
error,
|
|
948
1053
|
status: 403,
|
|
949
|
-
title: `preDelete hook error on ${req.params.id}: ${error
|
|
1054
|
+
title: `preDelete hook error on ${req.params.id}: ${errorMessage(error)}`,
|
|
950
1055
|
});
|
|
951
1056
|
}
|
|
952
1057
|
if (body === undefined) {
|
|
@@ -976,12 +1081,12 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
976
1081
|
// For models without the isDeleted plugin
|
|
977
1082
|
try {
|
|
978
1083
|
await doc.deleteOne();
|
|
979
|
-
} catch (error:
|
|
1084
|
+
} catch (error: unknown) {
|
|
980
1085
|
throw new APIError({
|
|
981
1086
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
982
1087
|
error,
|
|
983
1088
|
status: 400,
|
|
984
|
-
title: error
|
|
1089
|
+
title: errorMessage(error),
|
|
985
1090
|
});
|
|
986
1091
|
}
|
|
987
1092
|
}
|
|
@@ -989,12 +1094,12 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
989
1094
|
if (options.postDelete) {
|
|
990
1095
|
try {
|
|
991
1096
|
await options.postDelete(req, doc);
|
|
992
|
-
} catch (error:
|
|
1097
|
+
} catch (error: unknown) {
|
|
993
1098
|
throw new APIError({
|
|
994
1099
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
995
1100
|
error,
|
|
996
1101
|
status: 400,
|
|
997
|
-
title: `postDelete hook error: ${error
|
|
1102
|
+
title: `postDelete hook error: ${errorMessage(error)}`,
|
|
998
1103
|
});
|
|
999
1104
|
}
|
|
1000
1105
|
}
|
|
@@ -1048,16 +1153,16 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
1048
1153
|
});
|
|
1049
1154
|
}
|
|
1050
1155
|
|
|
1051
|
-
const array = [...doc[field]];
|
|
1156
|
+
const array = [...(doc as unknown as Record<string, unknown[]>)[field]];
|
|
1052
1157
|
if (operation === "POST") {
|
|
1053
1158
|
array.push(req.body[field]);
|
|
1054
1159
|
} else if (operation === "PATCH" || operation === "DELETE") {
|
|
1055
1160
|
// Check for subschema vs String array:
|
|
1056
|
-
let index;
|
|
1161
|
+
let index: number;
|
|
1057
1162
|
if (isValidObjectId(itemId)) {
|
|
1058
|
-
index = array.findIndex((x
|
|
1163
|
+
index = array.findIndex((x) => (x as {id?: string})?.id === itemId);
|
|
1059
1164
|
} else {
|
|
1060
|
-
index = array.
|
|
1165
|
+
index = array.indexOf(itemId);
|
|
1061
1166
|
}
|
|
1062
1167
|
if (index === -1) {
|
|
1063
1168
|
throw new APIError({
|
|
@@ -1068,7 +1173,7 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
1068
1173
|
// For PATCHing an item by ID, we need to merge the objects so we don't override the _id or
|
|
1069
1174
|
// other parts of the subdocument.
|
|
1070
1175
|
if (operation === "PATCH" && isValidObjectId(itemId)) {
|
|
1071
|
-
Object.assign(array[index], req.body[field]);
|
|
1176
|
+
Object.assign(array[index] as object, req.body[field]);
|
|
1072
1177
|
} else if (operation === "PATCH") {
|
|
1073
1178
|
// For PATCHing a string array, we can replace the whole object.
|
|
1074
1179
|
array[index] = req.body[field];
|
|
@@ -1085,7 +1190,7 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
1085
1190
|
|
|
1086
1191
|
try {
|
|
1087
1192
|
body = transform<T>(options, body, "update", req.user) as Partial<T>;
|
|
1088
|
-
} catch (error:
|
|
1193
|
+
} catch (error: unknown) {
|
|
1089
1194
|
if (isAPIError(error)) {
|
|
1090
1195
|
throw error;
|
|
1091
1196
|
}
|
|
@@ -1093,19 +1198,19 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
1093
1198
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
1094
1199
|
error,
|
|
1095
1200
|
status: 403,
|
|
1096
|
-
title: error
|
|
1201
|
+
title: errorMessage(error),
|
|
1097
1202
|
});
|
|
1098
1203
|
}
|
|
1099
1204
|
|
|
1100
1205
|
if (options.preUpdate) {
|
|
1101
1206
|
try {
|
|
1102
1207
|
body = await options.preUpdate(body, req);
|
|
1103
|
-
} catch (error:
|
|
1208
|
+
} catch (error: unknown) {
|
|
1104
1209
|
throw new APIError({
|
|
1105
1210
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
1106
1211
|
error,
|
|
1107
1212
|
status: 400,
|
|
1108
|
-
title: `preUpdate hook error on ${req.params.id}: ${error
|
|
1213
|
+
title: `preUpdate hook error on ${req.params.id}: ${errorMessage(error)}`,
|
|
1109
1214
|
});
|
|
1110
1215
|
}
|
|
1111
1216
|
if (body === undefined) {
|
|
@@ -1129,28 +1234,35 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
1129
1234
|
try {
|
|
1130
1235
|
Object.assign(doc, body);
|
|
1131
1236
|
await doc.save();
|
|
1132
|
-
} catch (error:
|
|
1237
|
+
} catch (error: unknown) {
|
|
1133
1238
|
throw new APIError({
|
|
1134
1239
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
1135
1240
|
error,
|
|
1136
1241
|
status: 400,
|
|
1137
|
-
title: `PATCH Pre Update error on ${req.params.id}: ${error
|
|
1242
|
+
title: `PATCH Pre Update error on ${req.params.id}: ${errorMessage(error)}`,
|
|
1138
1243
|
});
|
|
1139
1244
|
}
|
|
1140
1245
|
|
|
1141
1246
|
if (options.postUpdate) {
|
|
1142
1247
|
try {
|
|
1143
|
-
await options.postUpdate(
|
|
1144
|
-
|
|
1248
|
+
await options.postUpdate(
|
|
1249
|
+
doc as unknown as Document<unknown, unknown, unknown> & T,
|
|
1250
|
+
body,
|
|
1251
|
+
req,
|
|
1252
|
+
prevDoc as unknown as T
|
|
1253
|
+
);
|
|
1254
|
+
} catch (error: unknown) {
|
|
1145
1255
|
throw new APIError({
|
|
1146
1256
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
1147
1257
|
error,
|
|
1148
1258
|
status: 400,
|
|
1149
|
-
title: `PATCH Post Update error on ${req.params.id}: ${error
|
|
1259
|
+
title: `PATCH Post Update error on ${req.params.id}: ${errorMessage(error)}`,
|
|
1150
1260
|
});
|
|
1151
1261
|
}
|
|
1152
1262
|
}
|
|
1153
|
-
return res.json({
|
|
1263
|
+
return res.json({
|
|
1264
|
+
data: serialize<T>(req, options, doc as unknown as Document<unknown, unknown, unknown> & T),
|
|
1265
|
+
});
|
|
1154
1266
|
}
|
|
1155
1267
|
|
|
1156
1268
|
async function arrayPost(req: Request, res: Response) {
|
|
@@ -1165,7 +1277,7 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
1165
1277
|
return arrayOperation(req, res, "DELETE");
|
|
1166
1278
|
}
|
|
1167
1279
|
// Set up routes for managing array fields. Check if there any array fields to add this for.
|
|
1168
|
-
if (Object.values(model.schema.paths).find((config
|
|
1280
|
+
if (Object.values(model.schema.paths).find((config) => config.instance === "Array")) {
|
|
1169
1281
|
router.post(
|
|
1170
1282
|
"/:id/:field",
|
|
1171
1283
|
authenticateMiddleware(options.allowAnonymous),
|
|
@@ -1243,7 +1355,10 @@ export interface AsyncHandlerOptions {
|
|
|
1243
1355
|
* }));
|
|
1244
1356
|
* ```
|
|
1245
1357
|
*/
|
|
1246
|
-
|
|
1358
|
+
// biome-ignore lint/suspicious/noExplicitAny: handlers may have narrower Request<Params> generics — Express's overload signature uses any for the same reason
|
|
1359
|
+
type AsyncHandlerFn = (req: any, res: Response, next: NextFunction) => Promise<unknown> | unknown;
|
|
1360
|
+
|
|
1361
|
+
export const asyncHandler = (fn: AsyncHandlerFn, options?: AsyncHandlerOptions) => {
|
|
1247
1362
|
// If no validation options, return simple handler
|
|
1248
1363
|
if (!options?.bodySchema && !options?.querySchema) {
|
|
1249
1364
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
@@ -1283,13 +1398,13 @@ export const asyncHandler = (fn: any, options?: AsyncHandlerOptions) => {
|
|
|
1283
1398
|
}
|
|
1284
1399
|
|
|
1285
1400
|
try {
|
|
1286
|
-
validators[index](req, res, (err?:
|
|
1401
|
+
validators[index](req, res, ((err?: unknown) => {
|
|
1287
1402
|
if (err) {
|
|
1288
1403
|
next(err);
|
|
1289
1404
|
return;
|
|
1290
1405
|
}
|
|
1291
1406
|
runValidators(index + 1);
|
|
1292
|
-
});
|
|
1407
|
+
}) as NextFunction);
|
|
1293
1408
|
} catch (err) {
|
|
1294
1409
|
next(err);
|
|
1295
1410
|
}
|