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.
- package/README.md +148 -148
- package/dist/src/factories/create_mongo_collection.d.mts +55 -11
- package/dist/src/usecases/mongo/operation/aggregate.d.mts +16 -0
- package/dist/src/usecases/mongo/operation/bulk_write.d.mts +15 -0
- package/dist/src/usecases/mongo/operation/find_one_and_delete.d.mts +16 -0
- package/dist/src/usecases/mongo/operation/find_one_and_replace.d.mts +18 -0
- package/dist/src/usecases/mongo/operation/find_one_and_update.d.mts +18 -0
- package/dist/src/usecases/mongo/operation/index.d.mts +5 -0
- package/index.mjs +2 -2
- package/jsconfig.json +17 -20
- package/jsconfig.prod.json +12 -12
- package/package.json +40 -39
- package/src/__test/manage_mock_database.mjs +24 -24
- package/src/__test/manage_mock_registry.mjs +54 -54
- package/src/__test/mock_get_collection.mjs +20 -20
- package/src/data/errors/operation_fail.mjs +9 -9
- package/src/data/errors/unsupported_error.mjs +9 -9
- package/src/data/interfaces/mongo_operator.mjs +2 -2
- package/src/data/interfaces/native_collection.mjs +27 -27
- package/src/factories/create_mock_collection.mjs +173 -173
- package/src/factories/create_mongo_collection.mjs +224 -181
- package/src/factories/index.mjs +1 -1
- package/src/types.ts +31 -31
- package/src/usecases/index.mjs +1 -1
- package/src/usecases/mongo/generate_new_id.mjs +4 -4
- package/src/usecases/mongo/index.mjs +3 -3
- package/src/usecases/mongo/mongo_client.mjs +36 -36
- package/src/usecases/mongo/operation/additional/delete_one_or_throw.mjs +17 -17
- package/src/usecases/mongo/operation/additional/index.mjs +1 -1
- package/src/usecases/mongo/operation/aggregate.mjs +26 -0
- package/src/usecases/mongo/operation/bulk_write.mjs +54 -0
- package/src/usecases/mongo/operation/count.mjs +16 -16
- package/src/usecases/mongo/operation/delete_many.mjs +15 -15
- package/src/usecases/mongo/operation/delete_one.mjs +16 -16
- package/src/usecases/mongo/operation/find.mjs +34 -34
- package/src/usecases/mongo/operation/find_one.mjs +32 -32
- package/src/usecases/mongo/operation/find_one_and_delete.mjs +23 -0
- package/src/usecases/mongo/operation/find_one_and_replace.mjs +24 -0
- package/src/usecases/mongo/operation/find_one_and_update.mjs +40 -0
- package/src/usecases/mongo/operation/index.mjs +15 -10
- package/src/usecases/mongo/operation/insert_many.mjs +39 -39
- package/src/usecases/mongo/operation/insert_one.mjs +32 -32
- package/src/usecases/mongo/operation/update_many.mjs +39 -39
- package/src/usecases/mongo/operation/update_one.mjs +40 -40
- package/src/usecases/mongo/transformers/id/index.mjs +4 -4
- package/src/usecases/mongo/transformers/id/rename_find_options.mjs +12 -12
- package/src/usecases/mongo/transformers/id/rename_to_dev_id.mjs +13 -13
- package/src/usecases/mongo/transformers/id/rename_to_mongo_id.mjs +13 -13
- package/src/usecases/mongo/transformers/index.mjs +2 -2
- package/src/usecases/mongo/transformers/input_transformer.mjs +32 -32
- package/src/usecases/mongo/transformers/object_id/ids_into_strings.mjs +29 -29
- package/src/usecases/mongo/transformers/object_id/index.mjs +3 -3
- package/src/usecases/mongo/transformers/object_id/strings_into_id.mjs +44 -44
- package/src/usecases/mongo/transformers/output_transformer.mjs +18 -18
- 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
|
}
|