Skip to content

Commit c6adc3a

Browse files
committed
Import driver from sapling
1 parent 206a582 commit c6adc3a

File tree

4 files changed

+457
-0
lines changed

4 files changed

+457
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
npm-debug.log
3+
env

index.js

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/**
2+
* MongoDB driver for Sapling
3+
*/
4+
5+
6+
/* Dependencies */
7+
const { MongoClient, ObjectID } = require("mongodb");
8+
const Interface = require("@sapling/sapling").drivers.db.Interface;
9+
const { console } = require("@sapling/sapling").lib.Cluster;
10+
11+
/* Default values */
12+
const HOST = "localhost";
13+
const PORT = 27017;
14+
15+
/* Default options for each type of operation */
16+
const mongo_options = {
17+
open: { w: 1, strict: false, safe: true },
18+
collection: { strict: false },
19+
insert: { w: 1, strict: false },
20+
update: { upsert: false, multi: true, w: 1, strict: false },
21+
find: {}
22+
};
23+
24+
module.exports = class Mongo extends Interface {
25+
26+
/**
27+
* The MongoClient instance
28+
*/
29+
client = null
30+
31+
/**
32+
* The selected database instance
33+
*/
34+
database = null
35+
36+
/**
37+
* Name of the database to be selected
38+
*/
39+
databaseName = null
40+
41+
42+
/**
43+
* Convert all "_id" fields with a string representation of an object ID
44+
* to the appropriate MongoDB object ID object
45+
*
46+
* @param {object} conditions Search query object
47+
*/
48+
convertObjectId(conditions) {
49+
if (conditions._id) {
50+
try {
51+
conditions._id = new ObjectID(conditions._id);
52+
} catch (er) {}
53+
}
54+
55+
return conditions;
56+
}
57+
58+
59+
/**
60+
* Establish a connection to the database server
61+
*
62+
* @param {object} config {name: Name of the database, host: Host IP, port: Port number}
63+
*/
64+
async connect({name, host, port}) {
65+
/* Setup the Mongo connection */
66+
this.client = new MongoClient(`mongodb://${host || HOST}:${port || PORT}?useUnifiedTopology=true`);
67+
68+
/* Set the given database (actually select it in open()) */
69+
this.databaseName = name;
70+
await this.open();
71+
72+
return true;
73+
}
74+
75+
76+
/**
77+
* Open a connection and select the database
78+
*/
79+
async open() {
80+
this.connection = await this.client.connect();
81+
this.database = await this.client.db(this.databaseName);
82+
}
83+
84+
85+
/**
86+
* Close a connection
87+
*/
88+
async close() {
89+
return await this.client.close();
90+
}
91+
92+
93+
/**
94+
* Create a collection in the database where one doesn't yet exist
95+
*
96+
* @param {string} name Name for the collection being created
97+
* @param {array} fields Model object
98+
*/
99+
async createCollection(name, fields) {
100+
101+
console.log("CREATE COLLECTION", name, fields);
102+
await this.open();
103+
104+
const self = this;
105+
106+
const collection = await this.database.createCollection(name, mongo_options.open, () => {
107+
/* Go through all the fields in the model */
108+
for (const key in fields) {
109+
const rule = fields[key];
110+
111+
/* Create indices for any fields marked unique */
112+
if (rule.unique) {
113+
const ufields = {};
114+
ufields[key] = 1;
115+
self.createIndex(name, ufields, {unique: true});
116+
}
117+
}
118+
});
119+
120+
await this.close();
121+
122+
return collection;
123+
}
124+
125+
126+
/**
127+
* Create an index for the specified fields
128+
*
129+
* @param {string} name Name of the target collection
130+
* @param {object} fields List of field names that should have indexes created. Key is the field name, value is the type of index
131+
* @param {object} config Driver specific options for the operation
132+
*/
133+
async createIndex(name, fields, config) {
134+
135+
console.log("CREATE INDEX", name, fields, config);
136+
await this.open();
137+
138+
/* Select the given collection */
139+
const collection = await this.database.collection(name, mongo_options.collection);
140+
141+
/* Create an index for the given field(s) */
142+
const index = collection.createIndex(fields, config);
143+
144+
await this.close();
145+
146+
return index;
147+
}
148+
149+
150+
/**
151+
* Find one or more records for the given conditions in the given collection
152+
*
153+
* @param {string} name Name of the target collection
154+
* @param {object} conditions The search query
155+
* @param {object} options Driver specific options for the operation
156+
*/
157+
async read(name, conditions, options, references) {
158+
159+
console.log("READ", name, conditions);
160+
await this.open();
161+
162+
/* If there is an _id field in constraints, create a proper object ID object */
163+
conditions = this.convertObjectId(conditions);
164+
165+
/* TODO: find out what this is */
166+
if (options['in']) {
167+
const inOpts = options['in'];
168+
const key = Object.keys(inOpts)[0];
169+
170+
if (key == '_id') { // TODO: include keys with rule.type == id
171+
for (let i = 0; i < inOpts[key].length; ++i) {
172+
try {
173+
inOpts[key][i] = mongo.ObjectID(inOpts[key][i])
174+
} catch (e) {}
175+
}
176+
}
177+
178+
conditions[key] = {'$in': inOpts[key]};
179+
options = {};
180+
}
181+
182+
/* Get the collection */
183+
const collection = await this.database.collection(name, mongo_options.collection);
184+
185+
/* Plain aggregation stack */
186+
const stack = [
187+
{
188+
'$match': conditions
189+
}
190+
];
191+
192+
/* Handle reference fields if we have any */
193+
for(const reference in references) {
194+
stack.push({
195+
'$lookup': references[reference]
196+
});
197+
}
198+
199+
/* Do it */
200+
const result = await collection.aggregate(stack, options);
201+
202+
await this.close();
203+
204+
return result;
205+
}
206+
207+
208+
/**
209+
* Create one new records in the given collection
210+
*
211+
* @param {string} name Name of the target collection
212+
* @param {object} data Data for the collection
213+
*/
214+
async write(name, conditions, data) {
215+
216+
console.log("WRITE", name, conditions, data);
217+
await this.open();
218+
219+
/* For any reference constraints, create a proper object ID object */
220+
for (const i in conditions['references']) {
221+
const reference = conditions['references'][i];
222+
if(data[reference])
223+
data[reference] = new mongo.ObjectID(data[reference]);
224+
}
225+
226+
/* Remove the raw references */
227+
delete conditions['references'];
228+
229+
/* Select the given collection */
230+
const collection = await this.database.collection(name, mongo_options.collection);
231+
232+
/* Create a new record with the data */
233+
const result = await collection.insert(data, mongo_options.insert);
234+
235+
await this.close();
236+
237+
return result;
238+
}
239+
240+
241+
/**
242+
* Modify the given values in data in any and all records matching the given conditions
243+
*
244+
* @param {string} collection Name of the target collection
245+
* @param {object} conditions The search query
246+
* @param {object} data New data for the matching record(s). Omitted values does not imply deletion.
247+
*/
248+
async modify(name, conditions, data) {
249+
250+
console.log("MODIFY", name, conditions, data);
251+
await this.open();
252+
253+
/* If there is an _id field in constraints, create a proper object ID object */
254+
conditions = this.convertObjectId(conditions);
255+
256+
/* For any reference constraints, create a proper object ID object */
257+
for (const i in conditions['references']) {
258+
const reference = conditions['references'][i];
259+
if(data[reference])
260+
data[reference] = new mongo.ObjectID(data[reference]);
261+
}
262+
263+
/* Remove the raw references */
264+
delete conditions['references'];
265+
266+
/* Select the given collection */
267+
const collection = await this.database.collection(name, mongo_options.collection);
268+
269+
/* Update the given record with new data */
270+
const result = await collection.update(conditions, {"$set": data}, mongo_options.update);
271+
272+
await this.close();
273+
274+
return result;
275+
}
276+
277+
278+
/**
279+
* Delete any and all matching records for the given conditions
280+
*
281+
* @param {string} collection Name of the target collection
282+
* @param {object} conditions The search query
283+
*/
284+
async remove(name, conditions) {
285+
286+
console.log("REMOVE", name, conditions);
287+
await this.open();
288+
289+
/* If there is an _id field in constraints, create a proper object ID object */
290+
conditions = this.convertObjectId(conditions);
291+
292+
/* Select the given collection */
293+
const collection = await this.database.collection(name, mongo_options.collection);
294+
295+
/* Delete the given records */
296+
const result = await collection.remove(conditions, mongo_options.remove);
297+
298+
await this.close();
299+
300+
return result;
301+
}
302+
};

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "db-driver-mongodb",
3+
"version": "0.1.0",
4+
"license": "MPL-2.0",
5+
"description": "MongoDB driver for Sapling",
6+
"keywords": [
7+
"mongodb",
8+
"sapling",
9+
"saplingjs",
10+
"database",
11+
"driver"
12+
],
13+
"homepage": "https://www.saplingjs.com",
14+
"bugs": "https://github.com/saplingjs/db-driver-mongodb/issues",
15+
"author": {
16+
"name": "Oskari Groenroos",
17+
"email": "[email protected]",
18+
"url": "https://www.groenroos.fi"
19+
},
20+
"dependencies": {
21+
"mongodb": "^3.6.3"
22+
},
23+
"peerDependencies": {
24+
"@sapling/sapling": "^0.1.0"
25+
}
26+
}

0 commit comments

Comments
 (0)