sharedb-mongo 1.0.0-beta.6 → 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/.eslintrc.js ADDED
@@ -0,0 +1,49 @@
1
+ // The ESLint ecmaVersion argument is inconsistently used. Some rules will ignore it entirely, so if the rule has
2
+ // been set, it will still error even if it's not applicable to that version number. Since Google sets these
3
+ // rules, we have to turn them off ourselves.
4
+ const DISABLED_ES6_OPTIONS = {
5
+ 'no-var': 'off',
6
+ 'prefer-rest-params': 'off'
7
+ };
8
+
9
+ const SHAREDB_RULES = {
10
+ // Comma dangle is not supported in ES3
11
+ 'comma-dangle': ['error', 'never'],
12
+ // We control our own objects and prototypes, so no need for this check
13
+ 'guard-for-in': 'off',
14
+ // Google prescribes different indents for different cases. Let's just use 2 spaces everywhere. Note that we have
15
+ // to override ESLint's default of 0 indents for this.
16
+ 'indent': ['error', 2, {
17
+ 'SwitchCase': 1
18
+ }],
19
+ // Less aggressive line length than Google, which is especially useful when we have a lot of callbacks in our code
20
+ 'max-len': ['error',
21
+ {
22
+ code: 120,
23
+ tabWidth: 2,
24
+ ignoreUrls: true,
25
+ }
26
+ ],
27
+ // Google overrides the default ESLint behaviour here, which is slightly better for catching erroneously unused variables
28
+ 'no-unused-vars': ['error', {vars: 'all', args: 'after-used'}],
29
+ // It's more readable to ensure we only have one statement per line
30
+ 'max-statements-per-line': ['error', {max: 1}],
31
+ // ES3 doesn't support spread
32
+ 'prefer-spread': 'off',
33
+ // as-needed quote props are easier to write
34
+ 'quote-props': ['error', 'as-needed'],
35
+ 'require-jsdoc': 'off',
36
+ 'valid-jsdoc': 'off'
37
+ };
38
+
39
+ module.exports = {
40
+ extends: 'google',
41
+ parserOptions: {
42
+ ecmaVersion: 3
43
+ },
44
+ rules: Object.assign(
45
+ {},
46
+ DISABLED_ES6_OPTIONS,
47
+ SHAREDB_RULES
48
+ ),
49
+ };
@@ -0,0 +1,58 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ test:
13
+ name: Node.js ${{ matrix.node }} + mongoDB ${{ matrix.mongodb }}
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ node:
18
+ - 12
19
+ - 14
20
+ - 16
21
+ mongodb:
22
+ - 4.0
23
+ - 4.2
24
+ - 4.4
25
+ - 5.0
26
+ services:
27
+ mongodb:
28
+ image: mongo:${{ matrix.mongodb }}
29
+ ports:
30
+ - 27017:27017
31
+ timeout-minutes: 10
32
+ steps:
33
+ - uses: actions/checkout@v2
34
+ - uses: actions/setup-node@v1
35
+ with:
36
+ node-version: ${{ matrix.node }}
37
+ - name: Install
38
+ run: npm install
39
+ - name: Lint
40
+ run: npm run lint
41
+ - name: Test
42
+ run: npm run test-cover
43
+ - name: Coveralls
44
+ uses: coverallsapp/github-action@master
45
+ with:
46
+ github-token: ${{ secrets.GITHUB_TOKEN }}
47
+ flag-name: node-${{ matrix.node }}-mongodb-${{ matrix.mongodb }}
48
+ parallel: true
49
+
50
+ finish:
51
+ needs: test
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - name: Submit coverage
55
+ uses: coverallsapp/github-action@master
56
+ with:
57
+ github-token: ${{ secrets.GITHUB_TOKEN }}
58
+ parallel-finished: true
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2015 Joseph Gentle and Nate Smith
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # sharedb-mongo
2
2
 
3
- [![NPM Version](https://img.shields.io/npm/v/sharedb-mongo.svg)](https://npmjs.org/package/sharedb-mongo)
4
- [![Build Status](https://travis-ci.org/share/sharedb-mongo.svg?branch=master)](https://travis-ci.org/share/sharedb-mongo)
5
- [![Coverage Status](https://coveralls.io/repos/github/share/sharedb-mongo/badge.svg?branch=master)](https://coveralls.io/github/share/sharedb-mongo?branch=master)
3
+ [![NPM Version](https://img.shields.io/npm/v/sharedb-mongo.svg)](https://npmjs.org/package/sharedb-mongo)
4
+ [![Test](https://github.com/share/sharedb-mongo/workflows/Test/badge.svg)](https://github.com/share/sharedb-mongo/actions/workflows/test.yml)
5
+ [![Coverage Status](https://coveralls.io/repos/github/share/sharedb-mongo/badge.svg?branch=master)](https://coveralls.io/github/share/sharedb-mongo?branch=master)
6
6
 
7
7
  MongoDB database adapter for [sharedb](https://github.com/share/sharedb). This
8
8
  driver can be used both as a snapshot store and oplog.
9
9
 
10
- Snapshots are stored where you'd expect (the named collection with _id=id). In
10
+ Snapshots are stored where you'd expect (the named collection with \_id=id). In
11
11
  addition, operations are stored in `o_COLLECTION`. For example, if you have
12
12
  a `users` collection, the operations are stored in `o_users`.
13
13
 
@@ -17,38 +17,36 @@ the form of `_v` and `_type`). It is safe to query documents directly with the
17
17
  MongoDB driver or command line. Any read only mongo features, including find,
18
18
  aggregate, and map reduce are safe to perform concurrent with ShareDB.
19
19
 
20
- However, you must *always* use ShareDB to edit documents. Never use the
20
+ However, you must _always_ use ShareDB to edit documents. Never use the
21
21
  MongoDB driver or command line to directly modify any documents that ShareDB
22
22
  might create or edit. ShareDB must be used to properly persist operations
23
23
  together with snapshots.
24
24
 
25
-
26
25
  ## Usage
27
26
 
28
- `sharedb-mongo` wraps native [mongodb](https://github.com/mongodb/node-mongodb-native), and it supports the same configuration options.
27
+ `sharedb-mongo` uses the [MongoDB NodeJS Driver](https://github.com/mongodb/node-mongodb-native), and it supports the same configuration options.
29
28
 
30
29
  There are two ways to instantiate a sharedb-mongo wrapper:
31
30
 
32
- 1. The simplest way is to invoke the module and pass in your mongo DB
33
- arguments as arguments to the module function. For example:
34
-
35
- ```javascript
36
- const db = require('sharedb-mongo')('mongodb://localhost:27017/test');
37
- const backend = new ShareDB({db});
38
- ```
31
+ 1. The simplest way is to invoke the module and pass in your mongo DB
32
+ arguments as arguments to the module function. For example:
39
33
 
40
- 2. If you'd like to reuse a mongo db connection or handle mongo driver
41
- instantiation yourself, you can pass in a function that calls back with
42
- a mongo instance.
34
+ ```javascript
35
+ const db = require('sharedb-mongo')('mongodb://localhost:27017/test', {mongoOptions: {...}});
36
+ const backend = new ShareDB({db});
37
+ ```
43
38
 
44
- ```javascript
45
- const mongodb = require('mongodb');
46
- const db = require('sharedb-mongo')({mongo: function(callback) {
47
- mongodb.connect('mongodb://localhost:27017/test', callback);
48
- }});
49
- const backend = new ShareDB({db});
50
- ```
39
+ 2. If you'd like to reuse a mongo db connection or handle mongo driver
40
+ instantiation yourself, you can pass in a function that calls back with
41
+ a mongo instance.
51
42
 
43
+ ```javascript
44
+ const mongodb = require('mongodb');
45
+ const db = require('sharedb-mongo')({mongo: function(callback) {
46
+ mongodb.connect('mongodb://localhost:27017/test', callback);
47
+ }});
48
+ const backend = new ShareDB({db});
49
+ ```
52
50
 
53
51
  ## Queries
54
52
 
@@ -83,48 +81,196 @@ For a full list of supported collection and cursor methods, see
83
81
  `collectionOperationsMap`, `cursorTransformsMap` and
84
82
  `cursorOperationsMap` in index.js
85
83
 
86
- ## Error codes
84
+ ## `getOps` without strict linking
87
85
 
88
- Mongo errors are passed back directly. Additional error codes:
86
+ There is a `getOpsWithoutStrictLinking` flag, which can be set to
87
+ `true` to speed up `getOps` under certain circumstances, but with
88
+ potential risks to the integrity of the results. Read below for
89
+ more detail.
89
90
 
90
- #### 4100 -- Bad request - DB
91
+ ### Introduction
91
92
 
92
- * 4101 -- Invalid op version
93
- * 4102 -- Invalid collection name
94
- * 4103 -- $where queries disabled
95
- * 4104 -- $mapReduce queries disabled
96
- * 4105 -- $aggregate queries disabled
97
- * 4106 -- $query property deprecated in queries
98
- * 4107 -- Malformed query operator
99
- * 4108 -- Only one collection operation allowed
100
- * 4109 -- Only one cursor operation allowed
101
- * 4110 -- Cursor methods can't run after collection method
93
+ ShareDB has to deal with concurrency issues. In particular, here we
94
+ discuss the issue of submitting multiple competing ops against a
95
+ version of a document.
102
96
 
103
- #### 5100 -- Internal error - DB
97
+ For example, if I have a version of a document at v1, and I
98
+ simultaneously submit two ops (from different servers, say) against
99
+ this snapshot, then we need to handle the fact that only one of
100
+ these ops can be accepted as canonical and applied to the snapshot.
101
+
102
+ This issue is dealt with through **optimistic locking**. Even if
103
+ you are only asking for a subset of the ops, under the default
104
+ behaviour, `getOps` will fetch **all** the ops up to the current
105
+ version.
106
+
107
+ ### Optimistic locking and linked ops
108
+
109
+ `sharedb-mongo` deals with its concurrency issue with multiple op
110
+ submissions with optimistic locking. Here's an example of its
111
+ behaviour:
112
+
113
+ - my doc exists at v1
114
+ - two simultaneous v1 ops are submitted to ShareDB
115
+ - both ops are committed to the database
116
+ - one op is applied to the snapshot, and the updated snapshot is
117
+ written to the database
118
+ - the second op finds that its updated snapshot conflicts with
119
+ the committed snapshot, and the snapshot is rejected, but the
120
+ committed op **remains in the database**
121
+
122
+ In reality, `sharedb-mongo` attempts to clean up this failed op,
123
+ but there's still the small chance that the server crashes
124
+ before it can do so, meaning that we may have multiple ops
125
+ lingering in the database with the same version.
126
+
127
+ Because some non-canonical ops may exist in the database, we
128
+ cannot just perform a naive fetch of all the ops associated with
129
+ a document, because it may return multiple ops with the same
130
+ version (where one was successfully applied, and one was not).
131
+
132
+ In order to return a valid set of canonical ops, the optimistic
133
+ locking has a notion of **linked ops**. That is, each op will
134
+ point back to the op that it built on top of, and ultimately
135
+ the current snapshot points to the op that committed it to the
136
+ database.
137
+
138
+ Because of this, we can work backwards from the current snapshot,
139
+ following the trail of op links all the way back to get a chain
140
+ of canonical, valid, linked ops. This way, even if a spurious
141
+ op exists in the database, no other op will point to it, and it
142
+ will be correctly ignored.
143
+
144
+ This approach has a big down-side: it forces us to fetch all the
145
+ ops up to the current version. This might be fine if you want
146
+ all ops, or are fetching very recent ops, but can have a large
147
+ impact on performance if you only want ops 1-10 of a 10,000
148
+ op document, because you actually have to fetch all the ops.
149
+
150
+ ### Dropping strict linking
151
+
152
+ In order to speed up the performance of `getOps`, you can set
153
+ `getOpsWithoutStrictLinking: true`. This will attempt to fetch
154
+ the bare minimum ops, whilst still trying to maintain op
155
+ integrity.
156
+
157
+ The assumption that underpins this approach is that any op
158
+ that exists with a unique combination of `d` (document ID)
159
+ and `v` (version), **is a valid op**. In other words, it
160
+ had no conflicts and can be considered canonical.
161
+
162
+ Consider a document with some ops, including some spurious,
163
+ failed ops:
164
+
165
+ - v1: unique
166
+ - v2: unique
167
+ - v3: collision 3
168
+ - v3: collision 3
169
+ - v4: collision 4
170
+ - v4: collision 4
171
+ - v5: unique
172
+ - v6: unique
173
+ ...
174
+ - v1000: unique
175
+
176
+ If I want to fetch ops v1-v3, then we:
104
177
 
105
- * 5101 -- Already closed
106
- * 5102 -- Snapshot missing last operation field
107
- * 5103 -- Missing ops from requested version
108
- * 5104 -- Failed to parse query
178
+ - look up v4
179
+ - find that v4 is not unique
180
+ - look up v5
181
+ - see that v5 is unique and therefore assumed valid
182
+ - look backwards from v5 for a chain of valid ops, avoiding
183
+ the spurious commits for v4 and v3.
184
+ - This way we don't need to fetch all the ops from v5 to the
185
+ current version.
109
186
 
187
+ In the case where a valid op cannot be determined, we still
188
+ fall back to fetching all ops and working backwards from the
189
+ current version.
110
190
 
111
- ## MIT License
112
- Copyright (c) 2015 by Joseph Gentle and Nate Smith
191
+ ### Middlewares
113
192
 
114
- Permission is hereby granted, free of charge, to any person obtaining a copy
115
- of this software and associated documentation files (the "Software"), to deal
116
- in the Software without restriction, including without limitation the rights
117
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
118
- copies of the Software, and to permit persons to whom the Software is
119
- furnished to do so, subject to the following conditions:
193
+ Middlewares let you hook into the `sharedb-mongo` pipeline for certain actions. They are distinct from [middleware in `ShareDB`](https://github.com/share/sharedb) as they are closer to the concrete calls that are made to `MongoDB` itself.
120
194
 
121
- The above copyright notice and this permission notice shall be included in
122
- all copies or substantial portions of the Software.
195
+ The original intent for middleware on `sharedb-mongo` is to support running in a sharded `MongoDB` cluster to satisfy the requirements on shard keys for versions 4.2 and greater of `MongoDB`. For more information see [the MongoDB docs](https://docs.mongodb.com/manual/core/sharding-shard-key/#shard-keys).
196
+
197
+ #### Usage
198
+
199
+ `share.use(action, fn)`
200
+ Register a new middleware.
201
+
202
+ - `action` _(String)_
203
+ One of:
204
+ - `'beforeCreate'`: directly before the call to write a new document
205
+ - `'beforeOverwrite'`: directly before the call to replace a document, can include edits as well as deletions
206
+ - `'beforeSnapshotLookup'`: directly before the call to issue a query for one or more snapshots by ID
207
+ - `fn` _(Function(context, callback))_
208
+ Call this function at the time specified by `action`
209
+ - `context` will always have the following properties:
210
+ - `action`: The action this middleware is handling
211
+ - `collectionName`: The collection name being handled
212
+ - `options`: Original options as they were passed into the relevant function that triggered the action
213
+ - `'beforeCreate'` actions have additional context properties:
214
+ - `documentToWrite` - The document to be written
215
+ - `op` - The op that represents the changes that will be made to the document
216
+ - `'beforeOverwrite'` actions have additional context properties:
217
+ - `documentToWrite` - The document to be written
218
+ - `op` - The op that represents the changes that will be made to the document
219
+ - `query` - A filter that will be used to lookup the document that is about to be edited, which should always include an ID and snapshot version e.g. `{_id: 'uuid', _v: 1}`
220
+ - `'beforeSnapshotLookup'` actions have additional context properties:
221
+ - `query` - A filter that will be used to lookup the snapshot. When a single snapshot is looked up the query will take the shape `{_id: docId}` while a bulk lookup by a list of IDs will resemble `{_id: {$in: docIdsArray}}`.
222
+ - `findOptions` - Middleware can define and populate this object on the context to pass options to the MongoDB driver when doing the lookup.
223
+
224
+ ### Limitations
225
+
226
+ #### Integrity
227
+
228
+ Attempting to infer a canonical op can be dangerous compared
229
+ to simply following the valid op chain from the snapshot,
230
+ which is - by definition - canonical.
231
+
232
+ This alternative behaviour should be safe, but should be used
233
+ with caution, because we are attempting to _infer_ a canonical
234
+ op, which may have unforeseen corner cases that return an
235
+ **invalid set of ops**.
236
+
237
+ This may be especially true if the ops are modified outside
238
+ of `sharedb-mongo` (eg by setting a TTL, or manually updating
239
+ them).
240
+
241
+ #### Recent ops
242
+
243
+ There are cases where this flag may slow down behaviour. In
244
+ the case of attempting to fetch very recent ops, setting this
245
+ flag may make extra database round-trips where fetching the
246
+ snapshot would have been faster.
247
+
248
+ #### `getOpsBulk` and `getOpsToSnapshot`
249
+
250
+ This flag **only** applies to `getOps`, and **not** to the
251
+ similar `getOpsBulk` and `getOpsToSnapshot` methods, whose
252
+ performance will remain unchanged.
253
+
254
+ ## Error codes
255
+
256
+ Mongo errors are passed back directly. Additional error codes:
257
+
258
+ #### 4100 -- Bad request - DB
259
+
260
+ - 4101 -- Invalid op version
261
+ - 4102 -- Invalid collection name
262
+ - 4103 -- $where queries disabled
263
+ - 4104 -- $mapReduce queries disabled
264
+ - 4105 -- $aggregate queries disabled
265
+ - 4106 -- $query property deprecated in queries
266
+ - 4107 -- Malformed query operator
267
+ - 4108 -- Only one collection operation allowed
268
+ - 4109 -- Only one cursor operation allowed
269
+ - 4110 -- Cursor methods can't run after collection method
270
+
271
+ #### 5100 -- Internal error - DB
123
272
 
124
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
125
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
126
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
127
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
128
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
129
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
130
- THE SOFTWARE.
273
+ - 5101 -- Already closed
274
+ - 5102 -- Snapshot missing last operation field
275
+ - 5103 -- Missing ops from requested version
276
+ - 5104 -- Failed to parse query