@nimbus-cqrs/mongodb 2.0.0-beta.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 (121) hide show
  1. package/LICENSE +85 -0
  2. package/README.md +26 -0
  3. package/esm/_dnt.shims.d.ts +2 -0
  4. package/esm/_dnt.shims.d.ts.map +1 -0
  5. package/esm/_dnt.shims.js +57 -0
  6. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts +700 -0
  7. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts.map +1 -0
  8. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.js +903 -0
  9. package/esm/deps/jsr.io/@std/text/1.0.18/_util.d.ts +3 -0
  10. package/esm/deps/jsr.io/@std/text/1.0.18/_util.d.ts.map +1 -0
  11. package/esm/deps/jsr.io/@std/text/1.0.18/_util.js +13 -0
  12. package/esm/deps/jsr.io/@std/text/1.0.18/closest_string.d.ts +42 -0
  13. package/esm/deps/jsr.io/@std/text/1.0.18/closest_string.d.ts.map +1 -0
  14. package/esm/deps/jsr.io/@std/text/1.0.18/closest_string.js +46 -0
  15. package/esm/deps/jsr.io/@std/text/1.0.18/compare_similarity.d.ts +47 -0
  16. package/esm/deps/jsr.io/@std/text/1.0.18/compare_similarity.d.ts.map +1 -0
  17. package/esm/deps/jsr.io/@std/text/1.0.18/compare_similarity.js +39 -0
  18. package/esm/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.d.ts +23 -0
  19. package/esm/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.d.ts.map +1 -0
  20. package/esm/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.js +127 -0
  21. package/esm/deps/jsr.io/@std/text/1.0.18/mod.d.ts +26 -0
  22. package/esm/deps/jsr.io/@std/text/1.0.18/mod.d.ts.map +1 -0
  23. package/esm/deps/jsr.io/@std/text/1.0.18/mod.js +27 -0
  24. package/esm/deps/jsr.io/@std/text/1.0.18/to_camel_case.d.ts +16 -0
  25. package/esm/deps/jsr.io/@std/text/1.0.18/to_camel_case.d.ts.map +1 -0
  26. package/esm/deps/jsr.io/@std/text/1.0.18/to_camel_case.js +22 -0
  27. package/esm/deps/jsr.io/@std/text/1.0.18/to_kebab_case.d.ts +16 -0
  28. package/esm/deps/jsr.io/@std/text/1.0.18/to_kebab_case.d.ts.map +1 -0
  29. package/esm/deps/jsr.io/@std/text/1.0.18/to_kebab_case.js +21 -0
  30. package/esm/deps/jsr.io/@std/text/1.0.18/to_pascal_case.d.ts +16 -0
  31. package/esm/deps/jsr.io/@std/text/1.0.18/to_pascal_case.d.ts.map +1 -0
  32. package/esm/deps/jsr.io/@std/text/1.0.18/to_pascal_case.js +21 -0
  33. package/esm/deps/jsr.io/@std/text/1.0.18/to_snake_case.d.ts +16 -0
  34. package/esm/deps/jsr.io/@std/text/1.0.18/to_snake_case.d.ts.map +1 -0
  35. package/esm/deps/jsr.io/@std/text/1.0.18/to_snake_case.js +21 -0
  36. package/esm/deps/jsr.io/@std/text/1.0.18/word_similarity_sort.d.ts +42 -0
  37. package/esm/deps/jsr.io/@std/text/1.0.18/word_similarity_sort.d.ts.map +1 -0
  38. package/esm/deps/jsr.io/@std/text/1.0.18/word_similarity_sort.js +43 -0
  39. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts +14 -0
  40. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts.map +1 -0
  41. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.js +65 -0
  42. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts +20 -0
  43. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts.map +1 -0
  44. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.js +43 -0
  45. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts +44 -0
  46. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts.map +1 -0
  47. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.js +47 -0
  48. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts +44 -0
  49. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts.map +1 -0
  50. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.js +51 -0
  51. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts +31 -0
  52. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts.map +1 -0
  53. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.js +37 -0
  54. package/esm/index.d.ts +21 -0
  55. package/esm/index.d.ts.map +1 -0
  56. package/esm/index.js +20 -0
  57. package/esm/lib/crud/aggregate.d.ts +31 -0
  58. package/esm/lib/crud/aggregate.d.ts.map +1 -0
  59. package/esm/lib/crud/aggregate.js +37 -0
  60. package/esm/lib/crud/bulkWrite.d.ts +25 -0
  61. package/esm/lib/crud/bulkWrite.d.ts.map +1 -0
  62. package/esm/lib/crud/bulkWrite.js +22 -0
  63. package/esm/lib/crud/countDocuments.d.ts +25 -0
  64. package/esm/lib/crud/countDocuments.d.ts.map +1 -0
  65. package/esm/lib/crud/countDocuments.js +22 -0
  66. package/esm/lib/crud/deleteMany.d.ts +25 -0
  67. package/esm/lib/crud/deleteMany.d.ts.map +1 -0
  68. package/esm/lib/crud/deleteMany.js +22 -0
  69. package/esm/lib/crud/deleteOne.d.ts +25 -0
  70. package/esm/lib/crud/deleteOne.d.ts.map +1 -0
  71. package/esm/lib/crud/deleteOne.js +22 -0
  72. package/esm/lib/crud/find.d.ts +39 -0
  73. package/esm/lib/crud/find.d.ts.map +1 -0
  74. package/esm/lib/crud/find.js +53 -0
  75. package/esm/lib/crud/findOne.d.ts +29 -0
  76. package/esm/lib/crud/findOne.d.ts.map +1 -0
  77. package/esm/lib/crud/findOne.js +38 -0
  78. package/esm/lib/crud/findOneAndDelete.d.ts +31 -0
  79. package/esm/lib/crud/findOneAndDelete.d.ts.map +1 -0
  80. package/esm/lib/crud/findOneAndDelete.js +44 -0
  81. package/esm/lib/crud/findOneAndReplace.d.ts +33 -0
  82. package/esm/lib/crud/findOneAndReplace.d.ts.map +1 -0
  83. package/esm/lib/crud/findOneAndReplace.js +45 -0
  84. package/esm/lib/crud/findOneAndUpdate.d.ts +33 -0
  85. package/esm/lib/crud/findOneAndUpdate.d.ts.map +1 -0
  86. package/esm/lib/crud/findOneAndUpdate.js +45 -0
  87. package/esm/lib/crud/insertMany.d.ts +25 -0
  88. package/esm/lib/crud/insertMany.d.ts.map +1 -0
  89. package/esm/lib/crud/insertMany.js +22 -0
  90. package/esm/lib/crud/insertOne.d.ts +25 -0
  91. package/esm/lib/crud/insertOne.d.ts.map +1 -0
  92. package/esm/lib/crud/insertOne.js +22 -0
  93. package/esm/lib/crud/replaceOne.d.ts +27 -0
  94. package/esm/lib/crud/replaceOne.d.ts.map +1 -0
  95. package/esm/lib/crud/replaceOne.js +23 -0
  96. package/esm/lib/crud/updateMany.d.ts +27 -0
  97. package/esm/lib/crud/updateMany.d.ts.map +1 -0
  98. package/esm/lib/crud/updateMany.js +23 -0
  99. package/esm/lib/crud/updateOne.d.ts +27 -0
  100. package/esm/lib/crud/updateOne.d.ts.map +1 -0
  101. package/esm/lib/crud/updateOne.js +23 -0
  102. package/esm/lib/deployCollection.d.ts +33 -0
  103. package/esm/lib/deployCollection.d.ts.map +1 -0
  104. package/esm/lib/deployCollection.js +125 -0
  105. package/esm/lib/handleMongoError.d.ts +10 -0
  106. package/esm/lib/handleMongoError.d.ts.map +1 -0
  107. package/esm/lib/handleMongoError.js +29 -0
  108. package/esm/lib/mongoConnectionManager.d.ts +107 -0
  109. package/esm/lib/mongoConnectionManager.d.ts.map +1 -0
  110. package/esm/lib/mongoConnectionManager.js +195 -0
  111. package/esm/lib/mongoJSON.d.ts +36 -0
  112. package/esm/lib/mongoJSON.d.ts.map +1 -0
  113. package/esm/lib/mongoJSON.js +96 -0
  114. package/esm/lib/repository.d.ts +145 -0
  115. package/esm/lib/repository.d.ts.map +1 -0
  116. package/esm/lib/repository.js +235 -0
  117. package/esm/lib/tracing.d.ts +17 -0
  118. package/esm/lib/tracing.d.ts.map +1 -0
  119. package/esm/lib/tracing.js +62 -0
  120. package/esm/package.json +3 -0
  121. package/package.json +35 -0
@@ -0,0 +1,235 @@
1
+ import { NotFoundException } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
2
+ import { toSnakeCase } from '../deps/jsr.io/@std/text/1.0.18/mod.js';
3
+ import { ObjectId } from 'mongodb';
4
+ import { bulkWrite } from './crud/bulkWrite.js';
5
+ import { countDocuments } from './crud/countDocuments.js';
6
+ import { deleteMany } from './crud/deleteMany.js';
7
+ import { deleteOne } from './crud/deleteOne.js';
8
+ import { find } from './crud/find.js';
9
+ import { findOne } from './crud/findOne.js';
10
+ import { insertMany } from './crud/insertMany.js';
11
+ import { insertOne } from './crud/insertOne.js';
12
+ import { replaceOne } from './crud/replaceOne.js';
13
+ /**
14
+ * Repository for interacting with a MongoDB Collection
15
+ * which provides a type-safe interface for MongoDB CRUD operations.
16
+ *
17
+ * Why do we not implement a generic repository interface?
18
+ *
19
+ * Implementing a generic repository interface is incredibly difficult in terms of
20
+ * mapping filter patterns to the Database without the user having to change a
21
+ * filter input when switching databases in the background.
22
+ *
23
+ * So we feel it is better to make the underlying data store obvious to the user
24
+ * and give the user a way to interact with the repository in a way that feels
25
+ * natural to the underlying data store.
26
+ *
27
+ * @param collection - MongoDB Collection instance
28
+ * @param entityType - Zod type for validating the data and ensure type safety
29
+ * @param entityName - Name for the entity, e.g. used in error messages like "Entity not found"
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { MongoDBRepository } from '@nimbus/mongodb';
34
+ * import { getEnv } from '@nimbus/utils';
35
+ * import { mongoClient } from './mongoDBClient.ts';
36
+ * import { User } from './user.type.ts';
37
+ * import { USER_COLLECTION } from './user.collection.ts';
38
+ *
39
+ * class UserRepository extends MongoDBRepository<User> {
40
+ * constructor() {
41
+ * const env = getEnv({ variables: ['MONGO_DB'] });
42
+ *
43
+ * super(
44
+ * mongoClient.db(env.MONGO_DB).collection(USER_COLLECTION.name),
45
+ * User,
46
+ * );
47
+ * }
48
+ *
49
+ * override _mapDocumentToEntity(doc: Document): User {
50
+ * return User.parse({
51
+ * _id: doc._id.toString(),
52
+ * email: doc.email,
53
+ * });
54
+ * }
55
+ *
56
+ * override _mapEntityToDocument(user: User): Document {
57
+ * return {
58
+ * _id: new ObjectId(user._id),
59
+ * email: user.email,
60
+ * };
61
+ * }
62
+ * }
63
+ *
64
+ * export const userRepository = new UserRepository();
65
+ * ```
66
+ */
67
+ export class MongoDBRepository {
68
+ _getCollection;
69
+ _entityType;
70
+ _entityName;
71
+ constructor(getCollection, entityType, entityName) {
72
+ this._getCollection = getCollection;
73
+ this._entityType = entityType;
74
+ this._entityName = entityName ?? 'Document';
75
+ }
76
+ /**
77
+ * Function to map the MongoDB Document to the Entity.
78
+ * ZodType.parse is used to ensure the data is valid and type-safe.
79
+ */
80
+ _mapDocumentToEntity(doc) {
81
+ return this._entityType.parse(doc);
82
+ }
83
+ /**
84
+ * Function to map the Entity to a MongoDB Document.
85
+ */
86
+ _mapEntityToDocument(item) {
87
+ return item;
88
+ }
89
+ /**
90
+ * Find one document based on a given filter.
91
+ */
92
+ async findOne({ filter, }) {
93
+ try {
94
+ const collection = await this._getCollection();
95
+ const res = await findOne({
96
+ collection,
97
+ filter,
98
+ mapDocument: this._mapDocumentToEntity,
99
+ outputType: this._entityType,
100
+ });
101
+ return res;
102
+ }
103
+ catch (error) {
104
+ if (error.name === 'NOT_FOUND_EXCEPTION') {
105
+ throw new NotFoundException(`${this._entityName} not found`, {
106
+ errorCode: `${toSnakeCase(this._entityName).toUpperCase()}_NOT_FOUND`,
107
+ reason: `Could not find ${this._entityName} matching the given filter`,
108
+ });
109
+ }
110
+ throw error;
111
+ }
112
+ }
113
+ /**
114
+ * Find multiple documents based on a given filter.
115
+ */
116
+ async find({ filter, limit, skip, sort, project, options, }) {
117
+ const collection = await this._getCollection();
118
+ return find({
119
+ collection,
120
+ filter,
121
+ limit,
122
+ skip,
123
+ sort,
124
+ project,
125
+ mapDocument: this._mapDocumentToEntity,
126
+ outputType: this._entityType,
127
+ options,
128
+ });
129
+ }
130
+ /**
131
+ * Count all documents matching a given filter.
132
+ */
133
+ async countDocuments({ filter, options, }) {
134
+ const collection = await this._getCollection();
135
+ return countDocuments({
136
+ collection,
137
+ filter,
138
+ options,
139
+ });
140
+ }
141
+ /**
142
+ * Insert a single new document.
143
+ */
144
+ async insertOne({ item, }) {
145
+ const collection = await this._getCollection();
146
+ await insertOne({
147
+ collection,
148
+ document: this._mapEntityToDocument(item),
149
+ });
150
+ return item;
151
+ }
152
+ /**
153
+ * Insert multiple new documents.
154
+ */
155
+ async insertMany({ items, options, }) {
156
+ const collection = await this._getCollection();
157
+ await insertMany({
158
+ collection,
159
+ documents: items.map(this._mapEntityToDocument),
160
+ options,
161
+ });
162
+ return items;
163
+ }
164
+ /**
165
+ * Replace a single document.
166
+ */
167
+ async replaceOne({ item, options, }) {
168
+ const collection = await this._getCollection();
169
+ const res = await replaceOne({
170
+ collection,
171
+ filter: { _id: new ObjectId(item._id) },
172
+ replacement: this._mapEntityToDocument(item),
173
+ options,
174
+ });
175
+ if (res.matchedCount === 0) {
176
+ throw new NotFoundException(`${this._entityName} not found`, {
177
+ errorCode: `${toSnakeCase(this._entityName).toUpperCase()}_NOT_FOUND`,
178
+ reason: `Could not find ${this._entityName} with the id: ${item._id}`,
179
+ });
180
+ }
181
+ return item;
182
+ }
183
+ /**
184
+ * Replace multiple documents.
185
+ */
186
+ async replaceMany({ items, options, }) {
187
+ if (items.length > 0) {
188
+ const collection = await this._getCollection();
189
+ const operations = items.map((item) => ({
190
+ replaceOne: {
191
+ filter: { _id: new ObjectId(item._id) },
192
+ replacement: this._mapEntityToDocument(item),
193
+ },
194
+ }));
195
+ await bulkWrite({
196
+ collection,
197
+ operations: operations,
198
+ options,
199
+ });
200
+ }
201
+ return items;
202
+ }
203
+ /**
204
+ * Delete a single document.
205
+ */
206
+ async deleteOne({ item, options, }) {
207
+ const collection = await this._getCollection();
208
+ const res = await deleteOne({
209
+ collection,
210
+ filter: { _id: new ObjectId(item._id) },
211
+ options,
212
+ });
213
+ if (res.deletedCount === 0) {
214
+ throw new NotFoundException(`${this._entityName} not found`, {
215
+ errorCode: `${toSnakeCase(this._entityName).toUpperCase()}_NOT_FOUND`,
216
+ reason: `Could not find ${this._entityName} with the id: ${item._id}`,
217
+ });
218
+ }
219
+ return item;
220
+ }
221
+ /**
222
+ * Delete multiple documents.
223
+ */
224
+ async deleteMany({ items, options, }) {
225
+ const collection = await this._getCollection();
226
+ await deleteMany({
227
+ collection,
228
+ filter: {
229
+ _id: { $in: items.map((item) => new ObjectId(item._id)) },
230
+ },
231
+ options,
232
+ });
233
+ return items;
234
+ }
235
+ }
@@ -0,0 +1,17 @@
1
+ import type { Collection, Document } from 'mongodb';
2
+ export declare const tracer: import("@opentelemetry/api").Tracer;
3
+ export declare const DB_SYSTEM = "mongodb";
4
+ /**
5
+ * Wraps an async function with OpenTelemetry tracing and metrics.
6
+ *
7
+ * Records:
8
+ * - `mongodb_operation_total` counter with operation, collection, and status labels
9
+ * - `mongodb_operation_duration_seconds` histogram with operation and collection labels
10
+ *
11
+ * @param operation - The MongoDB operation name (e.g., 'findOne', 'insertMany')
12
+ * @param collection - The MongoDB collection being operated on
13
+ * @param fn - The async function to execute within the span
14
+ * @returns The result of the async function
15
+ */
16
+ export declare const withSpan: <T>(operation: string, collection: Collection<Document>, fn: () => Promise<T>) => Promise<T>;
17
+ //# sourceMappingURL=tracing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/lib/tracing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEpD,eAAO,MAAM,MAAM,qCAA4B,CAAC;AAEhD,eAAO,MAAM,SAAS,YAAY,CAAC;AAgBnC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACtB,WAAW,MAAM,EACjB,YAAY,UAAU,CAAC,QAAQ,CAAC,EAChC,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,KACrB,OAAO,CAAC,CAAC,CAoDX,CAAC"}
@@ -0,0 +1,62 @@
1
+ import { metrics, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api';
2
+ export const tracer = trace.getTracer('nimbus');
3
+ export const DB_SYSTEM = 'mongodb';
4
+ const meter = metrics.getMeter('nimbus');
5
+ const operationCounter = meter.createCounter('mongodb_operation_total', {
6
+ description: 'Total number of MongoDB operations',
7
+ });
8
+ const operationDuration = meter.createHistogram('mongodb_operation_duration_seconds', {
9
+ description: 'Duration of MongoDB operations in seconds',
10
+ unit: 's',
11
+ });
12
+ /**
13
+ * Wraps an async function with OpenTelemetry tracing and metrics.
14
+ *
15
+ * Records:
16
+ * - `mongodb_operation_total` counter with operation, collection, and status labels
17
+ * - `mongodb_operation_duration_seconds` histogram with operation and collection labels
18
+ *
19
+ * @param operation - The MongoDB operation name (e.g., 'findOne', 'insertMany')
20
+ * @param collection - The MongoDB collection being operated on
21
+ * @param fn - The async function to execute within the span
22
+ * @returns The result of the async function
23
+ */
24
+ export const withSpan = (operation, collection, fn) => {
25
+ const startTime = performance.now();
26
+ const metricLabels = {
27
+ operation,
28
+ collection: collection.collectionName,
29
+ };
30
+ return tracer.startActiveSpan(`mongodb.${operation}`, {
31
+ kind: SpanKind.CLIENT,
32
+ attributes: {
33
+ 'db.system': DB_SYSTEM,
34
+ 'db.operation': operation,
35
+ 'db.mongodb.collection': collection.collectionName,
36
+ },
37
+ }, async (span) => {
38
+ try {
39
+ const result = await fn();
40
+ // Record success metrics
41
+ operationCounter.add(1, { ...metricLabels, status: 'success' });
42
+ operationDuration.record((performance.now() - startTime) / 1000, metricLabels);
43
+ return result;
44
+ }
45
+ catch (error) {
46
+ // Record error metrics
47
+ operationCounter.add(1, { ...metricLabels, status: 'error' });
48
+ operationDuration.record((performance.now() - startTime) / 1000, metricLabels);
49
+ span.setStatus({
50
+ code: SpanStatusCode.ERROR,
51
+ message: error instanceof Error
52
+ ? error.message
53
+ : 'Unknown error',
54
+ });
55
+ span.recordException(error instanceof Error ? error : new Error('Unknown error'));
56
+ throw error;
57
+ }
58
+ finally {
59
+ span.end();
60
+ }
61
+ });
62
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@nimbus-cqrs/mongodb",
3
+ "version": "2.0.0-beta.0",
4
+ "description": "MongoDB integration for the Nimbus CQRS framework.",
5
+ "author": "Daniel Gördes <d.goerdes@overlap.at> (https://overlap.at)",
6
+ "homepage": "https://nimbus.overlap.at",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/overlap-dev/Nimbus"
10
+ },
11
+ "license": "Apache-2.0",
12
+ "bugs": {
13
+ "url": "https://github.com/overlap-dev/Nimbus/issues"
14
+ },
15
+ "module": "./esm/index.js",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./esm/index.js"
19
+ }
20
+ },
21
+ "scripts": {},
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "dependencies": {
26
+ "@opentelemetry/api": "^1.9.1",
27
+ "mongodb": "^7.1.1",
28
+ "zod": "^4.3.6",
29
+ "@nimbus-cqrs/core": "^2.0.0-beta.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.0.0"
33
+ },
34
+ "_generatedBy": "dnt@dev"
35
+ }