@vida-global/core 2.0.11 → 2.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 (45) hide show
  1. package/lib/activeRecord/README.md +1 -1
  2. package/lib/activeRecord/baseRecord.js +70 -4
  3. package/lib/server/README.md +8 -219
  4. package/lib/server/controllerImporter.js +0 -1
  5. package/lib/server/controllerMixins/callbacks.js +16 -61
  6. package/lib/server/controllerMixins/classScopedRegistry.js +48 -0
  7. package/lib/server/controllerMixins/documentation.js +41 -9
  8. package/lib/server/controllerMixins/renderer.js +140 -99
  9. package/lib/server/controllerMixins/validations.js +57 -0
  10. package/lib/server/doc/callbacks.md +23 -0
  11. package/lib/server/doc/documentation.md +135 -0
  12. package/lib/server/doc/renderer.md +115 -0
  13. package/lib/server/doc/requestDetails.md +52 -0
  14. package/lib/server/doc/validations.md +40 -0
  15. package/lib/server/index.js +1 -1
  16. package/lib/server/openApi/apiDocGenerator.js +426 -0
  17. package/lib/server/openApi/apiDocsGenerator.js +147 -0
  18. package/lib/server/openApi/schemaImporter.js +41 -0
  19. package/lib/server/openApi/schemaRegistry.js +43 -0
  20. package/lib/server/openApi/schemas.js +97 -0
  21. package/lib/server/openApi/tagRegistry.js +27 -0
  22. package/lib/server/server.js +14 -0
  23. package/lib/server/serverController.js +4 -9
  24. package/lib/server/statusTexts.js +38 -0
  25. package/lib/utils/yamlLoader.js +17 -0
  26. package/package.json +4 -2
  27. package/test/activeRecord/baseRecord.test.js +101 -0
  28. package/test/server/apiDocGenerator.test.js +743 -0
  29. package/test/server/controllerMixins/callbacks.test.js +326 -0
  30. package/test/server/controllerMixins/classScopedRegistry.test.js +95 -0
  31. package/test/server/controllerMixins/documentation.test.js +168 -0
  32. package/test/server/controllerMixins/renderer.test.js +734 -0
  33. package/test/server/controllerMixins/requestDetails.test.js +225 -0
  34. package/test/server/controllerMixins/routing.test.js +82 -0
  35. package/test/server/controllerMixins/validations.test.js +509 -0
  36. package/test/server/openApi/apiDocsGenerator.test.js +306 -0
  37. package/test/server/openApi/helpers/apiDocsPackageFixture/package.json +5 -0
  38. package/test/server/openApi/schemaImporter.test.js +54 -0
  39. package/test/server/openApi/schemaRegistry.test.js +93 -0
  40. package/test/server/openApi/schemas.test.js +109 -0
  41. package/test/server/serverController.test.js +8 -867
  42. package/test/server/statusTexts.test.js +30 -0
  43. package/test/utils/yamlLoader.test.js +43 -0
  44. package/lib/server/apiDocsGenerator.js +0 -86
  45. package/test/server/apiDocsGenerator.test.js +0 -38
@@ -0,0 +1,30 @@
1
+ const { STATUS_TEXTS, statusTextFor } = require('../../lib/server/statusTexts');
2
+
3
+
4
+ describe('statusTextFor', () => {
5
+ it.each([
6
+ [200, 'ok'],
7
+ [201, 'created'],
8
+ [204, 'no content'],
9
+ [301, 'moved permanently'],
10
+ [400, 'bad request'],
11
+ [401, 'unauthorized'],
12
+ [404, 'not found'],
13
+ [422, 'unprocessable content'],
14
+ [429, 'too many requests'],
15
+ [500, 'server error'],
16
+ [503, 'service unavailable'],
17
+ ])('returns the text for status code %i', (code, expected) => {
18
+ expect(statusTextFor(code)).toBe(expected);
19
+ });
20
+
21
+
22
+ it ('returns undefined for an unknown status code', () => {
23
+ expect(statusTextFor(799)).toBeUndefined();
24
+ });
25
+
26
+
27
+ it ('exposes the underlying map', () => {
28
+ expect(STATUS_TEXTS[200]).toBe('ok');
29
+ });
30
+ });
@@ -0,0 +1,43 @@
1
+ jest.mock('fs');
2
+
3
+ const fs = require('fs');
4
+ const { YamlLoader } = require('../../lib/utils/yamlLoader');
5
+
6
+
7
+ afterEach(() => jest.resetAllMocks());
8
+
9
+
10
+ describe('YamlLoader', () => {
11
+ describe('.loadFile', () => {
12
+ it ('parses YAML content into an object', () => {
13
+ fs.readFileSync.mockReturnValue('getIndex:\n summary: List\n');
14
+ expect(YamlLoader.loadFile('/docs.yaml')).toEqual({ getIndex: { summary: 'List' } });
15
+ });
16
+
17
+
18
+ it ('returns an empty object for an empty file', () => {
19
+ fs.readFileSync.mockReturnValue('');
20
+ expect(YamlLoader.loadFile('/empty.yaml')).toEqual({});
21
+ });
22
+
23
+
24
+ it ('throws on malformed YAML', () => {
25
+ fs.readFileSync.mockReturnValue('foo: [unclosed');
26
+ expect(() => YamlLoader.loadFile('/bad.yaml')).toThrow();
27
+ });
28
+ });
29
+
30
+
31
+ describe('.fileExists', () => {
32
+ it ('returns true when the file is present', () => {
33
+ fs.existsSync.mockReturnValue(true);
34
+ expect(YamlLoader.fileExists('/docs.yaml')).toBe(true);
35
+ });
36
+
37
+
38
+ it ('returns false when the file is absent', () => {
39
+ fs.existsSync.mockReturnValue(false);
40
+ expect(YamlLoader.fileExists('/missing.yaml')).toBe(false);
41
+ });
42
+ });
43
+ });
@@ -1,86 +0,0 @@
1
- const { ControllerImporter } = require('./controllerImporter');
2
-
3
-
4
- class ApiDocsGenerator {
5
- #controllerDirectories
6
- #controllerClasses;
7
- #docs = {};
8
-
9
- constructor(server) {
10
- this.#controllerDirectories = server.controllerDirectories;
11
- }
12
-
13
-
14
- generateDocs() {
15
- console.log("\n\n");
16
- this.controllerClasses.forEach(controllerClass => {
17
- controllerClass.actions.forEach(action => {
18
- this.generateDocsForAction(action, controllerClass);
19
- });
20
- });
21
- console.log("\n");
22
- }
23
-
24
-
25
- generateDocsForAction(action, controllerClass) {
26
- const documentationDetails = controllerClass.documentationForAction(action.action);
27
- if (!documentationDetails?.description) return;
28
-
29
- const components = this.collectComponents(controllerClass, documentationDetails, action);
30
-
31
- const endpoint = components.endpoint;
32
- this.#docs[endpoint] = this.#docs[endpoint] || {};
33
- const actionDocs = this.#docs[endpoint][action.method.toLowerCase()] = {};
34
-
35
- }
36
-
37
-
38
- collectComponents(controllerClass, documentationDetails, action) {
39
- const components = {...documentationDetails};
40
- components.endpoint = this.formatEndpoint(action);
41
- components.method = action.method;
42
-
43
- const pathParamNames = components.endpoint.match(/{[^}]+}/g).map(p => {
44
- return p.replace(/[{}]/g, '')
45
- });
46
-
47
- const parameters = Object.entries(controllerClass.parametersForAction(action.action));
48
- const pathParams = parameters.filter(([param, _]) => pathParamNames.includes(param));
49
- const otherParams = parameters.filter(([param, _]) => !pathParamNames.includes(param));
50
- console.log(components.endpoint);
51
- console.log(pathParams);
52
- console.log(components.method);
53
- console.log(parameters);
54
- console.log(components.description);
55
- console.log(components.summary);
56
- console.log("\n");
57
- //const auth = controllerClass.authenticationDocumentation(action.action);
58
- //const method = action.method;
59
-
60
- return components;
61
- }
62
-
63
-
64
- get controllerClasses() {
65
- if (!this.#controllerClasses) {
66
- const controllerImporter = new ControllerImporter(this.#controllerDirectories);
67
- this.#controllerClasses = controllerImporter.controllerClasses;
68
- }
69
- return [...this.#controllerClasses];
70
- }
71
-
72
-
73
- /***********************************************************************************************
74
- * OPENAPI COMPONENTS
75
- ***********************************************************************************************/
76
- formatEndpoint(action) {
77
- const endpoint = action.path.replace(/:([^/]+)/g, '{$1}');
78
- return endpoint;
79
- }
80
-
81
- }
82
-
83
-
84
- module.exports = {
85
- ApiDocsGenerator
86
- }
@@ -1,38 +0,0 @@
1
- const { ApiDocsGenerator } = require('../../lib/server/apiDocsGenerator');
2
-
3
-
4
- describe('ApiDocsGenerator', () => {
5
- let generator;
6
-
7
- beforeEach(() => {
8
- generator = new ApiDocsGenerator({ controllerDirectories: [] });
9
- });
10
-
11
-
12
- describe('#formatEndpoint', () => {
13
- it ('replaces a single :param with {param}', () => {
14
- expect(generator.formatEndpoint({ path: '/users/:id' })).toEqual('/users/{id}');
15
- });
16
-
17
- it ('replaces multiple :params with {params}', () => {
18
- expect(generator.formatEndpoint({ path: '/users/:userId/posts/:postId' }))
19
- .toEqual('/users/{userId}/posts/{postId}');
20
- });
21
-
22
- it ('returns the path unchanged when there are no params', () => {
23
- expect(generator.formatEndpoint({ path: '/users' })).toEqual('/users');
24
- });
25
-
26
- it ('handles a trailing :param', () => {
27
- expect(generator.formatEndpoint({ path: '/foo/bar/:baz' })).toEqual('/foo/bar/{baz}');
28
- });
29
-
30
- it ('handles an empty path', () => {
31
- expect(generator.formatEndpoint({ path: '' })).toEqual('');
32
- });
33
-
34
- it ('only replaces up to the next slash', () => {
35
- expect(generator.formatEndpoint({ path: '/a/:x/b/:y' })).toEqual('/a/{x}/b/{y}');
36
- });
37
- });
38
- });