meadow-connection-mongodb 1.0.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.
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "meadow-connection-mongodb",
3
+ "version": "1.0.0",
4
+ "description": "Meadow MongoDB Connection Plugin",
5
+ "main": "source/Meadow-Connection-MongoDB.js",
6
+ "scripts": {
7
+ "coverage": "npx quack coverage",
8
+ "test": "npx quack test"
9
+ },
10
+ "mocha": {
11
+ "diff": true,
12
+ "extension": [
13
+ "js"
14
+ ],
15
+ "package": "./package.json",
16
+ "reporter": "spec",
17
+ "slow": "75",
18
+ "timeout": "5000",
19
+ "ui": "tdd",
20
+ "watch-files": [
21
+ "source/**/*.js",
22
+ "test/**/*.js"
23
+ ],
24
+ "watch-ignore": [
25
+ "lib/vendor"
26
+ ]
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/stevenvelozo/meadow-connection-mongodb.git"
31
+ },
32
+ "keywords": [
33
+ "meadow",
34
+ "mongodb",
35
+ "database"
36
+ ],
37
+ "author": "Steven Velozo <steven@velozo.com> (http://velozo.com/)",
38
+ "license": "MIT",
39
+ "bugs": {
40
+ "url": "https://github.com/stevenvelozo/meadow-connection-mongodb/issues"
41
+ },
42
+ "homepage": "https://github.com/stevenvelozo/meadow-connection-mongodb",
43
+ "devDependencies": {
44
+ "fable": "^3.1.55",
45
+ "quackage": "^1.0.58"
46
+ },
47
+ "dependencies": {
48
+ "fable-serviceproviderbase": "^3.0.18",
49
+ "mongodb": "^6.12.0"
50
+ }
51
+ }
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Meadow MongoDB Provider Fable Service
3
+ * @author Steven Velozo <steven@velozo.com>
4
+ */
5
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
6
+
7
+ const { MongoClient } = require('mongodb');
8
+
9
+ class MeadowConnectionMongoDB extends libFableServiceProviderBase
10
+ {
11
+ constructor(pFable, pManifest, pServiceHash)
12
+ {
13
+ super(pFable, pManifest, pServiceHash);
14
+
15
+ this.serviceType = 'MeadowConnectionMongoDB';
16
+
17
+ // See if the user passed in a MongoDB object already
18
+ if (typeof(this.options.MongoDB) == 'object')
19
+ {
20
+ // Support Meadow-style property names
21
+ if (!this.options.MongoDB.hasOwnProperty('host') && this.options.MongoDB.hasOwnProperty('Server'))
22
+ {
23
+ this.options.MongoDB.host = this.options.MongoDB.Server;
24
+ }
25
+ if (!this.options.MongoDB.hasOwnProperty('port') && this.options.MongoDB.hasOwnProperty('Port'))
26
+ {
27
+ this.options.MongoDB.port = this.options.MongoDB.Port;
28
+ }
29
+ if (!this.options.MongoDB.hasOwnProperty('user') && this.options.MongoDB.hasOwnProperty('User'))
30
+ {
31
+ this.options.MongoDB.user = this.options.MongoDB.User;
32
+ }
33
+ if (!this.options.MongoDB.hasOwnProperty('password') && this.options.MongoDB.hasOwnProperty('Password'))
34
+ {
35
+ this.options.MongoDB.password = this.options.MongoDB.Password;
36
+ }
37
+ if (!this.options.MongoDB.hasOwnProperty('database') && this.options.MongoDB.hasOwnProperty('Database'))
38
+ {
39
+ this.options.MongoDB.database = this.options.MongoDB.Database;
40
+ }
41
+ if (!this.options.MongoDB.hasOwnProperty('maxPoolSize') && this.options.MongoDB.hasOwnProperty('ConnectionPoolLimit'))
42
+ {
43
+ this.options.MongoDB.maxPoolSize = this.options.MongoDB.ConnectionPoolLimit;
44
+ }
45
+ }
46
+ else if (typeof(this.fable.settings.MongoDB) == 'object')
47
+ {
48
+ this.options.MongoDB = (
49
+ {
50
+ host: this.fable.settings.MongoDB.Server || '127.0.0.1',
51
+ port: this.fable.settings.MongoDB.Port || 27017,
52
+ user: this.fable.settings.MongoDB.User || '',
53
+ password: this.fable.settings.MongoDB.Password || '',
54
+ database: this.fable.settings.MongoDB.Database || 'test',
55
+ maxPoolSize: this.fable.settings.MongoDB.ConnectionPoolLimit || 10
56
+ });
57
+ }
58
+
59
+ if (!this.options.MeadowConnectionMongoDBAutoConnect)
60
+ {
61
+ this.options.MeadowConnectionMongoDBAutoConnect = this.fable.settings.MeadowConnectionMongoDBAutoConnect;
62
+ }
63
+
64
+ this.serviceType = 'MeadowConnectionMongoDB';
65
+ this._Client = false;
66
+ this._Database = false;
67
+ this.connected = false;
68
+
69
+ if (this.options.MeadowConnectionMongoDBAutoConnect)
70
+ {
71
+ this.connect();
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Build the MongoDB connection URI from options.
77
+ */
78
+ _buildConnectionURI()
79
+ {
80
+ let tmpOptions = this.options.MongoDB || {};
81
+ let tmpHost = tmpOptions.host || '127.0.0.1';
82
+ let tmpPort = tmpOptions.port || 27017;
83
+ let tmpUser = tmpOptions.user || '';
84
+ let tmpPassword = tmpOptions.password || '';
85
+ let tmpDatabase = tmpOptions.database || 'test';
86
+
87
+ if (tmpUser && tmpPassword)
88
+ {
89
+ return `mongodb://${encodeURIComponent(tmpUser)}:${encodeURIComponent(tmpPassword)}@${tmpHost}:${tmpPort}/${tmpDatabase}`;
90
+ }
91
+ else
92
+ {
93
+ return `mongodb://${tmpHost}:${tmpPort}/${tmpDatabase}`;
94
+ }
95
+ }
96
+
97
+ generateDropTableStatement(pTableName)
98
+ {
99
+ // Returns a descriptor; the actual drop uses the MongoDB driver
100
+ return { operation: 'drop', collection: pTableName };
101
+ }
102
+
103
+ generateCreateTableStatement(pMeadowTableSchema)
104
+ {
105
+ this.log.info(`--> Building the collection creation descriptor for ${pMeadowTableSchema.TableName} ...`);
106
+
107
+ let tmpDescriptor = {
108
+ operation: 'createCollection',
109
+ collection: pMeadowTableSchema.TableName,
110
+ indexes: []
111
+ };
112
+
113
+ for (let j = 0; j < pMeadowTableSchema.Columns.length; j++)
114
+ {
115
+ let tmpColumn = pMeadowTableSchema.Columns[j];
116
+
117
+ switch (tmpColumn.DataType)
118
+ {
119
+ case 'ID':
120
+ // AutoIdentity field gets a unique ascending index
121
+ tmpDescriptor.indexes.push(
122
+ {
123
+ key: { [tmpColumn.Column]: 1 },
124
+ unique: true,
125
+ name: `idx_${tmpColumn.Column}_unique`
126
+ });
127
+ break;
128
+ case 'GUID':
129
+ // GUID field gets a unique index
130
+ tmpDescriptor.indexes.push(
131
+ {
132
+ key: { [tmpColumn.Column]: 1 },
133
+ unique: true,
134
+ name: `idx_${tmpColumn.Column}_unique`
135
+ });
136
+ break;
137
+ case 'ForeignKey':
138
+ // Foreign key gets an index for lookups
139
+ tmpDescriptor.indexes.push(
140
+ {
141
+ key: { [tmpColumn.Column]: 1 },
142
+ name: `idx_${tmpColumn.Column}`
143
+ });
144
+ break;
145
+ default:
146
+ break;
147
+ }
148
+ }
149
+
150
+ return tmpDescriptor;
151
+ }
152
+
153
+ createTables(pMeadowSchema, fCallback)
154
+ {
155
+ this.fable.Utility.eachLimit(pMeadowSchema.Tables, 1,
156
+ (pTable, fCreateComplete) =>
157
+ {
158
+ return this.createTable(pTable, fCreateComplete);
159
+ },
160
+ (pCreateError) =>
161
+ {
162
+ if (pCreateError)
163
+ {
164
+ this.log.error(`Meadow-MongoDB Error creating collections from Schema: ${pCreateError}`, pCreateError);
165
+ }
166
+ this.log.info('Done creating collections!');
167
+ return fCallback(pCreateError);
168
+ });
169
+ }
170
+
171
+ createTable(pMeadowTableSchema, fCallback)
172
+ {
173
+ let tmpDescriptor = this.generateCreateTableStatement(pMeadowTableSchema);
174
+
175
+ if (!this._Database)
176
+ {
177
+ this.log.error(`Meadow-MongoDB CREATE COLLECTION ${tmpDescriptor.collection} failed: not connected.`);
178
+ return fCallback(new Error('Not connected to MongoDB'));
179
+ }
180
+
181
+ // Create collection (ignore error if it already exists)
182
+ this._Database.createCollection(tmpDescriptor.collection)
183
+ .then(() =>
184
+ {
185
+ this.log.info(`Meadow-MongoDB collection ${tmpDescriptor.collection} created or already exists.`);
186
+
187
+ // Create indexes if any
188
+ if (tmpDescriptor.indexes.length > 0)
189
+ {
190
+ let tmpCollection = this._Database.collection(tmpDescriptor.collection);
191
+ return tmpCollection.createIndexes(tmpDescriptor.indexes);
192
+ }
193
+ })
194
+ .then(() =>
195
+ {
196
+ this.log.info(`Meadow-MongoDB indexes for ${tmpDescriptor.collection} created successfully.`);
197
+ return fCallback();
198
+ })
199
+ .catch((pError) =>
200
+ {
201
+ // 48 = NamespaceExists (collection already exists)
202
+ if (pError.code === 48)
203
+ {
204
+ this.log.warn(`Meadow-MongoDB collection ${tmpDescriptor.collection} already existed.`);
205
+
206
+ // Still try to create indexes
207
+ if (tmpDescriptor.indexes.length > 0)
208
+ {
209
+ let tmpCollection = this._Database.collection(tmpDescriptor.collection);
210
+ tmpCollection.createIndexes(tmpDescriptor.indexes)
211
+ .then(() => fCallback())
212
+ .catch((pIndexError) =>
213
+ {
214
+ this.log.warn(`Meadow-MongoDB index creation for ${tmpDescriptor.collection}: ${pIndexError.message}`);
215
+ return fCallback();
216
+ });
217
+ }
218
+ else
219
+ {
220
+ return fCallback();
221
+ }
222
+ }
223
+ else
224
+ {
225
+ this.log.error(`Meadow-MongoDB CREATE COLLECTION ${tmpDescriptor.collection} failed!`, pError);
226
+ return fCallback(pError);
227
+ }
228
+ });
229
+ }
230
+
231
+ connect()
232
+ {
233
+ if (this._Client)
234
+ {
235
+ let tmpCleansedSettings = JSON.parse(JSON.stringify(this.options.MongoDB || {}));
236
+ tmpCleansedSettings.password = '*****************';
237
+ this.log.error(`Meadow-Connection-MongoDB trying to connect but is already connected - skipping.`, tmpCleansedSettings);
238
+ return;
239
+ }
240
+
241
+ let tmpURI = this._buildConnectionURI();
242
+ let tmpOptions = {
243
+ maxPoolSize: (this.options.MongoDB && this.options.MongoDB.maxPoolSize) || 10
244
+ };
245
+
246
+ this.fable.log.info(`Meadow-Connection-MongoDB connecting to [${this.options.MongoDB.host}:${this.options.MongoDB.port}] for database ${this.options.MongoDB.database} with pool size ${tmpOptions.maxPoolSize}`);
247
+
248
+ this._Client = new MongoClient(tmpURI, tmpOptions);
249
+ this._Database = this._Client.db(this.options.MongoDB.database);
250
+ this.connected = true;
251
+ }
252
+
253
+ connectAsync(fCallback)
254
+ {
255
+ let tmpCallback = fCallback;
256
+ if (typeof (tmpCallback) !== 'function')
257
+ {
258
+ this.log.error(`Meadow MongoDB connectAsync() called without a callback.`);
259
+ tmpCallback = () => { };
260
+ }
261
+
262
+ try
263
+ {
264
+ if (this._Client)
265
+ {
266
+ return tmpCallback(null, this._Database);
267
+ }
268
+ else
269
+ {
270
+ this.connect();
271
+ return tmpCallback(null, this._Database);
272
+ }
273
+ }
274
+ catch(pError)
275
+ {
276
+ this.log.error(`Meadow MongoDB connectAsync() error: ${pError}`, pError);
277
+ return tmpCallback(pError);
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Returns the MongoDB Db instance (analogous to pool in SQL drivers).
283
+ */
284
+ get pool()
285
+ {
286
+ return this._Database;
287
+ }
288
+
289
+ /**
290
+ * Returns the raw MongoClient instance.
291
+ */
292
+ get client()
293
+ {
294
+ return this._Client;
295
+ }
296
+ }
297
+
298
+ module.exports = MeadowConnectionMongoDB;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Unit tests for Meadow Connection MongoDB
3
+ *
4
+ * @license MIT
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ */
8
+
9
+ const Chai = require('chai');
10
+ const Expect = Chai.expect;
11
+
12
+ const libFable = require('fable');
13
+ const libMeadowConnectionMongoDB = require('../source/Meadow-Connection-MongoDB.js');
14
+
15
+ const _FableConfig = (
16
+ {
17
+ "Product": "MeadowMongoDBTest",
18
+ "ProductVersion": "1.0.0",
19
+
20
+ "UUID":
21
+ {
22
+ "DataCenter": 0,
23
+ "Worker": 0
24
+ },
25
+ "LogStreams":
26
+ [
27
+ {
28
+ "streamtype": "console"
29
+ }
30
+ ],
31
+
32
+ "MongoDB":
33
+ {
34
+ "Server": "127.0.0.1",
35
+ "Port": 27017,
36
+ "User": "",
37
+ "Password": "",
38
+ "Database": "testdb",
39
+ "ConnectionPoolLimit": 20
40
+ }
41
+ });
42
+
43
+ suite
44
+ (
45
+ 'Meadow-Connection-MongoDB',
46
+ () =>
47
+ {
48
+ setup(() => {});
49
+
50
+ suite
51
+ (
52
+ 'Object Sanity',
53
+ () =>
54
+ {
55
+ test
56
+ (
57
+ 'constructor should create a connection service',
58
+ () =>
59
+ {
60
+ let _Fable = new libFable(_FableConfig);
61
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
62
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
63
+
64
+ Expect(_Fable.MeadowMongoDBProvider).to.be.an('object');
65
+ Expect(_Fable.MeadowMongoDBProvider.serviceType).to.equal('MeadowConnectionMongoDB');
66
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(false);
67
+ }
68
+ );
69
+ test
70
+ (
71
+ 'pass in your own settings',
72
+ () =>
73
+ {
74
+ let _Fable = new libFable();
75
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
76
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider', {MongoDB: _FableConfig.MongoDB});
77
+
78
+ Expect(_Fable.MeadowMongoDBProvider).to.be.an('object');
79
+ Expect(_Fable.MeadowMongoDBProvider.serviceType).to.equal('MeadowConnectionMongoDB');
80
+ Expect(_Fable.MeadowMongoDBProvider.connected).to.equal(false);
81
+ }
82
+ );
83
+ }
84
+ );
85
+ suite
86
+ (
87
+ 'DDL Generation',
88
+ () =>
89
+ {
90
+ test
91
+ (
92
+ 'generateCreateTableStatement produces a valid MongoDB collection descriptor',
93
+ () =>
94
+ {
95
+ let _Fable = new libFable(_FableConfig);
96
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
97
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
98
+
99
+ let tmpSchema = {
100
+ TableName: 'Animal',
101
+ Columns: [
102
+ { Column: 'IDAnimal', DataType: 'ID' },
103
+ { Column: 'GUIDAnimal', DataType: 'GUID', Size: 36 },
104
+ { Column: 'Name', DataType: 'String', Size: 128 },
105
+ { Column: 'Age', DataType: 'Numeric' },
106
+ { Column: 'Cost', DataType: 'Decimal', Size: '10,2' },
107
+ { Column: 'Description', DataType: 'Text' },
108
+ { Column: 'Birthday', DataType: 'DateTime' },
109
+ { Column: 'Active', DataType: 'Boolean' },
110
+ { Column: 'IDFarm', DataType: 'ForeignKey' }
111
+ ]
112
+ };
113
+
114
+ let tmpResult = _Fable.MeadowMongoDBProvider.generateCreateTableStatement(tmpSchema);
115
+ Expect(tmpResult.operation).to.equal('createCollection');
116
+ Expect(tmpResult.collection).to.equal('Animal');
117
+ Expect(tmpResult.indexes).to.be.an('array');
118
+ // ID, GUID, and ForeignKey should each create an index
119
+ Expect(tmpResult.indexes.length).to.equal(3);
120
+ // First index should be IDAnimal (unique)
121
+ Expect(tmpResult.indexes[0].key.IDAnimal).to.equal(1);
122
+ Expect(tmpResult.indexes[0].unique).to.equal(true);
123
+ // Second index should be GUIDAnimal (unique)
124
+ Expect(tmpResult.indexes[1].key.GUIDAnimal).to.equal(1);
125
+ Expect(tmpResult.indexes[1].unique).to.equal(true);
126
+ // Third index should be IDFarm (not unique)
127
+ Expect(tmpResult.indexes[2].key.IDFarm).to.equal(1);
128
+ Expect(tmpResult.indexes[2]).to.not.have.property('unique');
129
+ }
130
+ );
131
+ test
132
+ (
133
+ 'generateDropTableStatement produces a valid MongoDB drop descriptor',
134
+ () =>
135
+ {
136
+ let _Fable = new libFable(_FableConfig);
137
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
138
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
139
+
140
+ let tmpResult = _Fable.MeadowMongoDBProvider.generateDropTableStatement('Animal');
141
+ Expect(tmpResult.operation).to.equal('drop');
142
+ Expect(tmpResult.collection).to.equal('Animal');
143
+ }
144
+ );
145
+ test
146
+ (
147
+ 'connection URI is built correctly without auth',
148
+ () =>
149
+ {
150
+ let _Fable = new libFable(_FableConfig);
151
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
152
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider');
153
+
154
+ let tmpURI = _Fable.MeadowMongoDBProvider._buildConnectionURI();
155
+ Expect(tmpURI).to.equal('mongodb://127.0.0.1:27017/testdb');
156
+ }
157
+ );
158
+ test
159
+ (
160
+ 'connection URI is built correctly with auth',
161
+ () =>
162
+ {
163
+ let _Fable = new libFable();
164
+ _Fable.serviceManager.addServiceType('MeadowMongoDBProvider', libMeadowConnectionMongoDB);
165
+ _Fable.serviceManager.instantiateServiceProvider('MeadowMongoDBProvider',
166
+ {
167
+ MongoDB:
168
+ {
169
+ Server: 'myhost',
170
+ Port: 27018,
171
+ User: 'admin',
172
+ Password: 'secret',
173
+ Database: 'mydb'
174
+ }
175
+ });
176
+
177
+ let tmpURI = _Fable.MeadowMongoDBProvider._buildConnectionURI();
178
+ Expect(tmpURI).to.equal('mongodb://admin:secret@myhost:27018/mydb');
179
+ }
180
+ );
181
+ }
182
+ );
183
+ }
184
+ );