@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.
- package/LICENSE +85 -0
- package/README.md +26 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts +700 -0
- package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.js +903 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/_util.d.ts +3 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/_util.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/_util.js +13 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/closest_string.d.ts +42 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/closest_string.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/closest_string.js +46 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/compare_similarity.d.ts +47 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/compare_similarity.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/compare_similarity.js +39 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.d.ts +23 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.js +127 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/mod.d.ts +26 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/mod.js +27 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_camel_case.d.ts +16 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_camel_case.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_camel_case.js +22 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_kebab_case.d.ts +16 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_kebab_case.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_kebab_case.js +21 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_pascal_case.d.ts +16 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_pascal_case.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_pascal_case.js +21 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_snake_case.d.ts +16 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_snake_case.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/to_snake_case.js +21 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/word_similarity_sort.d.ts +42 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/word_similarity_sort.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/word_similarity_sort.js +43 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts +14 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.js +65 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts +20 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.js +43 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts +44 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.js +47 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts +44 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.js +51 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts +31 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.js +37 -0
- package/esm/index.d.ts +21 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +20 -0
- package/esm/lib/crud/aggregate.d.ts +31 -0
- package/esm/lib/crud/aggregate.d.ts.map +1 -0
- package/esm/lib/crud/aggregate.js +37 -0
- package/esm/lib/crud/bulkWrite.d.ts +25 -0
- package/esm/lib/crud/bulkWrite.d.ts.map +1 -0
- package/esm/lib/crud/bulkWrite.js +22 -0
- package/esm/lib/crud/countDocuments.d.ts +25 -0
- package/esm/lib/crud/countDocuments.d.ts.map +1 -0
- package/esm/lib/crud/countDocuments.js +22 -0
- package/esm/lib/crud/deleteMany.d.ts +25 -0
- package/esm/lib/crud/deleteMany.d.ts.map +1 -0
- package/esm/lib/crud/deleteMany.js +22 -0
- package/esm/lib/crud/deleteOne.d.ts +25 -0
- package/esm/lib/crud/deleteOne.d.ts.map +1 -0
- package/esm/lib/crud/deleteOne.js +22 -0
- package/esm/lib/crud/find.d.ts +39 -0
- package/esm/lib/crud/find.d.ts.map +1 -0
- package/esm/lib/crud/find.js +53 -0
- package/esm/lib/crud/findOne.d.ts +29 -0
- package/esm/lib/crud/findOne.d.ts.map +1 -0
- package/esm/lib/crud/findOne.js +38 -0
- package/esm/lib/crud/findOneAndDelete.d.ts +31 -0
- package/esm/lib/crud/findOneAndDelete.d.ts.map +1 -0
- package/esm/lib/crud/findOneAndDelete.js +44 -0
- package/esm/lib/crud/findOneAndReplace.d.ts +33 -0
- package/esm/lib/crud/findOneAndReplace.d.ts.map +1 -0
- package/esm/lib/crud/findOneAndReplace.js +45 -0
- package/esm/lib/crud/findOneAndUpdate.d.ts +33 -0
- package/esm/lib/crud/findOneAndUpdate.d.ts.map +1 -0
- package/esm/lib/crud/findOneAndUpdate.js +45 -0
- package/esm/lib/crud/insertMany.d.ts +25 -0
- package/esm/lib/crud/insertMany.d.ts.map +1 -0
- package/esm/lib/crud/insertMany.js +22 -0
- package/esm/lib/crud/insertOne.d.ts +25 -0
- package/esm/lib/crud/insertOne.d.ts.map +1 -0
- package/esm/lib/crud/insertOne.js +22 -0
- package/esm/lib/crud/replaceOne.d.ts +27 -0
- package/esm/lib/crud/replaceOne.d.ts.map +1 -0
- package/esm/lib/crud/replaceOne.js +23 -0
- package/esm/lib/crud/updateMany.d.ts +27 -0
- package/esm/lib/crud/updateMany.d.ts.map +1 -0
- package/esm/lib/crud/updateMany.js +23 -0
- package/esm/lib/crud/updateOne.d.ts +27 -0
- package/esm/lib/crud/updateOne.d.ts.map +1 -0
- package/esm/lib/crud/updateOne.js +23 -0
- package/esm/lib/deployCollection.d.ts +33 -0
- package/esm/lib/deployCollection.d.ts.map +1 -0
- package/esm/lib/deployCollection.js +125 -0
- package/esm/lib/handleMongoError.d.ts +10 -0
- package/esm/lib/handleMongoError.d.ts.map +1 -0
- package/esm/lib/handleMongoError.js +29 -0
- package/esm/lib/mongoConnectionManager.d.ts +107 -0
- package/esm/lib/mongoConnectionManager.d.ts.map +1 -0
- package/esm/lib/mongoConnectionManager.js +195 -0
- package/esm/lib/mongoJSON.d.ts +36 -0
- package/esm/lib/mongoJSON.d.ts.map +1 -0
- package/esm/lib/mongoJSON.js +96 -0
- package/esm/lib/repository.d.ts +145 -0
- package/esm/lib/repository.d.ts.map +1 -0
- package/esm/lib/repository.js +235 -0
- package/esm/lib/tracing.d.ts +17 -0
- package/esm/lib/tracing.d.ts.map +1 -0
- package/esm/lib/tracing.js +62 -0
- package/esm/package.json +3 -0
- package/package.json +35 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { getLogger } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
|
|
2
|
+
import { MongoClient, } from 'mongodb';
|
|
3
|
+
/**
|
|
4
|
+
* Singleton wrapper around a single long-lived `MongoClient`.
|
|
5
|
+
*
|
|
6
|
+
* The MongoDB Node driver already handles connection pooling, server
|
|
7
|
+
* monitoring (SDAM), automatic reconnection, retryable reads/writes and
|
|
8
|
+
* per-socket idle eviction via `maxIdleTimeMS`. This class therefore does
|
|
9
|
+
* the minimum on top of the driver: hold one `MongoClient` for the lifetime
|
|
10
|
+
* of the application and provide typed `getDatabase` / `getCollection`
|
|
11
|
+
* convenience methods.
|
|
12
|
+
*
|
|
13
|
+
* Call {@link MongoConnectionManager#close} on graceful shutdown
|
|
14
|
+
* (e.g. on `SIGTERM` / `SIGINT`) to drain the pool.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { MongoConnectionManager } from '@nimbus/mongodb';
|
|
19
|
+
* import { ServerApiVersion } from 'mongodb';
|
|
20
|
+
*
|
|
21
|
+
* export const mongoManager = MongoConnectionManager.getInstance(
|
|
22
|
+
* process.env['MONGO_URL'] ?? '',
|
|
23
|
+
* {
|
|
24
|
+
* appName: 'my-app',
|
|
25
|
+
* serverApi: {
|
|
26
|
+
* version: ServerApiVersion.v1,
|
|
27
|
+
* strict: false,
|
|
28
|
+
* deprecationErrors: true,
|
|
29
|
+
* },
|
|
30
|
+
* },
|
|
31
|
+
* );
|
|
32
|
+
*
|
|
33
|
+
* process.on('SIGTERM', () => {
|
|
34
|
+
* mongoManager.close().catch(console.error);
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class MongoConnectionManager {
|
|
39
|
+
static _instance = null;
|
|
40
|
+
_client = null;
|
|
41
|
+
_connecting = null;
|
|
42
|
+
_uri;
|
|
43
|
+
_options;
|
|
44
|
+
/**
|
|
45
|
+
* Initialize the MongoConnectionManager.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} uri - The MongoDB connection URI
|
|
48
|
+
* @param {MongoClientOptions} [options] - Options forwarded to the underlying `MongoClient`
|
|
49
|
+
*/
|
|
50
|
+
constructor(uri, options) {
|
|
51
|
+
this._uri = uri;
|
|
52
|
+
this._options = options;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the singleton instance of the MongoConnectionManager.
|
|
56
|
+
*
|
|
57
|
+
* Subsequent calls return the existing instance and ignore the arguments.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} uri - The MongoDB connection URI
|
|
60
|
+
* @param {MongoClientOptions} [options] - Options forwarded to the underlying `MongoClient`
|
|
61
|
+
*
|
|
62
|
+
* @returns {MongoConnectionManager} The singleton instance of the MongoConnectionManager
|
|
63
|
+
*/
|
|
64
|
+
static getInstance(uri, options) {
|
|
65
|
+
if (!MongoConnectionManager._instance) {
|
|
66
|
+
MongoConnectionManager._instance = new MongoConnectionManager(uri, options);
|
|
67
|
+
}
|
|
68
|
+
return MongoConnectionManager._instance;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the connected MongoDB client.
|
|
72
|
+
*
|
|
73
|
+
* Lazily creates and connects a single `MongoClient` on the first call.
|
|
74
|
+
* Concurrent first-callers share the same in-flight `connect()` promise
|
|
75
|
+
* so only one connection is established.
|
|
76
|
+
*
|
|
77
|
+
* @returns {Promise<MongoClient>} The MongoDB client instance
|
|
78
|
+
*/
|
|
79
|
+
getClient() {
|
|
80
|
+
if (this._client !== null) {
|
|
81
|
+
return Promise.resolve(this._client);
|
|
82
|
+
}
|
|
83
|
+
if (this._connecting !== null) {
|
|
84
|
+
return this._connecting;
|
|
85
|
+
}
|
|
86
|
+
this._connecting = (async () => {
|
|
87
|
+
try {
|
|
88
|
+
const client = new MongoClient(this._uri, this._options);
|
|
89
|
+
await client.connect();
|
|
90
|
+
this._client = client;
|
|
91
|
+
getLogger().info({
|
|
92
|
+
category: 'Nimbus',
|
|
93
|
+
message: 'MongoConnectionManager :: Successfully connected',
|
|
94
|
+
});
|
|
95
|
+
return client;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
getLogger().critical({
|
|
99
|
+
category: 'Nimbus',
|
|
100
|
+
message: 'MongoConnectionManager :: Connection failed',
|
|
101
|
+
error: error,
|
|
102
|
+
});
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
})().finally(() => {
|
|
106
|
+
this._connecting = null;
|
|
107
|
+
});
|
|
108
|
+
return this._connecting;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get a database instance from the connected MongoDB client.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} dbName - The name of the database
|
|
114
|
+
* @returns {Promise<Db>} The database instance
|
|
115
|
+
*/
|
|
116
|
+
async getDatabase(dbName) {
|
|
117
|
+
const client = await this.getClient();
|
|
118
|
+
return client.db(dbName);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get a collection instance from the connected MongoDB client.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} dbName - The name of the database
|
|
124
|
+
* @param {string} collectionName - The name of the collection
|
|
125
|
+
* @returns {Promise<Collection>} The collection instance
|
|
126
|
+
*/
|
|
127
|
+
async getCollection(dbName, collectionName) {
|
|
128
|
+
const db = await this.getDatabase(dbName);
|
|
129
|
+
return db.collection(collectionName);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check the health of the MongoDB connection by issuing a `ping` against
|
|
133
|
+
* the `admin` database.
|
|
134
|
+
*
|
|
135
|
+
* @returns {Promise<{ status: 'healthy' | 'error'; details?: string }>} The health status and optional error details
|
|
136
|
+
*/
|
|
137
|
+
async healthCheck() {
|
|
138
|
+
try {
|
|
139
|
+
const client = await this.getClient();
|
|
140
|
+
await client.db('admin').command({ ping: 1 });
|
|
141
|
+
return { status: 'healthy' };
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return {
|
|
145
|
+
status: 'error',
|
|
146
|
+
details: error instanceof Error
|
|
147
|
+
? error.message
|
|
148
|
+
: 'Unknown error occurred',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Close the underlying MongoDB client and drain its connection pool.
|
|
154
|
+
*
|
|
155
|
+
* Intended for graceful shutdown handlers. After `close()` resolves, the
|
|
156
|
+
* next call to {@link MongoConnectionManager#getClient} establishes a
|
|
157
|
+
* fresh connection.
|
|
158
|
+
*
|
|
159
|
+
* @returns {Promise<void>}
|
|
160
|
+
*/
|
|
161
|
+
async close() {
|
|
162
|
+
// If a connection is currently being established, wait for it to
|
|
163
|
+
// settle so we don't leave an orphaned client behind after shutdown.
|
|
164
|
+
if (this._connecting !== null) {
|
|
165
|
+
try {
|
|
166
|
+
await this._connecting;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// The in-flight connect failed - there is no client to close.
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const client = this._client;
|
|
173
|
+
if (client === null) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await client.close();
|
|
178
|
+
this._client = null;
|
|
179
|
+
getLogger().info({
|
|
180
|
+
category: 'Nimbus',
|
|
181
|
+
message: 'MongoConnectionManager :: Connection closed',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
// Keep `_client` so the caller can retry `close()` and we don't
|
|
186
|
+
// leak the still-open client by losing its only reference.
|
|
187
|
+
getLogger().error({
|
|
188
|
+
category: 'Nimbus',
|
|
189
|
+
message: 'MongoConnectionManager :: Error closing connection',
|
|
190
|
+
error: error,
|
|
191
|
+
});
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse and stringify JSON with support for MongoDB types.
|
|
3
|
+
*
|
|
4
|
+
* The parse function takes care to convert certain datatype
|
|
5
|
+
* to the correct JS representation by looking for specific
|
|
6
|
+
* prefixes in the JSON string.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { MongoJSON } from '@nimbus/mongodb';
|
|
11
|
+
*
|
|
12
|
+
* const filterString = MongoJSON.stringify({
|
|
13
|
+
* _id: 'objectId::507f1f77bcf86cd799439011',
|
|
14
|
+
* name: 'John Doe',
|
|
15
|
+
* age: 'int::30',
|
|
16
|
+
* price: 'double::19.99',
|
|
17
|
+
* createdAt: 'date::2023-01-01T00:00:00Z',
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const result = MongoJSON.parse(filterString);
|
|
21
|
+
*
|
|
22
|
+
* // Result will be:
|
|
23
|
+
* // {
|
|
24
|
+
* // _id: new ObjectId('507f1f77bcf86cd799439011'),
|
|
25
|
+
* // name: 'John Doe',
|
|
26
|
+
* // age: 30,
|
|
27
|
+
* // price: 19.99,
|
|
28
|
+
* // createdAt: new Date('2023-01-01T00:00:00Z),
|
|
29
|
+
* // }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare const MongoJSON: {
|
|
33
|
+
parse: (text: string, operatorBlackList?: string[]) => any;
|
|
34
|
+
stringify: (object: Record<string, unknown>) => string;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=mongoJSON.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongoJSON.d.ts","sourceRoot":"","sources":["../../src/lib/mongoJSON.ts"],"names":[],"mappings":"AAgEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,SAAS;kBArEZ,MAAM,sBACO,MAAM,EAAE,KAC5B,GAAG;wBAnB8B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM;CAyFnE,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { InvalidInputException } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
|
|
2
|
+
import { ObjectId } from 'mongodb';
|
|
3
|
+
/**
|
|
4
|
+
* Converts a JavaScript object to a JSON string.
|
|
5
|
+
*
|
|
6
|
+
* @param {Record<string, unknown>} object - The object to stringify.
|
|
7
|
+
* @returns {string} The JSON string representation of the object.
|
|
8
|
+
*/
|
|
9
|
+
const mongoJSONStringify = (object) => {
|
|
10
|
+
return JSON.stringify(object);
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Parses a JSON string of MongoDB filter object.
|
|
14
|
+
* Converts certain datatypes to the correct JS representation
|
|
15
|
+
* by looking for specific prefixes in the JSON string.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} text - JSON string to parse
|
|
18
|
+
* @param {string[]} operatorBlackList - List of operators that are not allowed. Defaults to ['$where']
|
|
19
|
+
*
|
|
20
|
+
* @returns {Object} The parsed object
|
|
21
|
+
*
|
|
22
|
+
* @throws {Error} - If the JSON string is invalid or contains a blacklisted operator
|
|
23
|
+
*/
|
|
24
|
+
const mongoJSONParse = (text, operatorBlackList = ['$where']) => {
|
|
25
|
+
for (const operator of operatorBlackList) {
|
|
26
|
+
if (text.includes(operator)) {
|
|
27
|
+
throw new Error(`Operator '${operator}' is not allowed`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const reviver = (_key, value) => {
|
|
31
|
+
if (typeof value !== 'string') {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
if (value.startsWith('objectId::')) {
|
|
35
|
+
return new ObjectId(value.replace('objectId::', ''));
|
|
36
|
+
}
|
|
37
|
+
else if (value.startsWith('date::')) {
|
|
38
|
+
return new Date(value.replace('date::', ''));
|
|
39
|
+
}
|
|
40
|
+
else if (value.startsWith('int::')) {
|
|
41
|
+
return Number.parseInt(value.replace('int::', ''));
|
|
42
|
+
}
|
|
43
|
+
else if (value.startsWith('double::')) {
|
|
44
|
+
return Number.parseFloat(value.replace('double::', ''));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(text, reviver);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
throw new InvalidInputException().fromError(error);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
throw new InvalidInputException('MongoJSON parse error');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Parse and stringify JSON with support for MongoDB types.
|
|
64
|
+
*
|
|
65
|
+
* The parse function takes care to convert certain datatype
|
|
66
|
+
* to the correct JS representation by looking for specific
|
|
67
|
+
* prefixes in the JSON string.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { MongoJSON } from '@nimbus/mongodb';
|
|
72
|
+
*
|
|
73
|
+
* const filterString = MongoJSON.stringify({
|
|
74
|
+
* _id: 'objectId::507f1f77bcf86cd799439011',
|
|
75
|
+
* name: 'John Doe',
|
|
76
|
+
* age: 'int::30',
|
|
77
|
+
* price: 'double::19.99',
|
|
78
|
+
* createdAt: 'date::2023-01-01T00:00:00Z',
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* const result = MongoJSON.parse(filterString);
|
|
82
|
+
*
|
|
83
|
+
* // Result will be:
|
|
84
|
+
* // {
|
|
85
|
+
* // _id: new ObjectId('507f1f77bcf86cd799439011'),
|
|
86
|
+
* // name: 'John Doe',
|
|
87
|
+
* // age: 30,
|
|
88
|
+
* // price: 19.99,
|
|
89
|
+
* // createdAt: new Date('2023-01-01T00:00:00Z),
|
|
90
|
+
* // }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export const MongoJSON = {
|
|
94
|
+
parse: mongoJSONParse,
|
|
95
|
+
stringify: mongoJSONStringify,
|
|
96
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { BulkWriteOptions, Collection, CountDocumentsOptions, DeleteOptions, Document, Filter, FindOptions, InsertOneOptions, ReplaceOptions, Sort } from 'mongodb';
|
|
2
|
+
import type { ZodType } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Type for entities that have a string id.
|
|
5
|
+
* Equivalent to the MongoDB WithId type but for the repository entity.
|
|
6
|
+
*/
|
|
7
|
+
export type WithStringId<TSchema> = Omit<TSchema, '_id'> & {
|
|
8
|
+
_id: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Repository for interacting with a MongoDB Collection
|
|
12
|
+
* which provides a type-safe interface for MongoDB CRUD operations.
|
|
13
|
+
*
|
|
14
|
+
* Why do we not implement a generic repository interface?
|
|
15
|
+
*
|
|
16
|
+
* Implementing a generic repository interface is incredibly difficult in terms of
|
|
17
|
+
* mapping filter patterns to the Database without the user having to change a
|
|
18
|
+
* filter input when switching databases in the background.
|
|
19
|
+
*
|
|
20
|
+
* So we feel it is better to make the underlying data store obvious to the user
|
|
21
|
+
* and give the user a way to interact with the repository in a way that feels
|
|
22
|
+
* natural to the underlying data store.
|
|
23
|
+
*
|
|
24
|
+
* @param collection - MongoDB Collection instance
|
|
25
|
+
* @param entityType - Zod type for validating the data and ensure type safety
|
|
26
|
+
* @param entityName - Name for the entity, e.g. used in error messages like "Entity not found"
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { MongoDBRepository } from '@nimbus/mongodb';
|
|
31
|
+
* import { getEnv } from '@nimbus/utils';
|
|
32
|
+
* import { mongoClient } from './mongoDBClient.ts';
|
|
33
|
+
* import { User } from './user.type.ts';
|
|
34
|
+
* import { USER_COLLECTION } from './user.collection.ts';
|
|
35
|
+
*
|
|
36
|
+
* class UserRepository extends MongoDBRepository<User> {
|
|
37
|
+
* constructor() {
|
|
38
|
+
* const env = getEnv({ variables: ['MONGO_DB'] });
|
|
39
|
+
*
|
|
40
|
+
* super(
|
|
41
|
+
* mongoClient.db(env.MONGO_DB).collection(USER_COLLECTION.name),
|
|
42
|
+
* User,
|
|
43
|
+
* );
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* override _mapDocumentToEntity(doc: Document): User {
|
|
47
|
+
* return User.parse({
|
|
48
|
+
* _id: doc._id.toString(),
|
|
49
|
+
* email: doc.email,
|
|
50
|
+
* });
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* override _mapEntityToDocument(user: User): Document {
|
|
54
|
+
* return {
|
|
55
|
+
* _id: new ObjectId(user._id),
|
|
56
|
+
* email: user.email,
|
|
57
|
+
* };
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* export const userRepository = new UserRepository();
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare class MongoDBRepository<TEntity extends WithStringId<Record<string, any>>> {
|
|
65
|
+
protected _getCollection: () => Promise<Collection<Document>>;
|
|
66
|
+
protected _entityType: ZodType;
|
|
67
|
+
protected _entityName: string;
|
|
68
|
+
constructor(getCollection: () => Promise<Collection<Document>>, entityType: ZodType, entityName?: string);
|
|
69
|
+
/**
|
|
70
|
+
* Function to map the MongoDB Document to the Entity.
|
|
71
|
+
* ZodType.parse is used to ensure the data is valid and type-safe.
|
|
72
|
+
*/
|
|
73
|
+
protected _mapDocumentToEntity(doc: Document): TEntity;
|
|
74
|
+
/**
|
|
75
|
+
* Function to map the Entity to a MongoDB Document.
|
|
76
|
+
*/
|
|
77
|
+
protected _mapEntityToDocument(item: TEntity): Document;
|
|
78
|
+
/**
|
|
79
|
+
* Find one document based on a given filter.
|
|
80
|
+
*/
|
|
81
|
+
findOne({ filter, }: {
|
|
82
|
+
filter: Filter<Document>;
|
|
83
|
+
}): Promise<TEntity>;
|
|
84
|
+
/**
|
|
85
|
+
* Find multiple documents based on a given filter.
|
|
86
|
+
*/
|
|
87
|
+
find({ filter, limit, skip, sort, project, options, }: {
|
|
88
|
+
filter: Filter<Document>;
|
|
89
|
+
limit?: number;
|
|
90
|
+
skip?: number;
|
|
91
|
+
sort?: Sort;
|
|
92
|
+
project?: Document;
|
|
93
|
+
options?: FindOptions;
|
|
94
|
+
}): Promise<TEntity[]>;
|
|
95
|
+
/**
|
|
96
|
+
* Count all documents matching a given filter.
|
|
97
|
+
*/
|
|
98
|
+
countDocuments({ filter, options, }: {
|
|
99
|
+
filter: Filter<Document>;
|
|
100
|
+
options?: CountDocumentsOptions;
|
|
101
|
+
}): Promise<number>;
|
|
102
|
+
/**
|
|
103
|
+
* Insert a single new document.
|
|
104
|
+
*/
|
|
105
|
+
insertOne({ item, }: {
|
|
106
|
+
item: TEntity;
|
|
107
|
+
options?: InsertOneOptions;
|
|
108
|
+
}): Promise<TEntity>;
|
|
109
|
+
/**
|
|
110
|
+
* Insert multiple new documents.
|
|
111
|
+
*/
|
|
112
|
+
insertMany({ items, options, }: {
|
|
113
|
+
items: TEntity[];
|
|
114
|
+
options?: BulkWriteOptions;
|
|
115
|
+
}): Promise<TEntity[]>;
|
|
116
|
+
/**
|
|
117
|
+
* Replace a single document.
|
|
118
|
+
*/
|
|
119
|
+
replaceOne({ item, options, }: {
|
|
120
|
+
item: TEntity;
|
|
121
|
+
options?: ReplaceOptions;
|
|
122
|
+
}): Promise<TEntity>;
|
|
123
|
+
/**
|
|
124
|
+
* Replace multiple documents.
|
|
125
|
+
*/
|
|
126
|
+
replaceMany({ items, options, }: {
|
|
127
|
+
items: TEntity[];
|
|
128
|
+
options?: BulkWriteOptions;
|
|
129
|
+
}): Promise<TEntity[]>;
|
|
130
|
+
/**
|
|
131
|
+
* Delete a single document.
|
|
132
|
+
*/
|
|
133
|
+
deleteOne({ item, options, }: {
|
|
134
|
+
item: TEntity;
|
|
135
|
+
options?: DeleteOptions;
|
|
136
|
+
}): Promise<TEntity>;
|
|
137
|
+
/**
|
|
138
|
+
* Delete multiple documents.
|
|
139
|
+
*/
|
|
140
|
+
deleteMany({ items, options, }: {
|
|
141
|
+
items: TEntity[];
|
|
142
|
+
options?: DeleteOptions;
|
|
143
|
+
}): Promise<TEntity[]>;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/lib/repository.ts"],"names":[],"mappings":"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"}
|