@terreno/api 0.0.18 → 0.2.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/README.md +73 -3
- package/dist/api.d.ts +96 -3
- package/dist/api.js +159 -11
- package/dist/api.test.js +906 -2
- package/dist/auth.js +3 -1
- package/dist/betterAuth.d.ts +91 -0
- package/dist/betterAuth.js +8 -0
- package/dist/betterAuth.test.d.ts +1 -0
- package/dist/betterAuth.test.js +181 -0
- package/dist/betterAuthApp.d.ts +22 -0
- package/dist/betterAuthApp.js +38 -0
- package/dist/betterAuthApp.test.d.ts +1 -0
- package/dist/betterAuthApp.test.js +242 -0
- package/dist/betterAuthSetup.d.ts +60 -0
- package/dist/betterAuthSetup.js +278 -0
- package/dist/betterAuthSetup.test.d.ts +1 -0
- package/dist/betterAuthSetup.test.js +684 -0
- package/dist/errors.js +14 -11
- package/dist/example.js +7 -7
- package/dist/expressServer.js +2 -2
- package/dist/githubAuth.test.js +3 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/openApi.test.js +8 -5
- package/dist/openApiBuilder.d.ts +69 -1
- package/dist/openApiBuilder.js +109 -5
- package/dist/openApiValidator.d.ts +296 -0
- package/dist/openApiValidator.js +698 -0
- package/dist/openApiValidator.test.d.ts +1 -0
- package/dist/openApiValidator.test.js +346 -0
- package/dist/plugins.test.js +3 -3
- package/dist/terrenoApp.d.ts +189 -0
- package/dist/terrenoApp.js +352 -0
- package/dist/terrenoApp.test.d.ts +1 -0
- package/dist/terrenoApp.test.js +264 -0
- package/dist/terrenoPlugin.d.ts +38 -0
- package/dist/terrenoPlugin.js +2 -0
- package/dist/tests.js +34 -24
- package/package.json +8 -2
- package/src/__snapshots__/openApi.test.ts.snap +399 -0
- package/src/__snapshots__/openApiBuilder.test.ts.snap +108 -0
- package/src/api.test.ts +743 -2
- package/src/api.ts +270 -6
- package/src/auth.ts +3 -1
- package/src/betterAuth.test.ts +160 -0
- package/src/betterAuth.ts +104 -0
- package/src/betterAuthApp.test.ts +114 -0
- package/src/betterAuthApp.ts +60 -0
- package/src/betterAuthSetup.test.ts +485 -0
- package/src/betterAuthSetup.ts +251 -0
- package/src/errors.ts +14 -11
- package/src/example.ts +7 -7
- package/src/expressServer.ts +4 -5
- package/src/githubAuth.test.ts +3 -3
- package/src/index.ts +6 -0
- package/src/openApi.test.ts +8 -5
- package/src/openApiBuilder.ts +188 -15
- package/src/openApiValidator.test.ts +241 -0
- package/src/openApiValidator.ts +860 -0
- package/src/plugins.test.ts +3 -3
- package/src/terrenoApp.test.ts +201 -0
- package/src/terrenoApp.ts +347 -0
- package/src/terrenoPlugin.ts +39 -0
- package/src/tests.ts +34 -24
- package/.cursorrules +0 -107
- package/.windsurfrules +0 -107
- package/AGENTS.md +0 -313
- package/dist/response.d.ts +0 -0
- package/dist/response.js +0 -1
- package/index.ts +0 -1
- package/src/response.ts +0 -0
package/README.md
CHANGED
|
@@ -9,6 +9,14 @@ These APIs integrate with @terreno/rtk to create consistent types on the fronten
|
|
|
9
9
|
and backend, and automatically generated React hooks to fetch, query, and modify
|
|
10
10
|
model instances.
|
|
11
11
|
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **modelRouter** — Automatic CRUD endpoints for Mongoose models
|
|
15
|
+
- **Authentication** — JWT with email/password and GitHub OAuth support
|
|
16
|
+
- **Permissions** — Fine-grained access control (IsAuthenticated, IsOwner, IsAdmin, etc.)
|
|
17
|
+
- **OpenAPI** — Automatic spec generation from models and routes
|
|
18
|
+
- **Logging** — Winston-based logging with Google Cloud and Sentry support
|
|
19
|
+
|
|
12
20
|
## Getting started
|
|
13
21
|
|
|
14
22
|
To install:
|
|
@@ -46,12 +54,25 @@ const eventSchema = new Schema({
|
|
|
46
54
|
Assuming we have a model:
|
|
47
55
|
|
|
48
56
|
const foodSchema = new Schema<Food>({
|
|
49
|
-
name:
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
name: {
|
|
58
|
+
description: "Name of the food item",
|
|
59
|
+
type: String,
|
|
60
|
+
},
|
|
61
|
+
hidden: {
|
|
62
|
+
description: "Whether the food is hidden from the list",
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
ownerId: {
|
|
67
|
+
description: "The user who added this food",
|
|
68
|
+
type: "ObjectId",
|
|
69
|
+
ref: "User",
|
|
70
|
+
},
|
|
52
71
|
});
|
|
53
72
|
export const FoodModel = model("Food", foodSchema);
|
|
54
73
|
|
|
74
|
+
**Important:** Every field must include a `description` property. This requirement ensures that the auto-generated OpenAPI specification and SDK have meaningful documentation for all fields.
|
|
75
|
+
|
|
55
76
|
We can expose this model as an API like this:
|
|
56
77
|
|
|
57
78
|
import express from "express";
|
|
@@ -99,6 +120,55 @@ Now we can perform operations on the Food model in a standard REST way. We've al
|
|
|
99
120
|
|
|
100
121
|
You can create your own permissions functions. Check permissions.ts for some examples of how to write them.
|
|
101
122
|
|
|
123
|
+
## Authentication
|
|
124
|
+
|
|
125
|
+
@terreno/api includes built-in authentication with JWT and OAuth support.
|
|
126
|
+
|
|
127
|
+
### Email/Password Authentication
|
|
128
|
+
|
|
129
|
+
Built-in email/password authentication using `passport-local-mongoose`:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import {setupServer} from "@terreno/api";
|
|
133
|
+
import {User} from "./models/user";
|
|
134
|
+
|
|
135
|
+
setupServer({
|
|
136
|
+
userModel: User,
|
|
137
|
+
addRoutes: (router) => {
|
|
138
|
+
// Your routes here
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This automatically adds:
|
|
144
|
+
- `POST /auth/signup` — User registration
|
|
145
|
+
- `POST /auth/login` — Authentication
|
|
146
|
+
- `POST /auth/refresh_token` — Token refresh
|
|
147
|
+
- `GET /auth/me` — Get current user
|
|
148
|
+
- `PATCH /auth/me` — Update current user
|
|
149
|
+
|
|
150
|
+
### GitHub OAuth
|
|
151
|
+
|
|
152
|
+
Add GitHub OAuth login to your API:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import {githubUserPlugin, setupServer} from "@terreno/api";
|
|
156
|
+
|
|
157
|
+
// Add GitHub fields to user schema
|
|
158
|
+
userSchema.plugin(githubUserPlugin);
|
|
159
|
+
|
|
160
|
+
setupServer({
|
|
161
|
+
userModel: User,
|
|
162
|
+
githubAuth: {
|
|
163
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
164
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
165
|
+
callbackURL: process.env.GITHUB_CALLBACK_URL!,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Learn more:** See the [GitHub OAuth how-to guide](../docs/how-to/add-github-oauth.md) for complete setup instructions.
|
|
171
|
+
|
|
102
172
|
## Sentry
|
|
103
173
|
To enable Sentry, create a "src/sentryInstrumment.ts" file in your project.
|
|
104
174
|
|
package/dist/api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import express, { type NextFunction, type Request, type Response } from "express";
|
|
2
2
|
import mongoose, { type Document, type Model } from "mongoose";
|
|
3
3
|
import { type User } from "./auth";
|
|
4
|
+
import { type ModelRouterValidationOptions } from "./openApiValidator";
|
|
4
5
|
import { type RESTPermissions } from "./permissions";
|
|
5
6
|
import type { PopulatePath } from "./populate";
|
|
6
7
|
import { type TerrenoTransformer } from "./transformers";
|
|
@@ -203,14 +204,106 @@ export interface ModelRouterOptions<T> {
|
|
|
203
204
|
* that you want to be documented and typed in the SDK.
|
|
204
205
|
*/
|
|
205
206
|
openApiExtraModelProperties?: any;
|
|
207
|
+
/**
|
|
208
|
+
* Enable runtime validation of request bodies against the OpenAPI schema.
|
|
209
|
+
* When enabled, requests that don't match the documented schema will return 400 errors.
|
|
210
|
+
*
|
|
211
|
+
* Can be set to:
|
|
212
|
+
* - `true`: Enable validation for create and update operations
|
|
213
|
+
* - `false`: Disable validation (default)
|
|
214
|
+
* - Object with `validateCreate` and `validateUpdate` booleans for fine-grained control
|
|
215
|
+
*
|
|
216
|
+
* Note: Global validation can be enabled via `configureOpenApiValidator()`.
|
|
217
|
+
* This option overrides the global setting for this specific router.
|
|
218
|
+
*/
|
|
219
|
+
validation?: boolean | ModelRouterValidationOptions;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Registration object returned by modelRouter when called with a path.
|
|
223
|
+
*
|
|
224
|
+
* Used with `TerrenoApp.register()` to mount model routers at specific paths.
|
|
225
|
+
* Contains the Express router and the path it should be mounted at.
|
|
226
|
+
*
|
|
227
|
+
* @see modelRouter for creating registrations
|
|
228
|
+
* @see TerrenoApp for registering routers
|
|
229
|
+
*/
|
|
230
|
+
export interface ModelRouterRegistration {
|
|
231
|
+
/** Internal type discriminator for registration detection */
|
|
232
|
+
__type: "modelRouter";
|
|
233
|
+
/** The path where the router should be mounted (e.g., "/todos") */
|
|
234
|
+
path: string;
|
|
235
|
+
/** The Express router containing CRUD endpoints */
|
|
236
|
+
router: express.Router;
|
|
206
237
|
}
|
|
207
238
|
/**
|
|
208
239
|
* Create a set of CRUD routes given a Mongoose model and configuration options.
|
|
209
240
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
241
|
+
* When called with a path as the first argument, returns a `ModelRouterRegistration` that can be
|
|
242
|
+
* passed to `TerrenoApp.register()`.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* // Traditional usage (returns express.Router):
|
|
246
|
+
* router.use("/todos", modelRouter(Todo, options));
|
|
247
|
+
*
|
|
248
|
+
* // Registration usage (returns ModelRouterRegistration):
|
|
249
|
+
* const todoRouter = modelRouter("/todos", Todo, options);
|
|
250
|
+
* app.register(todoRouter);
|
|
212
251
|
*/
|
|
252
|
+
export declare function modelRouter<T>(path: string, model: Model<T>, options: ModelRouterOptions<T>): ModelRouterRegistration;
|
|
213
253
|
export declare function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>): express.Router;
|
|
214
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Options for the asyncHandler function.
|
|
256
|
+
*/
|
|
257
|
+
export interface AsyncHandlerOptions {
|
|
258
|
+
/**
|
|
259
|
+
* Schema for validating request body.
|
|
260
|
+
* When provided and validation is enabled, the request body will be validated
|
|
261
|
+
* against this schema before the handler runs.
|
|
262
|
+
*/
|
|
263
|
+
bodySchema?: Record<string, import("./openApiBuilder").OpenApiSchemaProperty>;
|
|
264
|
+
/**
|
|
265
|
+
* Schema for validating query parameters.
|
|
266
|
+
* When provided and validation is enabled, query params will be validated
|
|
267
|
+
* against this schema before the handler runs.
|
|
268
|
+
*/
|
|
269
|
+
querySchema?: Record<string, import("./openApiBuilder").OpenApiSchemaProperty>;
|
|
270
|
+
/**
|
|
271
|
+
* Override global validation setting for this handler.
|
|
272
|
+
* - `true`: Enable validation regardless of global setting
|
|
273
|
+
* - `false`: Disable validation regardless of global setting
|
|
274
|
+
* - `undefined`: Use global setting
|
|
275
|
+
*/
|
|
276
|
+
validate?: boolean;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Wraps async route handlers to properly catch and forward errors.
|
|
280
|
+
*
|
|
281
|
+
* Since Express doesn't handle async routes well, wrap them with this function.
|
|
282
|
+
* Optionally supports integrated request validation.
|
|
283
|
+
*
|
|
284
|
+
* @param fn - The async route handler function
|
|
285
|
+
* @param options - Optional configuration for validation
|
|
286
|
+
* @returns Express middleware function
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* // Basic usage without validation
|
|
291
|
+
* router.post("/users", asyncHandler(async (req, res) => {
|
|
292
|
+
* // handler code
|
|
293
|
+
* }));
|
|
294
|
+
*
|
|
295
|
+
* // With integrated validation
|
|
296
|
+
* router.post("/users", asyncHandler(async (req, res) => {
|
|
297
|
+
* // handler code - body is already validated
|
|
298
|
+
* }, {
|
|
299
|
+
* bodySchema: {
|
|
300
|
+
* name: {type: "string", required: true},
|
|
301
|
+
* email: {type: "string", format: "email", required: true},
|
|
302
|
+
* },
|
|
303
|
+
* validate: true,
|
|
304
|
+
* }));
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
export declare const asyncHandler: (fn: any, options?: AsyncHandlerOptions) => (req: Request, res: Response, next: NextFunction) => void;
|
|
215
308
|
export declare const gooseRestRouter: typeof modelRouter;
|
|
216
309
|
export type GooseRESTOptions<T> = ModelRouterOptions<T>;
|
package/dist/api.js
CHANGED
|
@@ -135,6 +135,7 @@ var auth_1 = require("./auth");
|
|
|
135
135
|
var errors_1 = require("./errors");
|
|
136
136
|
var logger_1 = require("./logger");
|
|
137
137
|
var openApi_1 = require("./openApi");
|
|
138
|
+
var openApiValidator_1 = require("./openApiValidator");
|
|
138
139
|
var permissions_1 = require("./permissions");
|
|
139
140
|
var transformers_1 = require("./transformers");
|
|
140
141
|
var utils_1 = require("./utils");
|
|
@@ -227,13 +228,82 @@ function checkQueryParamAllowed(queryParam, queryParamValue, queryFields) {
|
|
|
227
228
|
//
|
|
228
229
|
// return result;
|
|
229
230
|
// }
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
231
|
+
// Helper to determine if validation should be enabled for a specific operation.
|
|
232
|
+
// When options.validation is not set, returns true — the middleware's own
|
|
233
|
+
// isConfigured check will decide whether to actually validate.
|
|
234
|
+
function shouldValidate(options, operation) {
|
|
235
|
+
var _a, _b, _c;
|
|
236
|
+
// Check route-specific validation option first
|
|
237
|
+
if (options.validation !== undefined) {
|
|
238
|
+
if (typeof options.validation === "boolean") {
|
|
239
|
+
return options.validation;
|
|
240
|
+
}
|
|
241
|
+
if (operation === "create") {
|
|
242
|
+
return (_a = options.validation.validateCreate) !== null && _a !== void 0 ? _a : true;
|
|
243
|
+
}
|
|
244
|
+
if (operation === "update") {
|
|
245
|
+
return (_b = options.validation.validateUpdate) !== null && _b !== void 0 ? _b : true;
|
|
246
|
+
}
|
|
247
|
+
return (_c = options.validation.validateQuery) !== null && _c !== void 0 ? _c : true;
|
|
248
|
+
}
|
|
249
|
+
// Default: let middleware's isConfigured check decide
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
// Get body validation middleware if validation is enabled
|
|
253
|
+
function getBodyValidationMiddleware(model, options, operation) {
|
|
254
|
+
var validationOptions = {};
|
|
255
|
+
if (!shouldValidate(options, operation)) {
|
|
256
|
+
validationOptions.enabled = false;
|
|
257
|
+
}
|
|
258
|
+
if (typeof options.validation === "object") {
|
|
259
|
+
if (options.validation.onError) {
|
|
260
|
+
validationOptions.onError = options.validation.onError;
|
|
261
|
+
}
|
|
262
|
+
if (options.validation.onAdditionalPropertiesRemoved) {
|
|
263
|
+
validationOptions.onAdditionalPropertiesRemoved =
|
|
264
|
+
options.validation.onAdditionalPropertiesRemoved;
|
|
265
|
+
}
|
|
266
|
+
var excludeFields = operation === "create"
|
|
267
|
+
? options.validation.excludeFromCreate
|
|
268
|
+
: options.validation.excludeFromUpdate;
|
|
269
|
+
if (excludeFields === null || excludeFields === void 0 ? void 0 : excludeFields.length) {
|
|
270
|
+
validationOptions.excludeFields = excludeFields;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return (0, openApiValidator_1.validateModelRequestBody)(model, validationOptions);
|
|
274
|
+
}
|
|
275
|
+
// Get query validation middleware if validation is enabled
|
|
276
|
+
function getQueryValidationMiddleware(model, options) {
|
|
277
|
+
var querySchema = (0, openApiValidator_1.buildQuerySchemaFromFields)(model, options.queryFields);
|
|
278
|
+
var validationOptions = {};
|
|
279
|
+
if (!shouldValidate(options, "query")) {
|
|
280
|
+
validationOptions.enabled = false;
|
|
281
|
+
}
|
|
282
|
+
if (typeof options.validation === "object" && options.validation.onError) {
|
|
283
|
+
validationOptions.onError = options.validation.onError;
|
|
284
|
+
}
|
|
285
|
+
return (0, openApiValidator_1.validateQueryParams)(querySchema, validationOptions);
|
|
286
|
+
}
|
|
287
|
+
function modelRouter(pathOrModel, modelOrOptions, maybeOptions) {
|
|
288
|
+
var model;
|
|
289
|
+
var options;
|
|
290
|
+
var path;
|
|
291
|
+
if (typeof pathOrModel === "string") {
|
|
292
|
+
path = pathOrModel;
|
|
293
|
+
model = modelOrOptions;
|
|
294
|
+
options = maybeOptions;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
model = pathOrModel;
|
|
298
|
+
options = modelOrOptions;
|
|
299
|
+
}
|
|
300
|
+
var router = _buildModelRouter(model, options);
|
|
301
|
+
if (path !== undefined) {
|
|
302
|
+
return { __type: "modelRouter", path: path, router: router };
|
|
303
|
+
}
|
|
304
|
+
return router;
|
|
305
|
+
}
|
|
306
|
+
function _buildModelRouter(model, options) {
|
|
237
307
|
var _this = this;
|
|
238
308
|
var _a;
|
|
239
309
|
var router = express_1.default.Router();
|
|
@@ -242,10 +312,15 @@ function modelRouter(model, options) {
|
|
|
242
312
|
options.endpoints(router);
|
|
243
313
|
}
|
|
244
314
|
var responseHandler = (_a = options.responseHandler) !== null && _a !== void 0 ? _a : transformers_1.defaultResponseHandler;
|
|
315
|
+
// Always install validation middleware — they are no-ops until configureOpenApiValidator() is called
|
|
316
|
+
var createValidation = getBodyValidationMiddleware(model, options, "create");
|
|
317
|
+
var updateValidation = getBodyValidationMiddleware(model, options, "update");
|
|
318
|
+
var queryValidation = getQueryValidationMiddleware(model, options);
|
|
245
319
|
router.post("/", [
|
|
246
320
|
(0, auth_1.authenticateMiddleware)(options.allowAnonymous),
|
|
247
321
|
(0, openApi_1.createOpenApiMiddleware)(model, options),
|
|
248
322
|
(0, permissions_1.permissionMiddleware)(model, options),
|
|
323
|
+
createValidation,
|
|
249
324
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
250
325
|
var body, error_1, data, error_2, populateQuery, error_3, error_4, serialized, error_5;
|
|
251
326
|
return __generator(this, function (_a) {
|
|
@@ -378,6 +453,7 @@ function modelRouter(model, options) {
|
|
|
378
453
|
(0, auth_1.authenticateMiddleware)(options.allowAnonymous),
|
|
379
454
|
(0, permissions_1.permissionMiddleware)(model, options),
|
|
380
455
|
(0, openApi_1.listOpenApiMiddleware)(model, options),
|
|
456
|
+
queryValidation,
|
|
381
457
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
382
458
|
var query, _a, _b, queryParam, _c, _d, queryParam, queryFilter, error_6, limit, builtQuery, total, populatedQuery, data, error_7, serialized, error_8, more, msg;
|
|
383
459
|
var e_4, _e, e_5, _f;
|
|
@@ -594,6 +670,7 @@ function modelRouter(model, options) {
|
|
|
594
670
|
(0, auth_1.authenticateMiddleware)(options.allowAnonymous),
|
|
595
671
|
(0, openApi_1.patchOpenApiMiddleware)(model, options),
|
|
596
672
|
(0, permissions_1.permissionMiddleware)(model, options),
|
|
673
|
+
updateValidation,
|
|
597
674
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
598
675
|
var doc, body, error_10, prevDoc, error_11, populateQuery, error_12, serialized, error_13;
|
|
599
676
|
var _a;
|
|
@@ -991,10 +1068,81 @@ function modelRouter(model, options) {
|
|
|
991
1068
|
router.use(errors_1.apiErrorMiddleware);
|
|
992
1069
|
return router;
|
|
993
1070
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1071
|
+
/**
|
|
1072
|
+
* Wraps async route handlers to properly catch and forward errors.
|
|
1073
|
+
*
|
|
1074
|
+
* Since Express doesn't handle async routes well, wrap them with this function.
|
|
1075
|
+
* Optionally supports integrated request validation.
|
|
1076
|
+
*
|
|
1077
|
+
* @param fn - The async route handler function
|
|
1078
|
+
* @param options - Optional configuration for validation
|
|
1079
|
+
* @returns Express middleware function
|
|
1080
|
+
*
|
|
1081
|
+
* @example
|
|
1082
|
+
* ```typescript
|
|
1083
|
+
* // Basic usage without validation
|
|
1084
|
+
* router.post("/users", asyncHandler(async (req, res) => {
|
|
1085
|
+
* // handler code
|
|
1086
|
+
* }));
|
|
1087
|
+
*
|
|
1088
|
+
* // With integrated validation
|
|
1089
|
+
* router.post("/users", asyncHandler(async (req, res) => {
|
|
1090
|
+
* // handler code - body is already validated
|
|
1091
|
+
* }, {
|
|
1092
|
+
* bodySchema: {
|
|
1093
|
+
* name: {type: "string", required: true},
|
|
1094
|
+
* email: {type: "string", format: "email", required: true},
|
|
1095
|
+
* },
|
|
1096
|
+
* validate: true,
|
|
1097
|
+
* }));
|
|
1098
|
+
* ```
|
|
1099
|
+
*/
|
|
1100
|
+
var asyncHandler = function (fn, options) {
|
|
1101
|
+
var _a, _b;
|
|
1102
|
+
// If no validation options, return simple handler
|
|
1103
|
+
if (!(options === null || options === void 0 ? void 0 : options.bodySchema) && !(options === null || options === void 0 ? void 0 : options.querySchema)) {
|
|
1104
|
+
return function (req, res, next) {
|
|
1105
|
+
return Promise.resolve(fn(req, res, next)).catch(next);
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
// Import validation functions dynamically to avoid circular deps at module load
|
|
1109
|
+
var _c = require("./openApiValidator"), validateRequestBody = _c.validateRequestBody, validateQueryParams = _c.validateQueryParams, getOpenApiValidatorConfig = _c.getOpenApiValidatorConfig;
|
|
1110
|
+
// Build validation middleware
|
|
1111
|
+
var validators = [];
|
|
1112
|
+
// Determine if validation should be enabled
|
|
1113
|
+
var shouldValidate = (_b = (_a = options.validate) !== null && _a !== void 0 ? _a : getOpenApiValidatorConfig().validateRequests) !== null && _b !== void 0 ? _b : false;
|
|
1114
|
+
if (shouldValidate) {
|
|
1115
|
+
if (options.bodySchema) {
|
|
1116
|
+
validators.push(validateRequestBody(options.bodySchema, { enabled: true }));
|
|
1117
|
+
}
|
|
1118
|
+
if (options.querySchema) {
|
|
1119
|
+
validators.push(validateQueryParams(options.querySchema, { enabled: true }));
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return function (req, res, next) {
|
|
1123
|
+
// Run validators sequentially, then the handler
|
|
1124
|
+
var runValidators = function (index) {
|
|
1125
|
+
if (index >= validators.length) {
|
|
1126
|
+
// All validators passed, run the actual handler
|
|
1127
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
try {
|
|
1131
|
+
validators[index](req, res, function (err) {
|
|
1132
|
+
if (err) {
|
|
1133
|
+
next(err);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
runValidators(index + 1);
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
catch (err) {
|
|
1140
|
+
next(err);
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
runValidators(0);
|
|
1144
|
+
};
|
|
1145
|
+
};
|
|
998
1146
|
exports.asyncHandler = asyncHandler;
|
|
999
1147
|
// For backwards compatibility with the old names.
|
|
1000
1148
|
exports.gooseRestRouter = modelRouter;
|