feat: improve monorepo dev experience

This commit is contained in:
2023-10-27 22:45:44 +02:00
parent f618725598
commit c6ab4ae48b
124 changed files with 2647 additions and 2857 deletions

View File

@@ -43,7 +43,7 @@
"test:unit": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 mocha --exit"
},
"dependencies": {
"@elastic/elasticsearch": "8.4.0",
"@elastic/elasticsearch": "8.10.0",
"@openstapps/core": "workspace:*",
"@openstapps/core-tools": "workspace:*",
"@openstapps/logger": "workspace:*",
@@ -56,6 +56,8 @@
"@types/nodemailer": "6.4.7",
"@types/promise-queue": "2.2.0",
"@types/uuid": "8.3.4",
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"body-parser": "1.20.2",
"cors": "2.8.5",
"cosmiconfig": "8.1.3",
@@ -102,16 +104,6 @@
"tsup": "6.7.0",
"typescript": "5.1.6"
},
"tsup": {
"entry": [
"src/cli.ts"
],
"sourcemap": true,
"clean": true,
"target": "es2022",
"format": "esm",
"outDir": "lib"
},
"prettier": "@openstapps/prettier-config",
"eslintConfig": {
"extends": [

View File

@@ -14,6 +14,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {
SCConfigFile,
SCNotFoundErrorResponse,
SCRequestBodyTooLargeErrorResponse,
SCSyntaxErrorResponse,
@@ -23,8 +24,7 @@ import {Logger} from '@openstapps/logger';
import cors from 'cors';
import {Express} from 'express';
import morgan from 'morgan';
import path from 'path';
import {DEFAULT_TIMEOUT, isTestEnvironment, mailer, plugins, validator} from './common.js';
import {DEFAULT_TIMEOUT, isTestEnvironment, mailer, plugins} from './common.js';
import {getPrometheusMiddleware} from './middleware/prometheus.js';
import {MailQueue} from './notification/mail-queue.js';
import {bulkAddRouter} from './routes/bulk-add-route.js';
@@ -39,7 +39,7 @@ import {virtualPluginRoute} from './routes/virtual-plugin-route.js';
import {BulkStorage} from './storage/bulk-storage.js';
import {DatabaseConstructor} from './storage/database.js';
import {backendConfig} from './config.js';
import {fileURLToPath} from 'url';
import {createValidator} from './validator.js';
/**
* Configure the backend
@@ -143,19 +143,10 @@ export async function configureApp(app: Express, databases: {[name: string]: Dat
request.on('data', chunkGatherer).on('end', endCallback);
});
// validate config file
const directory = path.dirname(fileURLToPath(import.meta.url));
await validator.addSchemas(
path.join(directory, '..', 'node_modules', '@openstapps', 'core', 'lib', 'schema'),
);
// validate the config file
const configValidation = validator.validate(backendConfig, 'SCConfigFile');
// validation failed
if (configValidation.errors.length > 0) {
const configFileValid = createValidator<SCConfigFile>('SCConfigFile');
if (!configFileValid(backendConfig)) {
throw new Error(
`Validation of config file failed. Errors were: ${JSON.stringify(configValidation.errors)}`,
`Validation of config file failed. Errors were: ${JSON.stringify(configFileValid.errors)}`,
);
}

View File

@@ -14,7 +14,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {SCPluginMetaData} from '@openstapps/core';
import {Validator} from '@openstapps/core-tools';
import {BackendTransport} from './notification/backend-transport.js';
/**
@@ -22,11 +21,6 @@ import {BackendTransport} from './notification/backend-transport.js';
*/
export const mailer = BackendTransport.getTransportInstance();
/**
* A validator instance to check if something is a valid JSON object (e.g. a request or a thing)
*/
export const validator = new Validator();
/**
* Provides information if the backend is executed in the "test" (non-production) environment
*/

View File

@@ -19,12 +19,12 @@ import {
SCRoute,
SCValidationErrorResponse,
} from '@openstapps/core';
import {ValidationError} from '@openstapps/core-tools/src/types/validator.js';
import {Logger} from '@openstapps/logger';
import {Application, Router} from 'express';
import PromiseRouter from 'express-promise-router';
import {isTestEnvironment, validator} from '../common.js';
import {isTestEnvironment} from '../common.js';
import {isHttpMethod} from './http-types.js';
import {createValidator} from '../validator.js';
/**
* Creates a router from a route class and a handler function which implements the logic
@@ -44,6 +44,8 @@ export function createRoute<REQUESTTYPE, RETURNTYPE>(
): Router {
// create router
const router = PromiseRouter({mergeParams: true});
const requestValidator = createValidator<REQUESTTYPE>(routeClass.requestBodyName);
const responseValidator = createValidator<RETURNTYPE>(routeClass.responseBodyName);
// create route
// the given type has no index signature so we have to cast to get the IRouteHandler when a HTTP method is given
@@ -56,11 +58,8 @@ export function createRoute<REQUESTTYPE, RETURNTYPE>(
// create a route handler for the given HTTP method
route[verb](async (request, response) => {
try {
// validate request
const requestValidation = validator.validate(request.body, routeClass.requestBodyName);
if (requestValidation.errors.length > 0) {
const error = new SCValidationErrorResponse(requestValidation.errors, isTestEnvironment);
if (!requestValidator(request.body)) {
const error = new SCValidationErrorResponse(requestValidator.errors as any, isTestEnvironment);
response.status(error.statusCode);
response.json(error);
await Logger.error(error);
@@ -68,17 +67,13 @@ export function createRoute<REQUESTTYPE, RETURNTYPE>(
return;
}
// hand over request to handler with path parameters
const handlerResponse = await handler(request.body, request.app, request.params);
// validate response generated by handler
const responseErrors: ValidationError[] = validator.validate(
handlerResponse,
routeClass.responseBodyName,
).errors;
if (responseErrors.length > 0) {
const validationError = new SCValidationErrorResponse(responseErrors, isTestEnvironment);
if (!responseValidator(handlerResponse)) {
const validationError = new SCValidationErrorResponse(
responseValidator.errors as any,
isTestEnvironment,
);
// The validation error is not caused by faulty user input, but through an error that originates somewhere in
// the backend, therefore we use this "stacked" error.
const internalServerError = new SCInternalServerErrorResponse(validationError, isTestEnvironment);

View File

@@ -17,8 +17,9 @@
import {SCInternalServerErrorResponse, SCPluginMetaData, SCValidationErrorResponse} from '@openstapps/core';
import {Request} from 'express';
import got from 'got';
import {isTestEnvironment, validator} from '../common.js';
import {isTestEnvironment} from '../common.js';
import {backendConfig} from '../config.js';
import {validator} from '../validator.js';
/**
* Generic route function used to proxy actual requests to plugins
@@ -28,10 +29,9 @@ import {backendConfig} from '../config.js';
*/
export async function virtualPluginRoute(request: Request, plugin: SCPluginMetaData): Promise<object> {
try {
const requestValidation = validator.validate(request.body, plugin.requestSchema);
if (requestValidation.errors.length > 0) {
if (!validator.validate(request.body, plugin.requestSchema)) {
// noinspection ExceptionCaughtLocallyJS
throw new SCValidationErrorResponse(requestValidation.errors, isTestEnvironment);
throw new SCValidationErrorResponse(validator.errors as any, isTestEnvironment);
}
// send the request to the plugin (forward the body) and save the response
const response = await got.post(plugin.route.replaceAll(/^\//gi, ''), {
@@ -43,10 +43,9 @@ export async function virtualPluginRoute(request: Request, plugin: SCPluginMetaD
responseType: 'json',
});
const responseBody = response.body;
const responseValidation = validator.validate(responseBody, plugin.responseSchema);
if (responseValidation.errors.length > 0) {
if (!validator.validate(responseBody, plugin.responseSchema)) {
// noinspection ExceptionCaughtLocallyJS
throw new SCValidationErrorResponse(responseValidation.errors, isTestEnvironment);
throw new SCValidationErrorResponse(validator.errors as any, isTestEnvironment);
}
return responseBody as object;
} catch (error) {

View File

@@ -0,0 +1,25 @@
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import schema from '@openstapps/core?json-schema';
export const validator = new Ajv.default({
schemas: [schema],
verbose: true,
allowUnionTypes: true,
});
addFormats.default(validator, {
formats: ['date-time', 'time', 'uuid', 'duration'],
mode: 'fast',
});
/**
* Create a validator function
* @example
* import schema from '@openstapps/core#schema:SCThings'
* createValidator<SCThings>(schema)
*/
export function createValidator<T>(schemaName: string): Ajv.ValidateFunction<T> {
return validator.compile({
$ref: `#/definitions/${schemaName}`,
});
}

View File

@@ -0,0 +1,10 @@
import {defineConfig} from 'tsup';
export default defineConfig({
entry: ['src/cli.ts'],
sourcemap: true,
clean: true,
target: 'es2022',
format: 'esm',
outDir: 'lib',
});