1 | # monogram
|
2 |
|
3 | Action-based anti-ODM for MongoDB and Node.js
|
4 |
|
5 | Read the [intro blog post here](http://thecodebarbarian.com/introducing-monogram-the-anti-odm-for-mongodb-nodejs.html).
|
6 |
|
7 | ## Usage
|
8 |
|
9 | ```javascript
|
10 | const { connect } = require('monogram');
|
11 | const db = await connect('mongodb://localhost:27017/test');
|
12 | ```
|
13 |
|
14 |
|
15 | # Usage
|
16 |
|
17 | ## Actions
|
18 |
|
19 |
|
20 | From an end developer perspective, monogram behaves just like the
|
21 | [MongoDB Node.js driver](https://www.npmjs.com/package/mongodb).
|
22 | The key difference is that monogram converts collection functions
|
23 | into _actions_ under the hood. Actions are an object representation
|
24 | of 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 |
|
56 | Monogram isn't an ODM/ORM like its uncle [mongoose](https://www.npmjs.com/package/mongoose),
|
57 | It's a new abstraction entirely. You can call it an AOM, "action-object mapping".
|
58 | Why is this abstraction better? Consider the problem of logging all
|
59 | database operations to the console in an ODM. In mongoose, this is hard,
|
60 | because there's a lot of different [types of middleware](http://mongoosejs.com/docs/middleware.html).
|
61 | In monogram, this is trivial, because all database operations are
|
62 | represented in a common form, actions, and all actions go through
|
63 | one 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 |
|
94 | The purpose of monogram is to allow you to enforce best practices, not
|
95 | to prescribe best practices. Beginners are best served using a tool like
|
96 | [mongoose](https://www.npmjs.com/package/mongoose), which has a lot of
|
97 | baked-in best practices to prevent you from shooting yourself in the foot.
|
98 | Monogram is more for advanced users who have established best practices
|
99 | they want to enforce. For example, here's how you would prevent users
|
100 | from calling `updateOne()` or `updateMany()` without any [update operators](https://docs.mongodb.com/manual/reference/operator/update/),
|
101 | which 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 |