@terreno/api 0.0.18 → 0.1.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.
Files changed (48) hide show
  1. package/.claude/CLAUDE.local.md +204 -0
  2. package/.cursor/rules/00-root.mdc +338 -0
  3. package/.github/copilot-instructions.md +333 -0
  4. package/AGENTS.md +23 -3
  5. package/README.md +73 -3
  6. package/dist/api.d.ts +68 -1
  7. package/dist/api.js +139 -4
  8. package/dist/api.test.js +906 -2
  9. package/dist/auth.js +3 -1
  10. package/dist/errors.js +14 -11
  11. package/dist/example.js +7 -7
  12. package/dist/githubAuth.test.js +3 -3
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +2 -0
  15. package/dist/openApi.test.js +8 -5
  16. package/dist/openApiBuilder.d.ts +69 -1
  17. package/dist/openApiBuilder.js +109 -5
  18. package/dist/openApiValidator.d.ts +296 -0
  19. package/dist/openApiValidator.js +698 -0
  20. package/dist/openApiValidator.test.d.ts +1 -0
  21. package/dist/openApiValidator.test.js +346 -0
  22. package/dist/plugins.test.js +3 -3
  23. package/dist/terrenoPlugin.d.ts +4 -0
  24. package/dist/terrenoPlugin.js +2 -0
  25. package/dist/tests.js +34 -24
  26. package/package.json +4 -1
  27. package/src/__snapshots__/openApi.test.ts.snap +399 -0
  28. package/src/__snapshots__/openApiBuilder.test.ts.snap +108 -0
  29. package/src/api.test.ts +743 -2
  30. package/src/api.ts +209 -3
  31. package/src/auth.ts +3 -1
  32. package/src/errors.ts +14 -11
  33. package/src/example.ts +7 -7
  34. package/src/githubAuth.test.ts +3 -3
  35. package/src/index.ts +2 -0
  36. package/src/openApi.test.ts +8 -5
  37. package/src/openApiBuilder.ts +188 -15
  38. package/src/openApiValidator.test.ts +241 -0
  39. package/src/openApiValidator.ts +860 -0
  40. package/src/plugins.test.ts +3 -3
  41. package/src/terrenoPlugin.ts +5 -0
  42. package/src/tests.ts +34 -24
  43. package/.cursorrules +0 -107
  44. package/.windsurfrules +0 -107
  45. package/dist/response.d.ts +0 -0
  46. package/dist/response.js +0 -1
  47. package/index.ts +0 -1
  48. package/src/response.ts +0 -0
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,6 +228,62 @@ function checkQueryParamAllowed(queryParam, queryParamValue, queryFields) {
227
228
  //
228
229
  // return result;
229
230
  // }
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
+ }
230
287
  /**
231
288
  * Create a set of CRUD routes given a Mongoose model and configuration options.
232
289
  *
@@ -242,10 +299,15 @@ function modelRouter(model, options) {
242
299
  options.endpoints(router);
243
300
  }
244
301
  var responseHandler = (_a = options.responseHandler) !== null && _a !== void 0 ? _a : transformers_1.defaultResponseHandler;
302
+ // Always install validation middleware — they are no-ops until configureOpenApiValidator() is called
303
+ var createValidation = getBodyValidationMiddleware(model, options, "create");
304
+ var updateValidation = getBodyValidationMiddleware(model, options, "update");
305
+ var queryValidation = getQueryValidationMiddleware(model, options);
245
306
  router.post("/", [
246
307
  (0, auth_1.authenticateMiddleware)(options.allowAnonymous),
247
308
  (0, openApi_1.createOpenApiMiddleware)(model, options),
248
309
  (0, permissions_1.permissionMiddleware)(model, options),
310
+ createValidation,
249
311
  ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
250
312
  var body, error_1, data, error_2, populateQuery, error_3, error_4, serialized, error_5;
251
313
  return __generator(this, function (_a) {
@@ -378,6 +440,7 @@ function modelRouter(model, options) {
378
440
  (0, auth_1.authenticateMiddleware)(options.allowAnonymous),
379
441
  (0, permissions_1.permissionMiddleware)(model, options),
380
442
  (0, openApi_1.listOpenApiMiddleware)(model, options),
443
+ queryValidation,
381
444
  ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
382
445
  var query, _a, _b, queryParam, _c, _d, queryParam, queryFilter, error_6, limit, builtQuery, total, populatedQuery, data, error_7, serialized, error_8, more, msg;
383
446
  var e_4, _e, e_5, _f;
@@ -594,6 +657,7 @@ function modelRouter(model, options) {
594
657
  (0, auth_1.authenticateMiddleware)(options.allowAnonymous),
595
658
  (0, openApi_1.patchOpenApiMiddleware)(model, options),
596
659
  (0, permissions_1.permissionMiddleware)(model, options),
660
+ updateValidation,
597
661
  ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
598
662
  var doc, body, error_10, prevDoc, error_11, populateQuery, error_12, serialized, error_13;
599
663
  var _a;
@@ -991,10 +1055,81 @@ function modelRouter(model, options) {
991
1055
  router.use(errors_1.apiErrorMiddleware);
992
1056
  return router;
993
1057
  }
994
- // Since express doesn't handle async routes well, wrap them with this function.
995
- var asyncHandler = function (fn) { return function (req, res, next) {
996
- return Promise.resolve(fn(req, res, next)).catch(next);
997
- }; };
1058
+ /**
1059
+ * Wraps async route handlers to properly catch and forward errors.
1060
+ *
1061
+ * Since Express doesn't handle async routes well, wrap them with this function.
1062
+ * Optionally supports integrated request validation.
1063
+ *
1064
+ * @param fn - The async route handler function
1065
+ * @param options - Optional configuration for validation
1066
+ * @returns Express middleware function
1067
+ *
1068
+ * @example
1069
+ * ```typescript
1070
+ * // Basic usage without validation
1071
+ * router.post("/users", asyncHandler(async (req, res) => {
1072
+ * // handler code
1073
+ * }));
1074
+ *
1075
+ * // With integrated validation
1076
+ * router.post("/users", asyncHandler(async (req, res) => {
1077
+ * // handler code - body is already validated
1078
+ * }, {
1079
+ * bodySchema: {
1080
+ * name: {type: "string", required: true},
1081
+ * email: {type: "string", format: "email", required: true},
1082
+ * },
1083
+ * validate: true,
1084
+ * }));
1085
+ * ```
1086
+ */
1087
+ var asyncHandler = function (fn, options) {
1088
+ var _a, _b;
1089
+ // If no validation options, return simple handler
1090
+ if (!(options === null || options === void 0 ? void 0 : options.bodySchema) && !(options === null || options === void 0 ? void 0 : options.querySchema)) {
1091
+ return function (req, res, next) {
1092
+ return Promise.resolve(fn(req, res, next)).catch(next);
1093
+ };
1094
+ }
1095
+ // Import validation functions dynamically to avoid circular deps at module load
1096
+ var _c = require("./openApiValidator"), validateRequestBody = _c.validateRequestBody, validateQueryParams = _c.validateQueryParams, getOpenApiValidatorConfig = _c.getOpenApiValidatorConfig;
1097
+ // Build validation middleware
1098
+ var validators = [];
1099
+ // Determine if validation should be enabled
1100
+ var shouldValidate = (_b = (_a = options.validate) !== null && _a !== void 0 ? _a : getOpenApiValidatorConfig().validateRequests) !== null && _b !== void 0 ? _b : false;
1101
+ if (shouldValidate) {
1102
+ if (options.bodySchema) {
1103
+ validators.push(validateRequestBody(options.bodySchema, { enabled: true }));
1104
+ }
1105
+ if (options.querySchema) {
1106
+ validators.push(validateQueryParams(options.querySchema, { enabled: true }));
1107
+ }
1108
+ }
1109
+ return function (req, res, next) {
1110
+ // Run validators sequentially, then the handler
1111
+ var runValidators = function (index) {
1112
+ if (index >= validators.length) {
1113
+ // All validators passed, run the actual handler
1114
+ Promise.resolve(fn(req, res, next)).catch(next);
1115
+ return;
1116
+ }
1117
+ try {
1118
+ validators[index](req, res, function (err) {
1119
+ if (err) {
1120
+ next(err);
1121
+ return;
1122
+ }
1123
+ runValidators(index + 1);
1124
+ });
1125
+ }
1126
+ catch (err) {
1127
+ next(err);
1128
+ }
1129
+ };
1130
+ runValidators(0);
1131
+ };
1132
+ };
998
1133
  exports.asyncHandler = asyncHandler;
999
1134
  // For backwards compatibility with the old names.
1000
1135
  exports.gooseRestRouter = modelRouter;