UNPKG

4.19 kBMarkdownView Raw
1# monogram
2
3Action-based anti-ODM for MongoDB and Node.js
4
5Read the [intro blog post here](http://thecodebarbarian.com/introducing-monogram-the-anti-odm-for-mongodb-nodejs.html).
6
7## Usage
8
9```javascript
10const { connect } = require('monogram');
11const db = await connect('mongodb://localhost:27017/test');
12```
13
14
15# Usage
16
17## Actions
18
19
20From an end developer perspective, monogram behaves just like the
21[MongoDB Node.js driver](https://www.npmjs.com/package/mongodb).
22The key difference is that monogram converts collection functions
23into _actions_ under the hood. Actions are an object representation
24of a function call.
25
26
27```javascript
28
29 const Test = db.collection('Test');
30
31 let called = 0;
32 Test.pre(action => {
33 ++called;
34 // An _action_ is an object representation of a function call.
35 // It has an `_id` property to uniquely identify it, and
36 // some other properties:
37 assert.deepEqual(_.omit(action, ['_id']), {
38 collection: 'Test', // The name of the collection
39 name: 'insertOne', // The name of the function called
40 params: [{
41 hello: 'world'
42 }], // The parameters passed to the function
43 chained: [] // Function calls chained onto this one
44 });
45 });
46
47 await Test.insertOne({ hello: 'world' });
48
49 assert.equal(called, 1);
50
51```
52
53## Motivation: Logging
54
55
56Monogram isn't an ODM/ORM like its uncle [mongoose](https://www.npmjs.com/package/mongoose),
57It's a new abstraction entirely. You can call it an AOM, "action-object mapping".
58Why is this abstraction better? Consider the problem of logging all
59database operations to the console in an ODM. In mongoose, this is hard,
60because there's a lot of different [types of middleware](http://mongoosejs.com/docs/middleware.html).
61In monogram, this is trivial, because all database operations are
62represented in a common form, actions, and all actions go through
63one pipeline.
64
65
66```javascript
67
68 const Test = db.collection('Test');
69
70 let called = 0;
71
72 Test.action$.subscribe(action => {
73 ++called;
74 const params = action.params.
75 map(p => util.inspect(p, { depth: 5 })).
76 join(', ');
77 const msg = `${action.collection}.${action.name}(${params})`
78
79 assert.equal(msg,
80 `Test.updateOne({ _id: 1 }, { '$set': { hello: 'world' } })`);
81 });
82
83 await Test.updateOne({ _id: 1 }, {
84 $set: { hello: 'world' }
85 });
86
87 assert.equal(called, 1);
88
89```
90
91## Enforcing Internal Best Practices
92
93
94The purpose of monogram is to allow you to enforce best practices, not
95to prescribe best practices. Beginners are best served using a tool like
96[mongoose](https://www.npmjs.com/package/mongoose), which has a lot of
97baked-in best practices to prevent you from shooting yourself in the foot.
98Monogram is more for advanced users who have established best practices
99they want to enforce. For example, here's how you would prevent users
100from calling `updateOne()` or `updateMany()` without any [update operators](https://docs.mongodb.com/manual/reference/operator/update/),
101which would [overwrite the document](https://docs.mongodb.com/v3.2/reference/method/db.collection.replaceOne/).
102
103
104```javascript
105
106 const Test = db.collection('Test');
107
108 let called = 0;
109
110 // Will catch `updateOne()`, `updateMany()`, and `findOneAndUpdate()`
111 // actions
112 Test.pre(/update/i, action => {
113 const update = action.params[1] || {};
114 const keys = Object.keys(update);
115 if (keys.length > 0 && !keys[0].startsWith('$')) {
116 throw new Error('Not allowed to overwrite document ' +
117 'using `updateOne()`, use `replaceOne() instead`');
118 }
119 });
120
121 let threw = false;
122 try {
123 // Normally this would delete all properties on the document
124 // other than `_id` and `overwrite`. This is expected behavior,
125 // but you might want to disallow it. Monogram gives you a
126 // framework to do so.
127 await Test.updateOne({ _id: 1 }, { overwrite: 'woops!' });
128 } catch (error) {
129 threw = true;
130 assert.equal(error.message, 'Not allowed to overwrite document ' +
131 'using `updateOne()`, use `replaceOne() instead`');
132 }
133
134 assert.ok(threw);
135
136```
\No newline at end of file