retold-data-service 1.0.4 → 2.0.1

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.
@@ -1,10 +1,4 @@
1
1
  {
2
2
  "optOut": false,
3
- "lastUpdateCheck": 1686012350867,
4
- "update": {
5
- "latest": "9.6.7",
6
- "current": "6.14.18",
7
- "type": "major",
8
- "name": "npm"
9
- }
3
+ "lastUpdateCheck": 1684950851047
10
4
  }
package/README.md CHANGED
@@ -1,37 +1,27 @@
1
1
  # Retold Data Service
2
2
 
3
- Provide a consistent back or mid-tier data service with boilerplate tooling
4
- abstracted behind configuration. Leaving a simple mechanism to add complexity
5
- where desired.
3
+ Provide a consistent back or mid-tier data service.
6
4
 
7
5
  ## Basic Usage
8
6
 
9
- This library is meant to be run in any of three types of configurations:
7
+ This library can run in any of three types of configurations:
10
8
 
11
9
  * Configuration Driven
12
10
  * Managed via Backplane Endpoints
13
11
  * Hybrid (configuration plus endpoint)
14
12
 
15
- Currently only configuration driven mode is implemented.
16
-
17
13
  ## Configuration-Driven Mode
18
14
 
19
- By default this service looks in a relative `model/Model-Extended.json` from the CWD
20
- for the schemas.
21
-
22
- Default configuration:
23
-
24
- ```js
25
- {
26
- FullMeadowSchemaPath: `${process.cwd()}/model/`,
27
- FullMeadowSchemaFilename: `Model-Extended.json`,
15
+ The service looks for a `Retold` stanza in the configuration. For instance:
28
16
 
29
- DALMeadowSchemaPath: `${process.cwd()}/model/meadow/`,
30
- DALMeadowSchemaPrefix: `Model-MeadowSchema-`,
31
- DALMeadowSchemaPostfix: ``,
32
-
33
- AutoStartOrator: true
34
- })
17
+ ```
18
+ {
19
+ ...
20
+ "Retold": {
21
+ "MeadowModel": "./meadow-schema-extended.json"
22
+ },
23
+ ...
24
+ }
35
25
  ```
36
26
 
37
27
  The three auto-configured parameters are:
package/debug/Harness.js CHANGED
@@ -3,51 +3,38 @@ const _Settings = require('./bookstore-configuration.json');
3
3
  const libFable = require('fable');
4
4
 
5
5
  _Fable = new libFable(_Settings);
6
- let libRetoldDataService = _Fable.serviceManager.addAndInstantiateServiceType('RetoldDataService', require('../source/Retold-Data-Service.js'));
7
6
 
8
- libRetoldDataService.onAfterInitializeAsync =
9
- (fAfterInitializeComplete) =>
10
- {
11
- // Create a post operation behavior for the book Read singular record endpoint only
12
- _Fable.MeadowEndpoints.Book.controller.BehaviorInjection.setBehavior('Read-PostOperation',
13
- (pRequest, pRequestState, fRequestComplete) =>
14
- {
15
- // Get the join records
16
- _Fable.DAL.BookAuthorJoin.doReads(_Fable.DAL.BookAuthorJoin.query.addFilter('IDBook', pRequestState.Record.IDBook),
17
- (pJoinReadError, pJoinReadQuery, pJoinRecords)=>
18
- {
19
- let tmpAuthorList = [];
20
- for (let j = 0; j < pJoinRecords.length; j++)
21
- {
22
- tmpAuthorList.push(pJoinRecords[j].IDAuthor);
23
- }
24
- if (tmpAuthorList.length < 1)
25
- {
26
- _Fable.log.trace(`Found no authors for IDBook ${pRequestState.Record.IDBook} (${pRequestState.Record.Title}). What even is a book without authors?`)
27
- pRequestState.Record.Authors = [];
28
- return fRequestComplete();
29
- }
30
- else
31
- {
32
- _Fable.DAL.Author.doReads(_Fable.DAL.Author.query.addFilter('IDAuthor', tmpAuthorList, 'IN'),
33
- (pReadsError, pReadsQuery, pAuthors)=>
34
- {
35
- pRequestState.Record.Authors = pAuthors;
36
- _Fable.log.info(`Found ${pAuthors.length} authors for IDBook ${pRequestState.Record.IDBook} (${pRequestState.Record.Title}).`)
37
- return fRequestComplete();
38
- });
39
- }
40
- });
41
- });
7
+ _Fable.serviceManager.addAndInstantiateServiceType('RetoldDataService', require('../source/Retold-Data-Service.js'));
42
8
 
43
- return fAfterInitializeComplete();
44
- };
45
-
46
- libRetoldDataService.initializeAsync(
47
- (pError) =>
48
- {
49
- if (pError)
50
- {
51
- _Fable.log.error(`Error initializing `)
52
- }
53
- });
9
+ _Fable.RetoldDataService.initializeService(
10
+ (pError) =>
11
+ {
12
+ _Fable.MeadowEndpoints.Book.controller.BehaviorInjection.setBehavior('Read-PostOperation',
13
+ (pRequest, pRequestState, fComplete) =>
14
+ {
15
+ // Get the join records
16
+ _Fable.DAL.BookAuthorJoin.doReads(_Fable.DAL.BookAuthorJoin.query.addFilter('IDBook', pRequestState.Record.IDBook),
17
+ (pJoinReadError, pJoinReadQuery, pJoinRecords)=>
18
+ {
19
+ let tmpAuthorList = [];
20
+ for (let j = 0; j < pJoinRecords.length; j++)
21
+ {
22
+ tmpAuthorList.push(pJoinRecords[j].IDAuthor);
23
+ }
24
+ if (tmpAuthorList.length < 1)
25
+ {
26
+ pRequestState.Record.Authors = [];
27
+ return fComplete();
28
+ }
29
+ else
30
+ {
31
+ _Fable.DAL.Author.doReads(_Fable.DAL.Author.query.addFilter('IDAuthor', tmpAuthorList, 'IN'),
32
+ (pReadsError, pReadsQuery, pAuthors)=>
33
+ {
34
+ pRequestState.Record.Authors = pAuthors;
35
+ return fComplete();
36
+ });
37
+ }
38
+ });
39
+ });
40
+ });
@@ -14,7 +14,7 @@
14
14
  }
15
15
  ],
16
16
 
17
- "APIServerPort": 7765,
17
+ "APIServerPort": 8087,
18
18
 
19
19
  "MySQL":
20
20
  {
@@ -25,5 +25,6 @@
25
25
  "Database": "bookstore",
26
26
  "ConnectionPoolLimit": 20
27
27
  },
28
+
28
29
  "MeadowConnectionMySQLAutoConnect": true
29
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-data-service",
3
- "version": "1.0.4",
3
+ "version": "2.0.1",
4
4
  "description": "Serve up a whole model!",
5
5
  "main": "source/Retold-Data-Service.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@
9
9
  "test": "./node_modules/.bin/mocha -u tdd -R spec",
10
10
  "build": "npx quack build",
11
11
  "docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t retold-data-service-image:local",
12
- "docker-dev-run": "docker run -it -d --name retold-data-service-dev -p 44444:8080 -p 43306:3306 -p 8086:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/retold-data-service\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" retold-data-service-image:local",
12
+ "docker-dev-run": "docker run -it -d --name retold-data-service-dev -p 44444:8080 -p 43306:3306 -p 48086:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/retold-data-service\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" retold-data-service-image:local",
13
13
  "docker-dev-shell": "docker exec -it retold-data-service-dev /bin/bash"
14
14
  },
15
15
  "mocha": {
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "homepage": "https://github.com/stevenvelozo/retold-data-service",
48
48
  "devDependencies": {
49
- "quackage": "^1.0.24"
49
+ "quackage": "^1.0.28"
50
50
  },
51
51
  "dependencies": {
52
- "fable": "^3.0.96",
52
+ "fable": "^3.0.112",
53
53
  "fable-serviceproviderbase": "^3.0.12",
54
54
  "meadow": "^2.0.15",
55
55
  "meadow-connection-mysql": "^1.0.4",
56
- "meadow-endpoints": "^4.0.2",
56
+ "meadow-endpoints": "^4.0.5",
57
57
  "orator": "^4.0.2",
58
58
  "orator-serviceserver-restify": "^2.0.2"
59
59
  }
@@ -9,6 +9,7 @@ const libFableServiceProviderBase = require('fable-serviceproviderbase');
9
9
 
10
10
  const libOrator = require('orator');
11
11
  const libOratorServiceServerRestify = require('orator-serviceserver-restify');
12
+
12
13
  const libMeadow = require('meadow');
13
14
  const libMeadowEndpoints = require('meadow-endpoints');
14
15
 
@@ -21,6 +22,7 @@ const defaultDataServiceSettings = (
21
22
  DALMeadowSchemaPrefix: `Model-MeadowSchema-`,
22
23
  DALMeadowSchemaPostfix: ``,
23
24
 
25
+ AutoInitializeDataService: true,
24
26
  AutoStartOrator: true
25
27
  });
26
28
 
@@ -33,169 +35,131 @@ class RetoldDataService extends libFableServiceProviderBase
33
35
  this.serviceType = 'RetoldDataService';
34
36
 
35
37
  this.options = this.fable.Utility.extend(defaultDataServiceSettings, this.options);
38
+
39
+ // Add the restify server provider and orator base class to fable
40
+ this.fable.serviceManager.addServiceType('OratorServiceServer', libOratorServiceServerRestify);
41
+ this.fable.serviceManager.addServiceType('Orator', libOrator);
42
+
43
+ // Initialize Restify
44
+ this.fable.serviceManager.instantiateServiceProvider('OratorServiceServer', this.options);
45
+
46
+ // Initialize Orator, which will automatically use the default `OratorServiceServer` service we just instantiated
47
+ this.fable.serviceManager.instantiateServiceProvider('Orator', this.options);
48
+
49
+ // TODO: This code will be much cleaner with meadow and meadow-endpoints as services
50
+ this._Meadow = libMeadow.new(_Fable);
51
+
52
+ // Create DAL objects for each table in the schema
53
+ // These will be unnecessary when meadow and meadow-endpoints are full fledged fable services
54
+ this._DAL = {};
55
+ this._MeadowEndpoints = {};
56
+
57
+ // Decorate fable with the same -- this is a temporary hack until meadow and meadow-endpoints are full fledged fable services
58
+ this.fable.DAL = this._DAL;
59
+ this.fable.MeadowEndpoints = this._MeadowEndpoints;
60
+
61
+ // Storage for the model and entities
62
+ this.fullModel = false;
63
+ this.entityList = false;
64
+
65
+ this.serviceInitialized = false;
66
+ }
67
+
68
+ onBeforeInitialize(fCallback)
69
+ {
70
+ return fCallback();
71
+ }
72
+ onInitialize(fCallback)
73
+ {
74
+ return fCallback();
75
+ }
76
+ onAfterInitialize(fCallback)
77
+ {
78
+ return fCallback();
36
79
  }
37
80
 
38
- onBeforeInitializeAsync(fCallback)
81
+ initializePersistenceEngine(fCallback)
39
82
  {
40
- this.onBeforeInitialize();
83
+ // TODO: Change this to an option (e.g. we might want to do ALASQL)
84
+ // Load the mysql connection for meadow if it doesn't exist yet
85
+ this.fable.serviceManager.addAndInstantiateServiceType('MeadowMySQLProvider', require('meadow-connection-mysql'));
41
86
  return fCallback();
42
87
  }
43
- onBeforeInitialize()
88
+ initializeDataEndpoints(fCallback)
44
89
  {
45
- this.log.info("Retold Data Service Application is beginning initialization...");
90
+ this.fable.log.info("Retold Data Service initializing Endpoints...");
91
+
92
+ // Create DAL objects for each table in the schema
93
+
94
+ // 1. Load full compiled schema of the model from stricture
95
+ _Fable.log.info(`...loading full model stricture schema...`);
96
+ this.fullModel = require (`${this.options.FullMeadowSchemaPath}${this.options.FullMeadowSchemaFilename}`);
97
+ _Fable.log.info(`...full model stricture schema loaded.`);
98
+
99
+ // 2. Extract an array of each table in the schema
100
+ _Fable.log.info(`...getting entity list...`);
101
+ this.entityList = Object.keys(this.fullModel.Tables);
102
+
103
+ // 3. Enumerate each entry in the compiled model and load a DAL for that table
104
+ _Fable.log.info(`...initializing ${this.entityList.length} DAL objects and corresponding Meadow Endpoints...`);
105
+ for (let i = 0; i < this.entityList.length; i++)
106
+ {
107
+ // 4. Create the DAL for each entry (e.g. it would be at _DAL.Movie for the Movie entity)
108
+ let tmpDALEntityName = this.entityList[i];
109
+ let tmpDALPackageFile = `${this.options.DALMeadowSchemaPath}${this.options.DALMeadowSchemaPrefix}${tmpDALEntityName}${this.options.DALMeadowSchemaPostfix}.json`
110
+ _Fable.log.info(`Initializing the ${tmpDALEntityName} DAL from [${tmpDALPackageFile}]...`);
111
+ this._DAL[tmpDALEntityName] = this._Meadow.loadFromPackage(tmpDALPackageFile);
112
+ // 5. Tell this DAL object to use MySQL
113
+ _Fable.log.info(`...defaulting the ${tmpDALEntityName} DAL to use MySQL`);
114
+ this._DAL[tmpDALEntityName].setProvider('MySQL');
115
+ // 6. Create a Meadow Endpoints class for this DAL
116
+ _Fable.log.info(`...initializing the ${tmpDALEntityName} Meadow Endpoints to use MySQL`);
117
+ this._MeadowEndpoints[tmpDALEntityName] = libMeadowEndpoints.new(this._DAL[tmpDALEntityName]);
118
+ // 8. Expose the meadow endpoints on Orator
119
+ _Fable.log.info(`...mapping the ${tmpDALEntityName} Meadow Endpoints to Orator`);
120
+ this._MeadowEndpoints[tmpDALEntityName].connectRoutes(this.fable.Orator.webServer);
121
+ }
122
+
123
+ return fCallback();
46
124
  }
47
125
 
48
- initializeAsync(fCallback)
126
+ initializeService(fCallback)
49
127
  {
50
128
  if (this.serviceInitialized)
51
129
  {
52
- this.fable.log.error("Retold Data Service Application is being initialized but has already been initialized...");
130
+ return fCallback(new Error("Retold Data Service Application is being initialized but has already been initialized..."));
53
131
  }
54
132
  else
55
133
  {
56
- let tmpAnticipate = this.fable.instantiateServiceProviderWithoutRegistration('Anticipate');
57
- // Phase I: Allow the API host to inject behavior before anything magic happens.
58
- tmpAnticipate.anticipate(this.onBeforeInitializeAsync.bind(this));
59
- // Phase II: Make sure there is an Orator server available so we can put the behaviors somewhere
60
- tmpAnticipate.anticipate(
61
- function (fCallback)
62
- {
63
- // Check if Fable already has an Orator service provider, otherwise add one.
64
- if (!this.fable.serviceClasses.hasOwnProperty('OratorServiceServer'))
65
- {
66
- this.fable.serviceManager.addAndInstantiateServiceType('OratorServiceServer', libOratorServiceServerRestify, this.fable.settings);
67
- }
68
- else if (!this.fable.OratorServiceServer)
69
- {
70
- this.fable.serviceManager.instantiateServiceProvider('OratorServiceServer', this.fable.settings);
71
- }
72
- return fCallback();
73
- }.bind(this));
74
- tmpAnticipate.anticipate(
75
- function (fCallback)
76
- {
77
- // Check if Fable already has an Orator service provider, otherwise add one.
78
- if (!this.fable.serviceClasses.hasOwnProperty('Orator'))
79
- {
80
- this.fable.serviceManager.addAndInstantiateServiceType('Orator', libOrator, this.fable.settings);
81
- }
82
- else if (!this.fable.Orator)
83
- {
84
- this.fable.serviceManager.instantiateServiceProvider('Orator', this.fable.settings);
85
- }
86
- return fCallback();
87
- }.bind(this));
88
- // Phase III: Initialize orator
89
- tmpAnticipate.anticipate(this.fable.Orator.initialize.bind(this.fable.Orator));
90
- // Phase IV: Initialize Meadow (this will simplify greatly when meadow and meadow-endpoints are service-ized)
91
- tmpAnticipate.anticipate(
92
- function (fCallback)
93
- {
94
- // TODO: This code will be much cleaner with meadow and meadow-endpoints as a service. They are the only toolchain that is left!
95
- this._Meadow = libMeadow.new(_Fable);
134
+ let tmpAnticipate = this.fable.newAnticipate();
96
135
 
97
- // Create DAL objects for each table in the schema
98
- // These will be unnecessary when meadow and meadow-endpoints are full fledged fable services and then
99
- // refactored to auto-initialize on a schema provider.
100
- // For now, this is a mock of how we expect that service to provide interfaces to DAL objects.
101
- this._DAL = {};
102
- this.fable.DAL = this._DAL;
103
- // And a mock of how the endpoints will be expressed.
104
- this._MeadowEndpoints = {};
105
- this.fable.MeadowEndpoints = this._MeadowEndpoints;
136
+ this.fable.log.info(`The Retold Data Service is Auto Starting Orator`);
106
137
 
107
- // TODO: This goes away when the above use services in a sane fashion.
108
- // Load the mysql connection for meadow if it doesn't exist yet
109
- _Fable.serviceManager.addAndInstantiateServiceType('MeadowMySQLProvider', require('meadow-connection-mysql'));
138
+ tmpAnticipate.anticipate(this.onBeforeInitialize.bind(this));
110
139
 
111
- this.fullModel = false;
112
- this.entityList = false;
140
+ tmpAnticipate.anticipate(this.fable.Orator.startWebServer.bind(this.fable.Orator));
141
+ tmpAnticipate.anticipate(this.initializePersistenceEngine.bind(this));
113
142
 
114
- this.serviceInitialized = false;
143
+ tmpAnticipate.anticipate(this.onInitialize.bind(this));
115
144
 
116
- return fCallback();
117
- }.bind(this));
118
- // Phase V: Initialize the meat of the matter (the retold data service)
119
- tmpAnticipate.anticipate(
120
- function(fCallback)
121
- {
122
- this.fable.log.info("Retold Data Service DAL methods initializing...");
123
-
124
- // Create DAL objects for each table in the schema
125
- // 1. Load full compiled schema of the model from stricture
126
- _Fable.log.info(`...loading full model stricture schema...`);
127
- this.fullModel = require (`${this.options.FullMeadowSchemaPath}${this.options.FullMeadowSchemaFilename}`);
128
- _Fable.log.info(`...full model stricture schema loaded.`);
129
-
130
- // 2. Extract an array of each table in the schema
131
- _Fable.log.info(`...getting entity list...`);
132
- this.entityList = Object.keys(this.fullModel.Tables);
145
+ tmpAnticipate.anticipate(this.initializeDataEndpoints.bind(this));
133
146
 
134
- // 3. Enumerate each entry in the compiled model and load a DAL for that table
135
- _Fable.log.info(`...initializing ${this.entityList.length} DAL objects and corresponding Meadow Endpoints...`);
136
- for (let i = 0; i < this.entityList.length; i++)
137
- {
138
- // 4. Create the DAL for each entry (e.g. it would be at _DAL.Movie for the Movie entity)
139
- let tmpDALEntityName = this.entityList[i];
140
- let tmpDALPackageFile = `${this.options.DALMeadowSchemaPath}${this.options.DALMeadowSchemaPrefix}${tmpDALEntityName}${this.options.DALMeadowSchemaPostfix}.json`
141
- _Fable.log.info(`Initializing the ${tmpDALEntityName} DAL from [${tmpDALPackageFile}]...`);
142
- this._DAL[tmpDALEntityName] = this._Meadow.loadFromPackage(tmpDALPackageFile);
143
- // 5. Tell this DAL object to use MySQL
144
- _Fable.log.info(`...defaulting the ${tmpDALEntityName} DAL to use MySQL`);
145
- this._DAL[tmpDALEntityName].setProvider('MySQL');
146
- // 6. Create a Meadow Endpoints class for this DAL
147
- _Fable.log.info(`...initializing the ${tmpDALEntityName} Meadow Endpoints to use MySQL`);
148
- this._MeadowEndpoints[tmpDALEntityName] = libMeadowEndpoints.new(this._DAL[tmpDALEntityName]);
149
- // 8. Expose the meadow endpoints on Orator
150
- _Fable.log.info(`...mapping the ${tmpDALEntityName} Meadow Endpoints to Orator`);
151
- this._MeadowEndpoints[tmpDALEntityName].connectRoutes(this.fable.Orator.webServer);
152
- }
153
-
154
- this.serviceInitialized = true;
147
+ tmpAnticipate.anticipate(this.onAfterInitialize.bind(this));
155
148
 
156
- return fCallback();
157
- }.bind(this));
158
- // Phase VI: Optionally auto start the orator server.
159
- tmpAnticipate.anticipate(
160
- function (fCallback)
161
- {
162
- if (this.options.AutoStartOrator)
163
- {
164
- this.fable.log.info(`The Retold Data Service is Auto Starting Orator...`);
165
- this.fable.Orator.startWebServer(fCallback);
166
- }
167
- else
168
- {
169
- return fCallback();
170
- }
171
- }.bind(this));
172
- tmpAnticipate.anticipate(this.onAfterInitializeAsync.bind(this));
173
-
174
- // Now anticipate (wait for) initialization to complete.
175
149
  tmpAnticipate.wait(
176
- function (pError)
150
+ (pError)=>
177
151
  {
178
152
  if (pError)
179
153
  {
180
- let tmpErrorMessage = `Error initializing Retold Data Service: ${pError}`;
181
- this.log.error(tmpErrorMessage);
182
- // TODO: Should this bubble up? I think not.
183
- throw new Error(tmpErrorMessage);
154
+ this.log.error(`Error initializing Retold Data Service: ${pError}`);
155
+ return fCallback(pError);
184
156
  }
157
+
158
+ this.serviceInitialized = true;
185
159
  return fCallback();
186
- }.bind(this));
160
+ });
187
161
  }
188
162
  }
189
-
190
- onAfterInitializeAsync(fCallback)
191
- {
192
- this.onAfterInitialize();
193
- return fCallback();
194
- }
195
- onAfterInitialize()
196
- {
197
- this.log.info("Retold Data Service Application has completed an initialization attempt.");
198
- }
199
163
  }
200
164
 
201
165
  module.exports = RetoldDataService;
@@ -8,6 +8,7 @@
8
8
 
9
9
  var Chai = require("chai");
10
10
  var Expect = Chai.expect;
11
+ var Assert = Chai.assert;
11
12
 
12
13
  const libFable = require('fable');
13
14
 
@@ -41,13 +42,9 @@ suite
41
42
  AutoStartOrator: true
42
43
  });
43
44
 
44
- Expect(tmpRetoldDataService).to.be.an('object', 'Retold Data Service should initialize as an object.');
45
+ tmpRetoldDataService.initializeService();
45
46
 
46
- tmpRetoldDataService.initializeAsync(
47
- function(pError)
48
- {
49
- return fDone();
50
- });
47
+ return fDone();
51
48
  }
52
49
  );
53
50
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "Product": "MeadowEndpointsTest",
3
3
 
4
- "APIServerPort": 7764,
4
+ "APIServerPort": 8088,
5
5
 
6
6
  "MySQL":
7
7
  {