minimonolith 0.14.4 → 0.15.2
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 +6 -25
- package/package.json +6 -1
- package/src/model/loadModels.js +7 -3
- package/src/service/{registerMethods.js → back.registerMethods.js} +1 -2
- package/src/service/parseMethodCode.js +13 -0
- package/src/service/registerMethod.js +32 -0
- package/src/service/registerService.js +16 -15
- package/src/service/parseMethodPathCode.js +0 -11
package/README.md
CHANGED
|
@@ -6,25 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
In addition to its simplicity, `minimonolith` enables seamless inter-service communication within your API. This allows services to call one another's functionality without directly importing them, fostering a modular design. For example, you can call the get method of the todo service from the todoList service using SERVICES.todo.get({ id }). By registering services within the API, you can easily call their methods from other services, which not only promotes a clean architecture but also paves the way for future support of automated end-to-end testing.
|
|
8
8
|
|
|
9
|
-
## Project Structure
|
|
10
|
-
|
|
11
|
-
A typical project using `minimonolith` will have the following structure:
|
|
12
|
-
|
|
13
|
-
```go
|
|
14
|
-
.
|
|
15
|
-
├── package.json
|
|
16
|
-
├── .gitignore
|
|
17
|
-
├── .env
|
|
18
|
-
├── server.js // For local development
|
|
19
|
-
├── index.js // Root of the code in a deployed AWS Lambda
|
|
20
|
-
└── service1
|
|
21
|
-
├── index.js // Service1 method handlers are declared here
|
|
22
|
-
├── model.js // Optional: Sequelize model for Service1 is declared here
|
|
23
|
-
└── method1
|
|
24
|
-
├── index.js // Service1 Method1 handler is declared here
|
|
25
|
-
└── valid.js // Optional: Method1 handler body validation, if not empty
|
|
26
|
-
```
|
|
27
|
-
|
|
28
9
|
## Example Project
|
|
29
10
|
|
|
30
11
|
Here's an example project using `minimonolith`:
|
|
@@ -37,11 +18,11 @@ Here's an example project using `minimonolith`:
|
|
|
37
18
|
├── server.js // For local development
|
|
38
19
|
├── index.js // Root of the code in a deployed AWS Lambda
|
|
39
20
|
└── todo
|
|
40
|
-
├── index.js //
|
|
41
|
-
├── model.js //
|
|
21
|
+
├── index.js // Service 'todo' exported method handlers are declared here
|
|
22
|
+
├── model.js // Optional: Sequelize model for service 'todo' is declared here
|
|
42
23
|
└── get
|
|
43
|
-
├── index.js //
|
|
44
|
-
└── valid.js //
|
|
24
|
+
├── index.js // Service 'todo' method 'get' handler
|
|
25
|
+
└── valid.js // Optional: Method 'get' handler validation, if body not empty
|
|
45
26
|
```
|
|
46
27
|
|
|
47
28
|
### server.js
|
|
@@ -82,7 +63,7 @@ Here, we declare the method handlers for the `todo` service:
|
|
|
82
63
|
|
|
83
64
|
```js
|
|
84
65
|
// todo/index.js
|
|
85
|
-
export
|
|
66
|
+
export default ['getAll', 'get:id', 'post', 'patch:id', 'delete:id'];
|
|
86
67
|
```
|
|
87
68
|
|
|
88
69
|
### todo/model.js
|
|
@@ -91,7 +72,7 @@ In this file, we define a Sequelize model for the `todo` service:
|
|
|
91
72
|
|
|
92
73
|
```js
|
|
93
74
|
// todo/model.js
|
|
94
|
-
export
|
|
75
|
+
export default serviceName => (orm, types) => {
|
|
95
76
|
const schema = orm.define(serviceName, {
|
|
96
77
|
name: {
|
|
97
78
|
type: types.STRING,
|
package/package.json
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minimonolith",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.2",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"start:development": "nodemon --inspect=0.0.0.0 ./server.js",
|
|
9
9
|
"test": "NODE_OPTIONS=--experimental-vm-modules npx jest --coverage"
|
|
10
10
|
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"lambda-api",
|
|
13
|
+
"zod",
|
|
14
|
+
"sequelize"
|
|
15
|
+
],
|
|
11
16
|
"dependencies": {
|
|
12
17
|
"lambda-api": "^1.0.1",
|
|
13
18
|
"sequelize": "^6.30.0",
|
package/src/model/loadModels.js
CHANGED
|
@@ -6,20 +6,24 @@ const modelSchemas = {};
|
|
|
6
6
|
|
|
7
7
|
const registerModel = async (SERVICE_NAME, SERVICE_URL) => {
|
|
8
8
|
const SERVICE_MODEL_MODULE = await import(`${SERVICE_URL}model.js`);
|
|
9
|
-
modelSchemas[SERVICE_NAME] = SERVICE_MODEL_MODULE.
|
|
9
|
+
modelSchemas[SERVICE_NAME] = SERVICE_MODEL_MODULE.default(SERVICE_NAME);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const loadAndSyncModels = async API => {
|
|
13
|
-
console.log({ ROUTE_CODE, LOADING_MODELS: 'LOADING_MODELS' });
|
|
14
13
|
const MODELS = Object.keys(modelSchemas).reduce((loadedModels, serviceName) => {
|
|
14
|
+
console.log({ ROUTE_CODE, LOADING_MODEL: serviceName });
|
|
15
15
|
loadedModels[serviceName] = modelSchemas[serviceName](API.ORM, DataTypes);
|
|
16
16
|
return loadedModels;
|
|
17
17
|
}, {});
|
|
18
18
|
|
|
19
|
-
Object.entries(MODELS).forEach(([serviceName, model]) => {
|
|
19
|
+
Object.entries(MODELS).forEach(([serviceName, model]) => {
|
|
20
|
+
console.log({ ROUTE_CODE, ASSOCIATING_MODEL: serviceName });
|
|
21
|
+
model.associate(MODELS);
|
|
22
|
+
});
|
|
20
23
|
|
|
21
24
|
console.log({ ROUTE_CODE, SYNCING_ORM: 'SYNCING_ORM' });
|
|
22
25
|
await API.ORM.sync({ alter: process.env.MM_API_LOCAL_ENV ? true : false });
|
|
26
|
+
console.log({ ROUTE_CODE, SYNCING_ORM: 'DONE_SYNCING_ORM' });
|
|
23
27
|
|
|
24
28
|
return MODELS;
|
|
25
29
|
};
|
|
@@ -12,8 +12,7 @@ const registerMethods = methodPathCodes => async (SERVICE_NAME, SERVICE_URL) =>
|
|
|
12
12
|
try {
|
|
13
13
|
const methods = await methodPathCodes.reduce(async (prevMethods, METHOD_PATH_CODE) => {
|
|
14
14
|
const methods = await prevMethods;
|
|
15
|
-
|
|
16
|
-
const { METHOD_NAME, METHOD_PATH } = parseMethodPathCode(METHOD_PATH_CODE);
|
|
15
|
+
const { METHOD_NAME } = parseMethodPathCode(METHOD_PATH_CODE);
|
|
17
16
|
ROUTE_CODE = 'REGISTER_METHODS_' + getMethodRouteCode(SERVICE_NAME, METHOD_NAME);
|
|
18
17
|
console.log(' FOUND_METHOD', METHOD_NAME);
|
|
19
18
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const parseMethodCode = (SERVICE_NAME, METHOD_CODE) => {
|
|
2
|
+
const methodPathParts = METHOD_CODE.split(":");
|
|
3
|
+
const pathParams = methodPathParts.slice(1).map(param => `:${param}`);
|
|
4
|
+
const rawMethodName = methodPathParts[0];
|
|
5
|
+
const METHOD_NAME = rawMethodName[0] === '!' ? rawMethodName.substring(1) : rawMethodName;
|
|
6
|
+
|
|
7
|
+
const relativeMethodRoute = `${rawMethodName}${pathParams.length > 0 ? '/' : ''}${pathParams.join("/")}`;
|
|
8
|
+
const METHOD_ROUTE = relativeMethodRoute[0]==='!' ?
|
|
9
|
+
`/${relativeMethodRoute.substring(1)}/${SERVICE_NAME}` :
|
|
10
|
+
`/${SERVICE_NAME}/${relativeMethodRoute}`;
|
|
11
|
+
|
|
12
|
+
return { METHOD_NAME, METHOD_ROUTE };
|
|
13
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import url from 'url';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
const registerMethod = async (SERVICE_NAME, SERVICE_URL, METHOD_NAME, METHOD_ROUTE_CODE) => {
|
|
7
|
+
|
|
8
|
+
const ROUTE_CODE = 'REGISTER_METHOD_' + METHOD_ROUTE_CODE;
|
|
9
|
+
const method = { VALIDATOR: MODELS => z.undefined() };
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
console.log(' FOUND_METHOD', METHOD_NAME);
|
|
13
|
+
|
|
14
|
+
method.handler = (await import(new URL(`${METHOD_NAME}/index.js`, SERVICE_URL))).default;
|
|
15
|
+
|
|
16
|
+
const VALIDATOR_URL = new URL(`${METHOD_NAME}/valid.js`, SERVICE_URL);
|
|
17
|
+
if (fs.existsSync(url.fileURLToPath(VALIDATOR_URL))) {
|
|
18
|
+
|
|
19
|
+
console.log(' FOUND_VALIDATOR');
|
|
20
|
+
|
|
21
|
+
const VALIDATOR_BODY = (await import(VALIDATOR_URL)).default;
|
|
22
|
+
method.VALIDATOR = MODELS => z.object(VALIDATOR_BODY(MODELS)).strict();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return method;
|
|
26
|
+
|
|
27
|
+
} catch (REGISTER_METHOD_ERROR) {
|
|
28
|
+
console.error({ ROUTE_CODE, REGISTER_METHOD_ERROR, STACK_TRASE: REGISTER_METHOD_ERROR.stack });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { registerMethod };
|
|
@@ -6,8 +6,8 @@ import { registerModel } from '../model/index.js';
|
|
|
6
6
|
import { exposedMethodHandler, validatedMethodHandler } from './methodHandler.js';
|
|
7
7
|
import { getMethodType } from './getMethodType.js';
|
|
8
8
|
import { getMethodRouteCode } from './getMethodRouteCode.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { parseMethodCode } from './parseMethodCode.js';
|
|
10
|
+
import { registerMethod } from './registerMethod.js';
|
|
11
11
|
|
|
12
12
|
const ROUTE_CODE = 'REGISTER_SERVICE';
|
|
13
13
|
|
|
@@ -24,32 +24,33 @@ const registerService = async (API, SERVICE_NAME, SRC_FOLDER, MODULE_FOLDER) =>
|
|
|
24
24
|
registerModel(SERVICE_NAME, SERVICE_URL);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
await registerMethods(SERVICE_MODULE.methods)(SERVICE_NAME, SERVICE_URL);
|
|
27
|
+
console.log('METHODS_TO_BE_REGISTERED', SERVICE_MODULE.default);
|
|
29
28
|
|
|
30
|
-
API.SERVICES[SERVICE_NAME] = {};
|
|
31
|
-
|
|
32
|
-
const { METHOD_NAME,
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
API.SERVICES[SERVICE_NAME] = {}; const SERVICE_METHODS = [];
|
|
30
|
+
for (const METHOD_CODE of SERVICE_MODULE.default) {
|
|
31
|
+
const { METHOD_NAME, METHOD_ROUTE } = parseMethodCode(SERVICE_NAME, METHOD_CODE);
|
|
32
|
+
console.log('REGISTERING_ROUTE', METHOD_ROUTE);
|
|
33
|
+
|
|
34
|
+
const METHOD_ROUTE_CODE = getMethodRouteCode(SERVICE_NAME, METHOD_NAME);
|
|
35
|
+
|
|
36
|
+
SERVICE_METHODS[METHOD_CODE] = await registerMethod(SERVICE_NAME, SERVICE_URL,
|
|
37
|
+
METHOD_NAME, METHOD_ROUTE_CODE);
|
|
35
38
|
|
|
36
39
|
API.SERVICES[SERVICE_NAME][METHOD_NAME] = async (body, claims=undefined) => {
|
|
37
40
|
return await validatedMethodHandler(body, claims, API,
|
|
38
|
-
SERVICE_METHODS[
|
|
41
|
+
SERVICE_METHODS[METHOD_CODE], METHOD_ROUTE_CODE);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
const methodType = getMethodType(METHOD_NAME);
|
|
42
|
-
|
|
43
|
-
console.log('REGISTERING_ROUTE', `/${SERVICE_NAME}/${METHOD_PATH}`);
|
|
44
|
-
API[methodType](`/${SERVICE_NAME}/${METHOD_PATH}`, async (event, res) => {
|
|
45
|
+
API[methodType](METHOD_ROUTE, async (event, res) => {
|
|
45
46
|
const { body, params, queryStringParameters } = event;
|
|
46
47
|
const claims = event.requestContext.authorizer?.jwt?.claims;
|
|
47
48
|
const parsedBody = body || Object.keys(params).length > 0 || queryStringParameters ?
|
|
48
49
|
{ ...body, ...params, ...queryStringParameters } : undefined;
|
|
49
50
|
await exposedMethodHandler(parsedBody, res, claims, API,
|
|
50
|
-
SERVICE_METHODS[
|
|
51
|
+
SERVICE_METHODS[METHOD_CODE], METHOD_ROUTE_CODE);
|
|
51
52
|
});
|
|
52
|
-
}
|
|
53
|
+
};
|
|
53
54
|
} catch (SERVICE_HANDLER_ERROR) {
|
|
54
55
|
console.error({
|
|
55
56
|
ROUTE_CODE,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export const parseMethodPathCode = (METHOD_PATH_CODE) => {
|
|
2
|
-
const methodPathParts = METHOD_PATH_CODE.split(":");
|
|
3
|
-
const METHOD_NAME = methodPathParts[0];
|
|
4
|
-
const pathParams = methodPathParts.slice(1).map(param => `:${param}`);
|
|
5
|
-
const METHOD_PATH = `${METHOD_NAME}${pathParams.length > 0 ? '/' : ''}${pathParams.join("/")}`;
|
|
6
|
-
|
|
7
|
-
return {
|
|
8
|
-
METHOD_NAME,
|
|
9
|
-
METHOD_PATH
|
|
10
|
-
};
|
|
11
|
-
};
|