@lazyapps/readmodelstorage-mongodb 0.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 (4) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +13 -0
  3. package/index.js +126 -0
  4. package/package.json +46 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026, Oliver Sturm
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @lazyapps/readmodelstorage-mongodb
2
+
3
+ MongoDB-backed read model storage. Provides persistence for projected read model data with standard CRUD operations.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @lazyapps/readmodelstorage-mongodb
9
+ ```
10
+
11
+ ## Part of LazyApps
12
+
13
+ This package is part of the [LazyApps](https://github.com/oliversturm/lazyapps-libs) event-sourcing and CQRS framework.
package/index.js ADDED
@@ -0,0 +1,126 @@
1
+ import { MongoClient } from 'mongodb';
2
+ import pRetry from 'p-retry';
3
+
4
+ import { getLogger } from '@lazyapps/logger';
5
+
6
+ const wrapCalls = (correlationId, dbContext, names) => {
7
+ const log = getLogger('RM/MongoAPI', correlationId);
8
+ return names.reduce(
9
+ (r, v) => ({
10
+ ...r,
11
+ [v]: (collection, ...args) => {
12
+ log.debug(`Calling ${v}(${JSON.stringify(args)}) on ${collection}`);
13
+ return dbContext.db.collection(collection)[v](...args);
14
+ },
15
+ }),
16
+ {},
17
+ );
18
+ };
19
+
20
+ export const mongodb =
21
+ ({ url, user, pwd, scheme, host, urlPath, database = 'readmodel' } = {}) =>
22
+ () => {
23
+ const connectUrl = url
24
+ ? url
25
+ : user && pwd && scheme && host
26
+ ? (url = `${scheme}://${user}:${pwd}@${host}/${urlPath}`)
27
+ : 'mongodb://127.0.0.1:27017';
28
+
29
+ // This is meant to be a logging-safe URL, but obviously we don't do
30
+ // anything about a URL that may already include sensitive details.
31
+ // Not perfect.
32
+ const logLocation = user ? host : connectUrl;
33
+
34
+ const initLog = getLogger('RM/Mongo', 'INIT');
35
+
36
+ return pRetry(
37
+ () =>
38
+ MongoClient.connect(connectUrl, {
39
+ useNewUrlParser: true,
40
+ useUnifiedTopology: true,
41
+ }),
42
+ {
43
+ onFailedAttempt: (error) => {
44
+ initLog.error(
45
+ `Attempt ${error.attemptNumber} failed connecting to MongoDB at ${logLocation}: '${error}'. Will retry another ${error.retriesLeft} times.`,
46
+ );
47
+ },
48
+ retries: 10,
49
+ },
50
+ )
51
+ .catch((err) => {
52
+ initLog.error(`Can't connect to MongoDB at ${logLocation}: ${err}`);
53
+ })
54
+ .then((client) => ({ client, db: client.db(database) }))
55
+ .then((dbcontext) => {
56
+ initLog.info(
57
+ `MongoDB connected to url=${logLocation} and database=${database}`,
58
+ );
59
+ return dbcontext;
60
+ })
61
+ .then((dbContext) => ({
62
+ perRequest: (correlationId) => ({
63
+ ...wrapCalls(correlationId, dbContext, [
64
+ 'insertOne',
65
+ 'insertMany',
66
+ 'updateOne',
67
+ 'updateMany',
68
+ 'deleteOne',
69
+ 'deleteMany',
70
+ 'findOneAndUpdate',
71
+ 'findOneAndDelete',
72
+ 'findOneAndReplace',
73
+ 'bulkWrite',
74
+ 'find',
75
+ 'countDocuments',
76
+ ]),
77
+ }),
78
+ close: () => dbContext.client.close(),
79
+ updateLastProjectedEventTimestamps: (
80
+ correlationId,
81
+ rmNames,
82
+ timestamp,
83
+ ) =>
84
+ rmNames.length
85
+ ? dbContext.db
86
+ .collection('readmodel.state')
87
+ // Can't use updateMany here, because it only ever upserts
88
+ // one document even if multiple are matched using e.g. $in
89
+ .bulkWrite(
90
+ rmNames.map((rmName) => ({
91
+ updateOne: {
92
+ filter: { name: rmName },
93
+ update: {
94
+ $set: { lastProjectedEventTimestamp: timestamp },
95
+ },
96
+ upsert: true,
97
+ },
98
+ })),
99
+ )
100
+ .then((r) => {
101
+ const log = getLogger('RM/Mongo', correlationId);
102
+ log.debug(
103
+ `Updated last projected event timestamps for ${JSON.stringify(
104
+ rmNames,
105
+ )}. Matched ${r.matchedCount}, upserted ${
106
+ r.upsertedCount
107
+ }.`,
108
+ );
109
+ })
110
+ : Promise.resolve(),
111
+ readLastProjectedEventTimestamps: (readModels) =>
112
+ Promise.all(
113
+ Object.keys(readModels).map((rmName) =>
114
+ dbContext.db
115
+ .collection('readmodel.state')
116
+ .find({ name: rmName }, { lastProjectedEventTimestamp: 1 })
117
+ .toArray()
118
+ .then((result) => {
119
+ if (result && result.length === 1)
120
+ readModels[rmName].lastProjectedEventTimestamp =
121
+ result[0].lastProjectedEventTimestamp;
122
+ }),
123
+ ),
124
+ ),
125
+ }));
126
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@lazyapps/readmodelstorage-mongodb",
3
+ "version": "0.1.0",
4
+ "description": "MongoDB-backed read model storage for LazyApps",
5
+ "main": "index.js",
6
+ "exports": {
7
+ ".": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js"
11
+ ],
12
+ "keywords": [
13
+ "event-sourcing",
14
+ "cqrs",
15
+ "lazyapps",
16
+ "read-models",
17
+ "mongodb"
18
+ ],
19
+ "author": "Oliver Sturm",
20
+ "license": "ISC",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/oliversturm/lazyapps-libs.git",
24
+ "directory": "packages/readmodelstorage-mongodb"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/oliversturm/lazyapps-libs/issues"
28
+ },
29
+ "homepage": "https://github.com/oliversturm/lazyapps-libs/tree/main/packages/readmodelstorage-mongodb#readme",
30
+ "engines": {
31
+ "node": ">=18.20.3 || >=20.18.0"
32
+ },
33
+ "devDependencies": {
34
+ "eslint": "^8.46.0",
35
+ "vitest": "^4.0.18"
36
+ },
37
+ "dependencies": {
38
+ "mongodb": "^5.7.0",
39
+ "p-retry": "^6.1.0",
40
+ "@lazyapps/logger": "^0.1.0"
41
+ },
42
+ "type": "module",
43
+ "scripts": {
44
+ "test": "vitest"
45
+ }
46
+ }