assai 2.0.0 → 2.1.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.
Files changed (55) hide show
  1. package/README.md +148 -148
  2. package/dist/src/factories/create_mongo_collection.d.mts +55 -11
  3. package/dist/src/usecases/mongo/operation/aggregate.d.mts +16 -0
  4. package/dist/src/usecases/mongo/operation/bulk_write.d.mts +15 -0
  5. package/dist/src/usecases/mongo/operation/find_one_and_delete.d.mts +16 -0
  6. package/dist/src/usecases/mongo/operation/find_one_and_replace.d.mts +18 -0
  7. package/dist/src/usecases/mongo/operation/find_one_and_update.d.mts +18 -0
  8. package/dist/src/usecases/mongo/operation/index.d.mts +5 -0
  9. package/index.mjs +2 -2
  10. package/jsconfig.json +17 -20
  11. package/jsconfig.prod.json +12 -12
  12. package/package.json +40 -39
  13. package/src/__test/manage_mock_database.mjs +24 -24
  14. package/src/__test/manage_mock_registry.mjs +54 -54
  15. package/src/__test/mock_get_collection.mjs +20 -20
  16. package/src/data/errors/operation_fail.mjs +9 -9
  17. package/src/data/errors/unsupported_error.mjs +9 -9
  18. package/src/data/interfaces/mongo_operator.mjs +2 -2
  19. package/src/data/interfaces/native_collection.mjs +27 -27
  20. package/src/factories/create_mock_collection.mjs +173 -173
  21. package/src/factories/create_mongo_collection.mjs +224 -181
  22. package/src/factories/index.mjs +1 -1
  23. package/src/types.ts +31 -31
  24. package/src/usecases/index.mjs +1 -1
  25. package/src/usecases/mongo/generate_new_id.mjs +4 -4
  26. package/src/usecases/mongo/index.mjs +3 -3
  27. package/src/usecases/mongo/mongo_client.mjs +36 -36
  28. package/src/usecases/mongo/operation/additional/delete_one_or_throw.mjs +17 -17
  29. package/src/usecases/mongo/operation/additional/index.mjs +1 -1
  30. package/src/usecases/mongo/operation/aggregate.mjs +26 -0
  31. package/src/usecases/mongo/operation/bulk_write.mjs +54 -0
  32. package/src/usecases/mongo/operation/count.mjs +16 -16
  33. package/src/usecases/mongo/operation/delete_many.mjs +15 -15
  34. package/src/usecases/mongo/operation/delete_one.mjs +16 -16
  35. package/src/usecases/mongo/operation/find.mjs +34 -34
  36. package/src/usecases/mongo/operation/find_one.mjs +32 -32
  37. package/src/usecases/mongo/operation/find_one_and_delete.mjs +23 -0
  38. package/src/usecases/mongo/operation/find_one_and_replace.mjs +24 -0
  39. package/src/usecases/mongo/operation/find_one_and_update.mjs +40 -0
  40. package/src/usecases/mongo/operation/index.mjs +15 -10
  41. package/src/usecases/mongo/operation/insert_many.mjs +39 -39
  42. package/src/usecases/mongo/operation/insert_one.mjs +32 -32
  43. package/src/usecases/mongo/operation/update_many.mjs +39 -39
  44. package/src/usecases/mongo/operation/update_one.mjs +40 -40
  45. package/src/usecases/mongo/transformers/id/index.mjs +4 -4
  46. package/src/usecases/mongo/transformers/id/rename_find_options.mjs +12 -12
  47. package/src/usecases/mongo/transformers/id/rename_to_dev_id.mjs +13 -13
  48. package/src/usecases/mongo/transformers/id/rename_to_mongo_id.mjs +13 -13
  49. package/src/usecases/mongo/transformers/index.mjs +2 -2
  50. package/src/usecases/mongo/transformers/input_transformer.mjs +32 -32
  51. package/src/usecases/mongo/transformers/object_id/ids_into_strings.mjs +29 -29
  52. package/src/usecases/mongo/transformers/object_id/index.mjs +3 -3
  53. package/src/usecases/mongo/transformers/object_id/strings_into_id.mjs +44 -44
  54. package/src/usecases/mongo/transformers/output_transformer.mjs +18 -18
  55. package/src/usecases/mongo/transformers/timestamps.mjs +27 -22
@@ -1,25 +1,25 @@
1
- import { MongoMemoryServer } from 'mongodb-memory-server'
2
- import { after, before } from 'node:test'
3
- import { closeClient } from '../usecases/mongo/mongo_client.mjs'
4
-
5
- /**
6
- * Starts a memory version of the mongodb server on a random free port.
7
- *
8
- * This method will also automatically close the connection with the database.
9
- */
10
- export function manageMockDatabase() {
11
- /** @type {null | MongoMemoryServer} */
12
- let server = null
13
-
14
- before(async () => {
15
- server = await MongoMemoryServer.create()
16
- const uri = server.getUri('test')
17
- process.env.DATABASE_URL = uri
18
- })
19
-
20
- after(async () => {
21
- await closeClient()
22
- await server?.stop()
23
- process.exit()
24
- })
1
+ import { MongoMemoryServer } from 'mongodb-memory-server'
2
+ import { after, before } from 'node:test'
3
+ import { closeClient } from '../usecases/mongo/mongo_client.mjs'
4
+
5
+ /**
6
+ * Starts a memory version of the mongodb server on a random free port.
7
+ *
8
+ * This method will also automatically close the connection with the database.
9
+ */
10
+ export function manageMockDatabase() {
11
+ /** @type {null | MongoMemoryServer} */
12
+ let server = null
13
+
14
+ before(async () => {
15
+ server = await MongoMemoryServer.create()
16
+ const uri = server.getUri('test')
17
+ process.env.DATABASE_URL = uri
18
+ })
19
+
20
+ after(async () => {
21
+ await closeClient()
22
+ await server?.stop()
23
+ process.exit()
24
+ })
25
25
  }
@@ -1,55 +1,55 @@
1
- import { fakerPT_BR } from '@faker-js/faker'
2
- import { ObjectId } from 'mongodb'
3
- import { after, before } from 'node:test'
4
- import { generateNewId } from '../usecases/mongo/generate_new_id.mjs'
5
- import { closeClient } from '../usecases/mongo/mongo_client.mjs'
6
- import { deleteOne } from '../usecases/mongo/operation/delete_one.mjs'
7
- import { insertOne } from '../usecases/mongo/operation/insert_one.mjs'
8
- import { mockGetCollection } from './mock_get_collection.mjs'
9
-
10
- /**
11
- *
12
- * @param {import('./mock_get_collection.mjs').ItestCollection} param
13
- * @param {object} options
14
- * @param {boolean} [options.deleteAtEnd]
15
- */
16
- export function manageMockRegistry({ tag, ...rest } = {}, {
17
- deleteAtEnd = true
18
- } = {}) {
19
-
20
- const id = generateNewId()
21
- const name = rest.name ?? fakerPT_BR.person.fullName()
22
-
23
- before(async () => {
24
- /** @type {import('./mock_get_collection.mjs').ItestCollection} */
25
- const doc = {
26
- id,
27
- createdAt: new Date(),
28
- tag,
29
- name: name,
30
- ...rest,
31
- }
32
- await insertOne({
33
- // @ts-ignore
34
- getCollection: mockGetCollection,
35
- doc: doc,
36
- })
37
- })
38
-
39
- after(async () => {
40
- if (deleteAtEnd) {
41
- await deleteOne({
42
- query: {
43
- _id: ObjectId.createFromHexString(id)
44
- },
45
- // @ts-ignore
46
- getCollection: mockGetCollection,
47
- })
48
- }
49
- await closeClient()
50
- })
51
-
52
- return {
53
- id, name
54
- }
1
+ import { fakerPT_BR } from '@faker-js/faker'
2
+ import { ObjectId } from 'mongodb'
3
+ import { after, before } from 'node:test'
4
+ import { generateNewId } from '../usecases/mongo/generate_new_id.mjs'
5
+ import { closeClient } from '../usecases/mongo/mongo_client.mjs'
6
+ import { deleteOne } from '../usecases/mongo/operation/delete_one.mjs'
7
+ import { insertOne } from '../usecases/mongo/operation/insert_one.mjs'
8
+ import { mockGetCollection } from './mock_get_collection.mjs'
9
+
10
+ /**
11
+ *
12
+ * @param {import('./mock_get_collection.mjs').ItestCollection} param
13
+ * @param {object} options
14
+ * @param {boolean} [options.deleteAtEnd]
15
+ */
16
+ export function manageMockRegistry({ tag, ...rest } = {}, {
17
+ deleteAtEnd = true
18
+ } = {}) {
19
+
20
+ const id = generateNewId()
21
+ const name = rest.name ?? fakerPT_BR.person.fullName()
22
+
23
+ before(async () => {
24
+ /** @type {import('./mock_get_collection.mjs').ItestCollection} */
25
+ const doc = {
26
+ id,
27
+ createdAt: new Date(),
28
+ tag,
29
+ name: name,
30
+ ...rest,
31
+ }
32
+ await insertOne({
33
+ // @ts-ignore
34
+ getCollection: mockGetCollection,
35
+ doc: doc,
36
+ })
37
+ })
38
+
39
+ after(async () => {
40
+ if (deleteAtEnd) {
41
+ await deleteOne({
42
+ query: {
43
+ _id: ObjectId.createFromHexString(id)
44
+ },
45
+ // @ts-ignore
46
+ getCollection: mockGetCollection,
47
+ })
48
+ }
49
+ await closeClient()
50
+ })
51
+
52
+ return {
53
+ id, name
54
+ }
55
55
  }
@@ -1,21 +1,21 @@
1
- import { Collection, ObjectId } from 'mongodb'
2
- import { getClient } from '../usecases/mongo/mongo_client.mjs'
3
-
4
- export async function mockGetCollection(collectionName = 'test') {
5
- const client = await getClient()
6
- const db = client.db()
7
- /** @type {Collection<ItestCollection>} */
8
- const collection = db.collection(collectionName)
9
- return collection
10
- }
11
-
12
- /**
13
- * @typedef {object} ItestCollection
14
- * @property {ObjectId} [_id]
15
- * @property {string} [id]
16
- * @property {string} [name]
17
- * @property {string | ObjectId} [tag]
18
- * @property {Date} [createdAt]
19
- * @property {object[]} [posts]
20
- * @property {object} [address]
1
+ import { Collection, ObjectId } from 'mongodb'
2
+ import { getClient } from '../usecases/mongo/mongo_client.mjs'
3
+
4
+ export async function mockGetCollection(collectionName = 'test') {
5
+ const client = await getClient()
6
+ const db = client.db()
7
+ /** @type {Collection<ItestCollection>} */
8
+ const collection = db.collection(collectionName)
9
+ return collection
10
+ }
11
+
12
+ /**
13
+ * @typedef {object} ItestCollection
14
+ * @property {ObjectId} [_id]
15
+ * @property {string} [id]
16
+ * @property {string} [name]
17
+ * @property {string | ObjectId} [tag]
18
+ * @property {Date} [createdAt]
19
+ * @property {object[]} [posts]
20
+ * @property {object} [address]
21
21
  */
@@ -1,10 +1,10 @@
1
- export class OperationFail extends Error {
2
-
3
- /**
4
- *
5
- * @param {string} msg
6
- */
7
- constructor(msg) {
8
- super(msg)
9
- }
1
+ export class OperationFail extends Error {
2
+
3
+ /**
4
+ *
5
+ * @param {string} msg
6
+ */
7
+ constructor(msg) {
8
+ super(msg)
9
+ }
10
10
  }
@@ -1,10 +1,10 @@
1
- export class NotImplemented extends Error {
2
-
3
- /**
4
- *
5
- * @param {string} msg
6
- */
7
- constructor(msg) {
8
- super(msg)
9
- }
1
+ export class NotImplemented extends Error {
2
+
3
+ /**
4
+ *
5
+ * @param {string} msg
6
+ */
7
+ constructor(msg) {
8
+ super(msg)
9
+ }
10
10
  }
@@ -1,3 +1,3 @@
1
- /**
2
- * @typedef {'$eq' | '$lt' | '$lte'} ImongoOperator
1
+ /**
2
+ * @typedef {'$eq' | '$lt' | '$lte'} ImongoOperator
3
3
  */
@@ -1,28 +1,28 @@
1
- /**
2
- * @template {import('mongodb').Document} T
3
- * @typedef {object} InativeCollection
4
- * @property {InativeInsertOne<T>} insertOne
5
- * @property {InativeDeleteOne<T>} deleteOne
6
- * @property {InativeUpdateOne<T>} updateOne
7
- */
8
-
9
- /**
10
- * @template {import('mongodb').Document} T
11
- * @callback InativeInsertOne
12
- * @param {import('../../types.js').Optional<T, '_id'>} data
13
- * @returns {Promise<any>}
14
- */
15
-
16
- /**
17
- * @template {import('mongodb').Document} T
18
- * @callback InativeDeleteOne
19
- * @param {import('mongodb').Filter<T>} filter
20
- * @returns {Promise<any>}
21
- */
22
-
23
- /**
24
- * @template {import('mongodb').Document} T
25
- * @callback InativeUpdateOne
26
- * @param {import('mongodb').Filter<T>} filter
27
- * @param {import('mongodb').UpdateFilter<T>} update
1
+ /**
2
+ * @template {import('mongodb').Document} T
3
+ * @typedef {object} InativeCollection
4
+ * @property {InativeInsertOne<T>} insertOne
5
+ * @property {InativeDeleteOne<T>} deleteOne
6
+ * @property {InativeUpdateOne<T>} updateOne
7
+ */
8
+
9
+ /**
10
+ * @template {import('mongodb').Document} T
11
+ * @callback InativeInsertOne
12
+ * @param {import('../../types.js').Optional<T, '_id'>} data
13
+ * @returns {Promise<any>}
14
+ */
15
+
16
+ /**
17
+ * @template {import('mongodb').Document} T
18
+ * @callback InativeDeleteOne
19
+ * @param {import('mongodb').Filter<T>} filter
20
+ * @returns {Promise<any>}
21
+ */
22
+
23
+ /**
24
+ * @template {import('mongodb').Document} T
25
+ * @callback InativeUpdateOne
26
+ * @param {import('mongodb').Filter<T>} filter
27
+ * @param {import('mongodb').UpdateFilter<T>} update
28
28
  */
@@ -1,174 +1,174 @@
1
- import { ObjectId } from 'mongodb'
2
- import { NotImplemented } from '../data/errors/unsupported_error.mjs'
3
-
4
- /** @type {Record<string, any[]>} */
5
- const memory = {}
6
-
7
- /**
8
- * @template {import('mongodb').Document} T
9
- * @param {string} [name]
10
- * @returns {import('../data/interfaces/native_collection.mjs').InativeCollection<T>}
11
- */
12
- export function createMockCollection(name = 'mock') {
13
-
14
- function getItems() {
15
- let items = memory[name]
16
- if (items == null) {
17
- items = []
18
- memory[name] = items
19
- }
20
- return items
21
- }
22
-
23
- /**
24
- *
25
- * @param {import('mongodb').Filter<T>} filter
26
- */
27
- function getFilteredItems(filter) {
28
- const keys = Object.keys(filter)
29
- const dollarFilters = keys.filter(x => x.startsWith('$'))
30
- if (dollarFilters.length > 0) {
31
- throw new NotImplemented('root dollar sign filters not supported')
32
- }
33
-
34
- /** @type {((item: T) => boolean)[]} */
35
- const fieldFilter = []
36
- for (const field of keys) {
37
- const value = filter[field]
38
- /**
39
- * @returns {import('../data/interfaces/mongo_operator.mjs').ImongoOperator[]}
40
- */
41
- function getOperators() {
42
- if (value == null) return ['$eq']
43
- if (typeof value != 'object') return ['$eq']
44
- if (Array.isArray(value)) return ['$eq']
45
- if (value instanceof RegExp) {
46
- throw new NotImplemented('Search using a regular expression is not supported')
47
- }
48
-
49
- // Getting operators used in query
50
- const fieldInnerKeys = Object.keys(value)
51
- const operators = fieldInnerKeys.filter(x => x.startsWith('$'))
52
- const hasOperator = operators.length > 0
53
-
54
- // Lack of operator presumes equal operator
55
- if (!hasOperator) return ['$eq']
56
-
57
- const areAllKeysOperators = fieldInnerKeys.every(x => x.startsWith('$'))
58
- if (!areAllKeysOperators) {
59
- // Means the object contained operators (such as "$eq" or "$lt"),
60
- // But not all keys are operators, which is not valid.
61
- throw new Error(`The value on field ${field} does not look right...`)
62
- }
63
-
64
- // Reading operators from object
65
- /** @type {import('../data/interfaces/mongo_operator.mjs').ImongoOperator[]} */
66
- const usedOperators = []
67
- for (const operator of operators) {
68
- switch (operator) {
69
- case '$eq':
70
- usedOperators.push('$eq')
71
- default:
72
- throw new NotImplemented(`Operator ${operator} not implemented`)
73
- }
74
- }
75
- return usedOperators
76
- }
77
-
78
- const operators = getOperators()
79
- for (const operator of operators) {
80
- switch (operator) {
81
- case '$eq':
82
- fieldFilter.push((doc) => {
83
- const savedValue = doc[field]
84
- return savedValue == value
85
- })
86
- default:
87
- throw new NotImplemented(`Operator ${operator} is not supported`)
88
- }
89
- }
90
- }
91
-
92
- const allCollectionItems = getItems()
93
- return allCollectionItems.filter(savedDoc => {
94
- return fieldFilter.every(comparator => {
95
- return comparator(savedDoc)
96
- })
97
- })
98
- }
99
-
100
- /**
101
- * $set
102
- * @param {*} doc
103
- * @param {*} update The update object. Should not contain operator
104
- */
105
- function performSetUpdate(doc, update) {
106
- for (const [field, newValue] of Object.entries(update)) {
107
- doc[field] = newValue
108
- }
109
- }
110
-
111
- return {
112
- async insertOne(doc) {
113
- const items = getItems()
114
- if (doc._id == null) {
115
- // @ts-ignore
116
- doc._id = new ObjectId()
117
- }
118
- items.push(doc)
119
- },
120
- async deleteOne(filter) {
121
- const documentsToDelete = getFilteredItems(filter)
122
- const allCollectionItems = getItems()
123
-
124
- // Deleting the documents, one document at a time
125
- while (documentsToDelete.length > 0) {
126
- const docToDelete = documentsToDelete[0]
127
- const index = allCollectionItems.indexOf(docToDelete)
128
- if (index < 0) {
129
- throw new Error('Document extracted from memory was not found in memory')
130
- }
131
- allCollectionItems.splice(index, 1)
132
- }
133
- },
134
- async updateOne(filter, update) {
135
- // Getting document to update
136
- const documentsToUpdate = getFilteredItems(filter)
137
- if (documentsToUpdate.length == 0) {
138
- return
139
- }
140
- const docToUpdate = documentsToUpdate[0]
141
-
142
- const updateKeys = Object.keys(update)
143
- const operators = updateKeys.filter(x => x.startsWith('$'))
144
-
145
- // Checking for empty updates
146
- if (updateKeys.length == 0) {
147
- return
148
- }
149
-
150
- // Is a simple $set operation
151
- if (operators.length == 0) {
152
- performSetUpdate(docToUpdate, update)
153
- return
154
- }
155
-
156
- // Validating update object
157
- if (updateKeys.length != operators.length) {
158
- throw new Error('This update does not look right...')
159
- }
160
-
161
- // Processing complex update
162
- for (const operator of operators) {
163
- const value = update[operator]
164
- switch (operator) {
165
- case '$set':
166
- performSetUpdate(docToUpdate, value)
167
- break
168
- default:
169
- throw new Error(`Operator ${operator} not supported`)
170
- }
171
- }
172
- }
173
- }
1
+ import { ObjectId } from 'mongodb'
2
+ import { NotImplemented } from '../data/errors/unsupported_error.mjs'
3
+
4
+ /** @type {Record<string, any[]>} */
5
+ const memory = {}
6
+
7
+ /**
8
+ * @template {import('mongodb').Document} T
9
+ * @param {string} [name]
10
+ * @returns {import('../data/interfaces/native_collection.mjs').InativeCollection<T>}
11
+ */
12
+ export function createMockCollection(name = 'mock') {
13
+
14
+ function getItems() {
15
+ let items = memory[name]
16
+ if (items == null) {
17
+ items = []
18
+ memory[name] = items
19
+ }
20
+ return items
21
+ }
22
+
23
+ /**
24
+ *
25
+ * @param {import('mongodb').Filter<T>} filter
26
+ */
27
+ function getFilteredItems(filter) {
28
+ const keys = Object.keys(filter)
29
+ const dollarFilters = keys.filter(x => x.startsWith('$'))
30
+ if (dollarFilters.length > 0) {
31
+ throw new NotImplemented('root dollar sign filters not supported')
32
+ }
33
+
34
+ /** @type {((item: T) => boolean)[]} */
35
+ const fieldFilter = []
36
+ for (const field of keys) {
37
+ const value = filter[field]
38
+ /**
39
+ * @returns {import('../data/interfaces/mongo_operator.mjs').ImongoOperator[]}
40
+ */
41
+ function getOperators() {
42
+ if (value == null) return ['$eq']
43
+ if (typeof value != 'object') return ['$eq']
44
+ if (Array.isArray(value)) return ['$eq']
45
+ if (value instanceof RegExp) {
46
+ throw new NotImplemented('Search using a regular expression is not supported')
47
+ }
48
+
49
+ // Getting operators used in query
50
+ const fieldInnerKeys = Object.keys(value)
51
+ const operators = fieldInnerKeys.filter(x => x.startsWith('$'))
52
+ const hasOperator = operators.length > 0
53
+
54
+ // Lack of operator presumes equal operator
55
+ if (!hasOperator) return ['$eq']
56
+
57
+ const areAllKeysOperators = fieldInnerKeys.every(x => x.startsWith('$'))
58
+ if (!areAllKeysOperators) {
59
+ // Means the object contained operators (such as "$eq" or "$lt"),
60
+ // But not all keys are operators, which is not valid.
61
+ throw new Error(`The value on field ${field} does not look right...`)
62
+ }
63
+
64
+ // Reading operators from object
65
+ /** @type {import('../data/interfaces/mongo_operator.mjs').ImongoOperator[]} */
66
+ const usedOperators = []
67
+ for (const operator of operators) {
68
+ switch (operator) {
69
+ case '$eq':
70
+ usedOperators.push('$eq')
71
+ default:
72
+ throw new NotImplemented(`Operator ${operator} not implemented`)
73
+ }
74
+ }
75
+ return usedOperators
76
+ }
77
+
78
+ const operators = getOperators()
79
+ for (const operator of operators) {
80
+ switch (operator) {
81
+ case '$eq':
82
+ fieldFilter.push((doc) => {
83
+ const savedValue = doc[field]
84
+ return savedValue == value
85
+ })
86
+ default:
87
+ throw new NotImplemented(`Operator ${operator} is not supported`)
88
+ }
89
+ }
90
+ }
91
+
92
+ const allCollectionItems = getItems()
93
+ return allCollectionItems.filter(savedDoc => {
94
+ return fieldFilter.every(comparator => {
95
+ return comparator(savedDoc)
96
+ })
97
+ })
98
+ }
99
+
100
+ /**
101
+ * $set
102
+ * @param {*} doc
103
+ * @param {*} update The update object. Should not contain operator
104
+ */
105
+ function performSetUpdate(doc, update) {
106
+ for (const [field, newValue] of Object.entries(update)) {
107
+ doc[field] = newValue
108
+ }
109
+ }
110
+
111
+ return {
112
+ async insertOne(doc) {
113
+ const items = getItems()
114
+ if (doc._id == null) {
115
+ // @ts-ignore
116
+ doc._id = new ObjectId()
117
+ }
118
+ items.push(doc)
119
+ },
120
+ async deleteOne(filter) {
121
+ const documentsToDelete = getFilteredItems(filter)
122
+ const allCollectionItems = getItems()
123
+
124
+ // Deleting the documents, one document at a time
125
+ while (documentsToDelete.length > 0) {
126
+ const docToDelete = documentsToDelete[0]
127
+ const index = allCollectionItems.indexOf(docToDelete)
128
+ if (index < 0) {
129
+ throw new Error('Document extracted from memory was not found in memory')
130
+ }
131
+ allCollectionItems.splice(index, 1)
132
+ }
133
+ },
134
+ async updateOne(filter, update) {
135
+ // Getting document to update
136
+ const documentsToUpdate = getFilteredItems(filter)
137
+ if (documentsToUpdate.length == 0) {
138
+ return
139
+ }
140
+ const docToUpdate = documentsToUpdate[0]
141
+
142
+ const updateKeys = Object.keys(update)
143
+ const operators = updateKeys.filter(x => x.startsWith('$'))
144
+
145
+ // Checking for empty updates
146
+ if (updateKeys.length == 0) {
147
+ return
148
+ }
149
+
150
+ // Is a simple $set operation
151
+ if (operators.length == 0) {
152
+ performSetUpdate(docToUpdate, update)
153
+ return
154
+ }
155
+
156
+ // Validating update object
157
+ if (updateKeys.length != operators.length) {
158
+ throw new Error('This update does not look right...')
159
+ }
160
+
161
+ // Processing complex update
162
+ for (const operator of operators) {
163
+ const value = update[operator]
164
+ switch (operator) {
165
+ case '$set':
166
+ performSetUpdate(docToUpdate, value)
167
+ break
168
+ default:
169
+ throw new Error(`Operator ${operator} not supported`)
170
+ }
171
+ }
172
+ }
173
+ }
174
174
  }