assai 0.1.4 → 0.2.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 (32) hide show
  1. package/README.md +5 -5
  2. package/index.mjs +2 -2
  3. package/package.json +1 -1
  4. package/src/{collection.mjs → factories/create_mongo_collection.mjs} +39 -21
  5. package/src/factories/index.mjs +1 -0
  6. package/src/types.ts +4 -0
  7. package/src/usecases/index.mjs +1 -0
  8. package/src/usecases/mongo/index.mjs +3 -0
  9. package/src/{mongo_client.mjs → usecases/mongo/mongo_client.mjs} +3 -3
  10. package/src/usecases/{operation → mongo/operation}/additional/delete_one_or_throw.mjs +2 -2
  11. package/src/usecases/{operation → mongo/operation}/count.mjs +1 -1
  12. package/src/usecases/{operation → mongo/operation}/delete_many.mjs +1 -1
  13. package/src/usecases/{operation → mongo/operation}/delete_one.mjs +2 -1
  14. package/src/usecases/{operation → mongo/operation}/find.mjs +3 -3
  15. package/src/usecases/{operation → mongo/operation}/find_one.mjs +3 -3
  16. package/src/usecases/{operation → mongo/operation}/insert_many.mjs +4 -2
  17. package/src/usecases/{operation → mongo/operation}/insert_one.mjs +6 -7
  18. package/src/usecases/{operation → mongo/operation}/update_many.mjs +1 -1
  19. package/src/usecases/{operation → mongo/operation}/update_one.mjs +1 -1
  20. package/src/usecases/mongo/test/manage_mock_registry.mjs +52 -0
  21. package/src/usecases/mongo/test/mock_get_collection.mjs +21 -0
  22. package/src/usecases/{transformers → mongo/transformers}/id/rename_find_options.mjs +1 -1
  23. /package/src/usecases/{generate_new_id.mjs → mongo/generate_new_id.mjs} +0 -0
  24. /package/src/usecases/{operation → mongo/operation}/additional/index.mjs +0 -0
  25. /package/src/usecases/{operation → mongo/operation}/index.mjs +0 -0
  26. /package/src/usecases/{transformers → mongo/transformers}/id/index.mjs +0 -0
  27. /package/src/usecases/{transformers → mongo/transformers}/id/rename_to_dev_id.mjs +0 -0
  28. /package/src/usecases/{transformers → mongo/transformers}/id/rename_to_mongo_id.mjs +0 -0
  29. /package/src/usecases/{transformers → mongo/transformers}/index.mjs +0 -0
  30. /package/src/usecases/{transformers → mongo/transformers}/object_id/ids_into_strings.mjs +0 -0
  31. /package/src/usecases/{transformers → mongo/transformers}/object_id/index.mjs +0 -0
  32. /package/src/usecases/{transformers → mongo/transformers}/object_id/strings_into_id.mjs +0 -0
package/README.md CHANGED
@@ -9,9 +9,9 @@ This is a small package to improve some things that you, as a developer, have to
9
9
  # Example
10
10
 
11
11
  ```js
12
- import {getCollection} from "assai"
12
+ import {createMongoCollection} from "assai"
13
13
 
14
- const collection = await getCollection()
14
+ const collection = await createMongoCollection()
15
15
  const docs = await collection.find({}, {limit: 10})
16
16
  /**
17
17
  * [{id: "507f1f77bcf86cd799439011", name: "Mario"}, ...]
@@ -85,13 +85,13 @@ A default environment variable is assumed: DATABASE_URL.
85
85
 
86
86
  Which makes it easier to start a connection:
87
87
  ```js
88
- const database = await getCollection('myCollection')
88
+ const database = await createMongoCollection('myCollection')
89
89
  ```
90
90
  This will read the value from `process.env.DATABASE_URL`.
91
91
 
92
92
  You can still pass a custom connection string:
93
93
  ```js
94
- const database = await getCollection('myCollection', {
94
+ const database = await createMongoCollection('myCollection', {
95
95
  cs: 'my connection string',
96
96
  })
97
97
  ```
@@ -125,7 +125,7 @@ router.post('/', (req, res) => {
125
125
  You can simply write:
126
126
  ```js
127
127
  router.post('/', (req, res) => {
128
- const col = getCollection('myCollection')
128
+ const col = createMongoCollection('myCollection')
129
129
  // Here the connection is opened only if it is not opened already.
130
130
  // Further calls to this route won't open a connection.
131
131
  await driver.insertOne({
package/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export * from './src/collection.mjs'
2
- export * from './src/mongo_client.mjs'
1
+ export * from './src/factories/index.mjs'
2
+ export * from './src/usecases/index.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assai",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "repository": {
5
5
  "url": "https://github.com/TimeLord2010/assai",
6
6
  "type": "git"
@@ -1,9 +1,11 @@
1
1
  import { Collection } from 'mongodb'
2
- import { getMongoClient } from './mongo_client.mjs'
3
- import { deleteOneOrThrow } from './usecases/operation/additional/index.mjs'
4
2
  import {
5
- count, deleteMany, deleteOne, find, findOne, insertMany, insertOne, updateMany, updateOne,
6
- } from './usecases/operation/index.mjs'
3
+ count, deleteMany, deleteOne,
4
+ deleteOneOrThrow,
5
+ find, findOne,
6
+ getClient,
7
+ insertMany, insertOne, updateMany, updateOne
8
+ } from '../usecases/mongo/index.mjs'
7
9
 
8
10
  /**
9
11
  * Generates a collection object that automatically manage ObjectId conversion to string.
@@ -13,14 +15,11 @@ import {
13
15
  *
14
16
  * The connection is cached by default. Use `collectionGetter` and `cachedCollectionGetter` to
15
17
  * customize this behavior.
16
- * @template {import('./types.js').MongoDocument} T
18
+ * @template {import('../types.js').MongoDocument} T
17
19
  * @param {string} name
18
- * @param {object} [options]
19
- * @param {IcollectionGetter<T>} [options.collectionGetter]
20
- * @param {IcollectionGetter<T>} [options.cachableCollectionGetter]
21
- * @param {string} [options.connectionString]
20
+ * @param {IcreateCollectionOptions<T>} [options]
22
21
  */
23
- export async function getCollection(name, options = {}) {
22
+ export async function createMongoCollection(name, options = {}) {
24
23
 
25
24
  /** @type {Collection<T> | null} */
26
25
  let _collection = null
@@ -41,7 +40,7 @@ export async function getCollection(name, options = {}) {
41
40
  }
42
41
 
43
42
  // Default connection getter
44
- const client = await getMongoClient({ connectionString })
43
+ const client = await getClient({ connectionString })
45
44
  let db = client.db()
46
45
  /** @type {Collection<T>} */
47
46
  _collection = db.collection(name)
@@ -54,33 +53,35 @@ export async function getCollection(name, options = {}) {
54
53
  */
55
54
  count: async (query = {}) => await count({ query, getCollection }),
56
55
  /**
57
- * @template {import('./types.js').Projection<T>} K
56
+ * @template {import('../types.js').Projection<T>} K
58
57
  * @param {import('mongodb').Filter<T>} query
59
- * @param {import('./types.js').FindOptions<T, K>} options
58
+ * @param {import('../types.js').FindOptions<T, K>} options
60
59
  * @returns {Promise<T[]>}
61
60
  */
62
61
  find: async (query, options = {}) => await find({ query, options, getCollection }),
63
62
  /**
64
- * @template {import('./types.js').Projection<T> | undefined} K
63
+ * @template {import('../types.js').Projection<T> | undefined} K
65
64
  * @param {import('mongodb').Filter<T>} query
66
- * @param {import('./types.js').FindOptions<T,K>} options
65
+ * @param {import('../types.js').FindOptions<T,K>} options
67
66
  * @returns {Promise<T | null>}
68
67
  */
69
68
  findOne: async (query, options = {}) => {
70
69
  return await findOne({ query, options, getCollection })
71
70
  },
72
71
  /**
73
- * @param {import('./types.js').Optional<T, 'id'>} doc
74
- * @returns {ReturnType<typeof insertOne<T>>}
72
+ * @param {import('../types.js').Optional<T, 'id'>} doc
73
+ * @returns {Promise<T>}
75
74
  */
76
75
  insertOne: async (doc) => await insertOne({ doc, getCollection }),
77
76
  /**
78
77
  *
79
- * @param {import('./types.js').Optional<T, 'id'>[]} docs
78
+ * @param {import('../types.js').Optional<T, 'id'>[]} docs
79
+ * @returns {Promise<T[]>}
80
80
  */
81
81
  insertMany: async (docs) => await insertMany({ docs, getCollection }),
82
82
  /**
83
83
  * @param {import('mongodb').Filter<T>} query
84
+ * @returns {Promise<boolean>}
84
85
  */
85
86
  deleteOne: async (query) => await deleteOne({ query, getCollection }),
86
87
  /**
@@ -109,12 +110,29 @@ export async function getCollection(name, options = {}) {
109
110
  }
110
111
 
111
112
  /**
112
- * @template {import('./types.js').MongoDocument} T
113
+ * @template {import('../types.js').MongoDocument} T
113
114
  * @callback IcollectionGetter
114
115
  * @returns {Promise<Collection<T>>}
115
116
  */
116
117
 
117
118
  /**
118
- * @template {import('./types.js').MongoDocument} T
119
- * @typedef {Awaited<ReturnType<typeof getCollection<T>>>} ICollection
119
+ * @template {import('../types.js').MongoDocument} T
120
+ * @typedef {object} IcreateCollectionOptions
121
+ * @property {IcollectionGetter<T>} [options.collectionGetter]
122
+ * A custom function to get the collection object from mongodb driver.
123
+ *
124
+ * This will be not be cached and will be used on every operation such as insertOne or findOne.
125
+ *
126
+ * If you wish to cache the collection object, see `cachableCollectionGetter`.
127
+ *
128
+ * @property {IcollectionGetter<T>} [options.cachableCollectionGetter]
129
+ * Same as `collectionGetter`. But the object is cached.
130
+ *
131
+ * @property {string} [options.connectionString]
132
+ * A custom connection string. If none is given, `process.env.DATABASE_URL` will be used.
133
+ */
134
+
135
+ /**
136
+ * @template {import('../types.js').MongoDocument} T
137
+ * @typedef {Awaited<ReturnType<typeof createMongoCollection<T>>>} ICollection
120
138
  */
@@ -0,0 +1 @@
1
+ export * from './create_mongo_collection.mjs'
package/src/types.ts CHANGED
@@ -7,6 +7,10 @@ export type MongoDocument = {
7
7
  [key: string]: any
8
8
  }
9
9
 
10
+ export type RequiredIfPresent<T, U> = {
11
+ [P in keyof T]: P extends keyof U ? Required<T[P]> : T[P];
12
+ };
13
+
10
14
  type NonNullProjection<T extends MongoDocument> = Partial<Record<keyof T, 1 | 0>>
11
15
 
12
16
  export type Projection<T extends MongoDocument> = NonNullProjection<T> | undefined
@@ -0,0 +1 @@
1
+ export * as mongo from './mongo/index.mjs'
@@ -0,0 +1,3 @@
1
+ export * from './generate_new_id.mjs'
2
+ export * from './mongo_client.mjs'
3
+ export * from './operation/index.mjs'
@@ -1,5 +1,5 @@
1
1
  import { MongoClient } from 'mongodb'
2
- import { OperationFail } from './data/errors/operation_fail.mjs'
2
+ import { OperationFail } from '../../data/errors/operation_fail.mjs'
3
3
 
4
4
  /** @type {MongoClient | null} */
5
5
  let client = null
@@ -9,7 +9,7 @@ let client = null
9
9
  * @param {object} params
10
10
  * @param {string} [params.connectionString]
11
11
  */
12
- export async function getMongoClient({
12
+ export async function getClient({
13
13
  connectionString,
14
14
  } = {}) {
15
15
  if (!client) {
@@ -27,7 +27,7 @@ export async function getMongoClient({
27
27
  return client
28
28
  }
29
29
 
30
- export async function closeMongoClient() {
30
+ export async function closeClient() {
31
31
  await client?.close()
32
32
  client = null
33
33
  }
@@ -1,9 +1,9 @@
1
1
  import { Collection } from 'mongodb'
2
- import { OperationFail } from '../../../data/errors/operation_fail.mjs'
2
+ import { OperationFail } from '../../../../data/errors/operation_fail.mjs'
3
3
  import { deleteOne } from '../delete_one.mjs'
4
4
 
5
5
  /**
6
- * @template {import('../../../types.js').MongoDocument} T
6
+ * @template {import('../../../../types.js').MongoDocument} T
7
7
  * @param {object} param
8
8
  * @param {import('mongodb').Filter<T>} param.query
9
9
  * @param {() => Promise<Collection<T>>} param.getCollection
@@ -2,7 +2,7 @@ import { Collection } from 'mongodb'
2
2
  import { renameToMongoId, stringsIntoId } from '../transformers/index.mjs'
3
3
 
4
4
  /**
5
- * @template {import('../../types.js').MongoDocument} T
5
+ * @template {import('../../../types.js').MongoDocument} T
6
6
  * @param {object} params
7
7
  * @param {() => Promise<Collection<T>>} params.getCollection
8
8
  * @param {import('mongodb').Filter<T>} [params.query]
@@ -2,7 +2,7 @@ import { Collection } from 'mongodb'
2
2
  import { renameToMongoId, stringsIntoId } from '../transformers/index.mjs'
3
3
 
4
4
  /**
5
- * @template {import('../../types.js').MongoDocument} T
5
+ * @template {import('../../../types.js').MongoDocument} T
6
6
  * @param {object} parameter
7
7
  * @param {import('mongodb').Filter<T>} parameter.query
8
8
  * @param {() => Promise<Collection<T>>} parameter.getCollection
@@ -2,10 +2,11 @@ import { Collection } from 'mongodb'
2
2
  import { renameToMongoId, stringsIntoId } from '../transformers/index.mjs'
3
3
 
4
4
  /**
5
- * @template {import('../../types.js').MongoDocument} T
5
+ * @template {import('../../../types.js').MongoDocument} T
6
6
  * @param {object} parameter
7
7
  * @param {import('mongodb').Filter<T>} parameter.query
8
8
  * @param {() => Promise<Collection<T>>} parameter.getCollection
9
+ * @returns {Promise<boolean>}
9
10
  */
10
11
  export async function deleteOne({ query, getCollection }) {
11
12
  renameToMongoId(query)
@@ -8,12 +8,12 @@ import {
8
8
  } from '../transformers/index.mjs'
9
9
 
10
10
  /**
11
- * @template {import('../../types.js').MongoDocument} T
12
- * @template {import('../../types.js').Projection<T>} K
11
+ * @template {import('../../../types.js').MongoDocument} T
12
+ * @template {import('../../../types.js').Projection<T>} K
13
13
  * @param {object} parameter
14
14
  * @param {() => Promise<Collection<T>>} parameter.getCollection
15
15
  * @param {import('mongodb').Filter<T>} parameter.query
16
- * @param {import('../../types.js').FindOptions<T, K>} [parameter.options]
16
+ * @param {import('../../../types.js').FindOptions<T, K>} [parameter.options]
17
17
  * @returns {Promise<T[]>}
18
18
  */
19
19
  export async function find({ getCollection, query, options }) {
@@ -2,11 +2,11 @@ import { Collection } from 'mongodb'
2
2
  import { find } from './find.mjs'
3
3
 
4
4
  /**
5
- * @template {import('../../types.js').MongoDocument} T
6
- * @template {import('../../types.js').Projection<T> | undefined} K
5
+ * @template {import('../../../types.js').MongoDocument} T
6
+ * @template {import('../../../types.js').Projection<T> | undefined} K
7
7
  * @param {object} param
8
8
  * @param {import('mongodb').Filter<T>} param.query
9
- * @param {import('../../types.js').FindOptions<T, K>} [param.options]
9
+ * @param {import('../../../types.js').FindOptions<T, K>} [param.options]
10
10
  * @param {() => Promise<Collection<T>>} param.getCollection
11
11
  */
12
12
  export async function findOne({ query, options, getCollection }) {
@@ -2,10 +2,11 @@ import { Collection, ObjectId } from 'mongodb'
2
2
  import { renameToMongoId, stringsIntoId } from '../transformers/index.mjs'
3
3
 
4
4
  /**
5
- * @template {import('../../types.js').MongoDocument} T
5
+ * @template {import('../../../types.js').MongoDocument} T
6
6
  * @param {object} param
7
- * @param {import('../../types.js').Optional<T, 'id'>[]} param.docs
7
+ * @param {import('../../../types.js').Optional<T, 'id'>[]} param.docs
8
8
  * @param {() => Promise<Collection<T>>} param.getCollection
9
+ * @returns {Promise<T[]>}
9
10
  */
10
11
  export async function insertMany({ docs, getCollection }) {
11
12
  if (docs.length == 0) return []
@@ -26,5 +27,6 @@ export async function insertMany({ docs, getCollection }) {
26
27
  docs[index].id = id.toHexString()
27
28
  }
28
29
  }
30
+ // @ts-ignore
29
31
  return docs
30
32
  }
@@ -2,11 +2,11 @@ import { Collection, ObjectId } from 'mongodb'
2
2
  import { renameToMongoId, stringsIntoId } from '../transformers/index.mjs'
3
3
 
4
4
  /**
5
- * @template {import('../../types.js').MongoDocument} T
5
+ * @template {import('../../../types.js').MongoDocument} T
6
6
  * @param {object} param
7
- * @param {import('../../types.js').Optional<T, 'id'>} param.doc
7
+ * @param {import('../../../types.js').Optional<T, 'id'>} param.doc
8
8
  * @param {() => Promise<Collection<T>>} param.getCollection
9
- * @returns {Promise<Omit<T, 'id'> & {id: NonNullable<T['id']>;}>}
9
+ * @returns {Promise<T>}
10
10
  */
11
11
  export async function insertOne({ doc, getCollection }) {
12
12
  renameToMongoId(doc)
@@ -21,8 +21,7 @@ export async function insertOne({ doc, getCollection }) {
21
21
  // @ts-ignore
22
22
  id = id.toHexString()
23
23
  }
24
- return {
25
- id,
26
- ...doc,
27
- }
24
+
25
+ // @ts-ignore
26
+ return { id, ...doc }
28
27
  }
@@ -2,7 +2,7 @@ import { Collection } from 'mongodb'
2
2
  import { renameToMongoId, stringsIntoId } from '../transformers/index.mjs'
3
3
 
4
4
  /**
5
- * @template {import('../../types.js').MongoDocument} T
5
+ * @template {import('../../../types.js').MongoDocument} T
6
6
  * @param {object} param
7
7
  * @param {import('mongodb').Filter<T>} param.query
8
8
  * @param {import('mongodb').UpdateFilter<T>} param.update
@@ -2,7 +2,7 @@ import { Collection } from 'mongodb'
2
2
  import { renameToMongoId, stringsIntoId } from '../transformers/index.mjs'
3
3
 
4
4
  /**
5
- * @template {import('../../types.js').MongoDocument} T
5
+ * @template {import('../../../types.js').MongoDocument} T
6
6
  * @param {object} param
7
7
  * @param {import('mongodb').Filter<T>} param.query
8
8
  * @param {import('mongodb').UpdateFilter<T>} param.update
@@ -0,0 +1,52 @@
1
+ import { fakerPT_BR } from '@faker-js/faker'
2
+ import { ObjectId } from 'mongodb'
3
+ import { after, before } from 'node:test'
4
+ import { generateNewId } from '../generate_new_id.mjs'
5
+ import { closeClient } from '../mongo_client.mjs'
6
+ import { deleteOne, insertOne } from '../operation/index.mjs'
7
+ import { mockGetCollection } from './mock_get_collection.mjs'
8
+
9
+ /**
10
+ *
11
+ * @param {import('./mock_get_collection.mjs').ItestCollection} param
12
+ * @param {object} options
13
+ * @param {boolean} [options.deleteAtEnd]
14
+ */
15
+ export function manageMockRegistry({ tag, ...rest } = {}, {
16
+ deleteAtEnd = true
17
+ } = {}) {
18
+
19
+ const id = generateNewId()
20
+ const name = rest.name ?? fakerPT_BR.person.fullName()
21
+
22
+ before(async () => {
23
+ await insertOne({
24
+ // @ts-ignore
25
+ getCollection: mockGetCollection,
26
+ doc: {
27
+ id,
28
+ createdAt: new Date(),
29
+ tag,
30
+ name: name,
31
+ ...rest,
32
+ },
33
+ })
34
+ })
35
+
36
+ after(async () => {
37
+ if (deleteAtEnd) {
38
+ await deleteOne({
39
+ query: {
40
+ _id: ObjectId.createFromHexString(id)
41
+ },
42
+ // @ts-ignore
43
+ getCollection: mockGetCollection,
44
+ })
45
+ }
46
+ await closeClient()
47
+ })
48
+
49
+ return {
50
+ id, name
51
+ }
52
+ }
@@ -0,0 +1,21 @@
1
+ import { Collection, ObjectId } from 'mongodb'
2
+ import { getClient } from '../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
+ */
@@ -2,7 +2,7 @@ import { renameToMongoId } from './index.mjs'
2
2
 
3
3
  /**
4
4
  *
5
- * @param {import('../../../types.js').FindOptions<any ,any>} options
5
+ * @param {import('../../../../types.js').FindOptions<any ,any>} options
6
6
  */
7
7
  export function renameFindOptions(options = {}) {
8
8
  renameToMongoId(options.projection)