@tumbaland/backend-core 1.21.0 → 1.22.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/dist/database/connection.d.ts +16 -12
- package/dist/database/connection.d.ts.map +1 -1
- package/dist/database/connection.js +32 -43
- package/dist/database/connection.js.map +1 -1
- package/package.json +1 -1
- package/src/database/connection.test.ts +35 -43
- package/src/database/connection.ts +31 -67
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Connects once and throws on failure. There is deliberately no retry loop here.
|
|
3
|
+
*
|
|
4
|
+
* MongoDB is Atlas (`mongodb+srv://`), not a sidecar container, so there is no
|
|
5
|
+
* boot race to ride out — and the driver already retries for us: `connect()`
|
|
6
|
+
* keeps doing server selection for `serverSelectionTimeoutMS` (30s by default)
|
|
7
|
+
* before it rejects, so a transient DNS or network blip is absorbed there.
|
|
8
|
+
* Anything that survives that is a real fault — bad credentials, an IP missing
|
|
9
|
+
* from the Atlas allowlist, a dead cluster — and no amount of retrying fixes it.
|
|
10
|
+
*
|
|
11
|
+
* Callers exit non-zero when this throws, which hands the problem to the
|
|
12
|
+
* container restart policy (`restart: unless-stopped`) that already supervises
|
|
13
|
+
* every service. One supervisor, and a service that can't reach its database
|
|
14
|
+
* shows up as a restarting container instead of a healthy one serving errors.
|
|
15
|
+
*/
|
|
16
|
+
export declare const connectDB: (serviceName?: string, uri?: string) => Promise<void>;
|
|
13
17
|
export declare const disconnectDB: (serviceName?: string) => Promise<void>;
|
|
14
18
|
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/database/connection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/database/connection.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GAAU,cAAc,MAAM,EAAE,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CAchF,CAAC;AAwCF,eAAO,MAAM,YAAY,GAAU,cAAc,MAAM,KAAG,OAAO,CAAC,IAAI,CASrE,CAAC"}
|
|
@@ -6,67 +6,56 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.disconnectDB = exports.connectDB = void 0;
|
|
7
7
|
const mongoose_1 = __importDefault(require("mongoose"));
|
|
8
8
|
const logger_1 = __importDefault(require("../logging/logger"));
|
|
9
|
-
const DEFAULT_INITIAL_DELAY_MS = 1000;
|
|
10
|
-
const DEFAULT_MAX_DELAY_MS = 30000;
|
|
11
9
|
let connected = false;
|
|
12
10
|
/**
|
|
13
|
-
* Set while a
|
|
14
|
-
* instead of
|
|
11
|
+
* Set while a connection attempt is in flight so concurrent callers await that
|
|
12
|
+
* attempt instead of opening a second one against the same mongoose singleton.
|
|
15
13
|
*/
|
|
16
14
|
let pending = null;
|
|
17
15
|
let listenersRegistered = false;
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Connects once and throws on failure. There is deliberately no retry loop here.
|
|
18
|
+
*
|
|
19
|
+
* MongoDB is Atlas (`mongodb+srv://`), not a sidecar container, so there is no
|
|
20
|
+
* boot race to ride out — and the driver already retries for us: `connect()`
|
|
21
|
+
* keeps doing server selection for `serverSelectionTimeoutMS` (30s by default)
|
|
22
|
+
* before it rejects, so a transient DNS or network blip is absorbed there.
|
|
23
|
+
* Anything that survives that is a real fault — bad credentials, an IP missing
|
|
24
|
+
* from the Atlas allowlist, a dead cluster — and no amount of retrying fixes it.
|
|
25
|
+
*
|
|
26
|
+
* Callers exit non-zero when this throws, which hands the problem to the
|
|
27
|
+
* container restart policy (`restart: unless-stopped`) that already supervises
|
|
28
|
+
* every service. One supervisor, and a service that can't reach its database
|
|
29
|
+
* shows up as a restarting container instead of a healthy one serving errors.
|
|
30
|
+
*/
|
|
31
|
+
const connectDB = async (serviceName, uri) => {
|
|
20
32
|
if (connected)
|
|
21
33
|
return;
|
|
22
34
|
if (pending)
|
|
23
35
|
return pending;
|
|
24
36
|
const mongoUri = uri || process.env.MONGODB_URI;
|
|
25
|
-
// A missing URI is a config error, not a transient one — retrying can never
|
|
26
|
-
// fix it, so fail immediately rather than looping forever on a typo.
|
|
27
37
|
if (!mongoUri) {
|
|
28
38
|
throw new Error('MONGODB_URI environment variable is not set');
|
|
29
39
|
}
|
|
30
|
-
pending =
|
|
40
|
+
pending = connectOnce(mongoUri, serviceName).finally(() => {
|
|
31
41
|
pending = null;
|
|
32
42
|
});
|
|
33
43
|
return pending;
|
|
34
44
|
};
|
|
35
45
|
exports.connectDB = connectDB;
|
|
36
|
-
async function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const message = error.message;
|
|
50
|
-
if (attempt >= maxAttempts) {
|
|
51
|
-
logger_1.default.error('Failed to connect to MongoDB, giving up', {
|
|
52
|
-
service: serviceName,
|
|
53
|
-
attempt,
|
|
54
|
-
error: message
|
|
55
|
-
});
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
// Exponential backoff, capped: a Mongo outage lasting hours shouldn't
|
|
59
|
-
// grow the gap past maxDelayMs and leave the service down long after
|
|
60
|
-
// the database comes back.
|
|
61
|
-
const delayMs = Math.min(initialDelayMs * 2 ** (attempt - 1), maxDelayMs);
|
|
62
|
-
logger_1.default.warn('Failed to connect to MongoDB, retrying', {
|
|
63
|
-
service: serviceName,
|
|
64
|
-
attempt,
|
|
65
|
-
delayMs,
|
|
66
|
-
error: message
|
|
67
|
-
});
|
|
68
|
-
await sleep(delayMs);
|
|
69
|
-
}
|
|
46
|
+
async function connectOnce(mongoUri, serviceName) {
|
|
47
|
+
try {
|
|
48
|
+
await mongoose_1.default.connect(mongoUri);
|
|
49
|
+
connected = true;
|
|
50
|
+
registerConnectionListeners(serviceName);
|
|
51
|
+
logger_1.default.info('MongoDB connected', { service: serviceName });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
logger_1.default.error('Failed to connect to MongoDB', {
|
|
55
|
+
service: serviceName,
|
|
56
|
+
error: error.message
|
|
57
|
+
});
|
|
58
|
+
throw error;
|
|
70
59
|
}
|
|
71
60
|
}
|
|
72
61
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/database/connection.ts"],"names":[],"mappings":";;;;;;AAAA,wDAAgC;AAChC,+DAAuC;
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/database/connection.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -23,10 +23,15 @@ import logger from '../logging/logger';
|
|
|
23
23
|
import { disconnectDB } from './connection';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* `connection.ts` keeps
|
|
27
|
-
* scenario needs its own fresh copy of the module
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* `connection.ts` keeps module-level `connected`/`pending` flags, so each
|
|
27
|
+
* connectDB scenario needs its own fresh copy of the module —
|
|
28
|
+
* isolateModulesAsync sandboxes that without leaking into other tests in this
|
|
29
|
+
* file the way a blanket jest.resetModules() would.
|
|
30
|
+
*
|
|
31
|
+
* Note that this does NOT re-create the mongoose/logger mocks: a jest.mock
|
|
32
|
+
* factory result is shared across isolated registries, so the objects returned
|
|
33
|
+
* here are the same instances every time. Resetting their implementations is
|
|
34
|
+
* beforeEach's job — see the comment there.
|
|
30
35
|
*/
|
|
31
36
|
async function freshConnectDB() {
|
|
32
37
|
let connectDBFn!: typeof import('./connection').connectDB;
|
|
@@ -49,6 +54,12 @@ describe('connectDB', () => {
|
|
|
49
54
|
process.env = { ...ORIGINAL_ENV };
|
|
50
55
|
delete process.env.MONGODB_URI;
|
|
51
56
|
for (const key of Object.keys(connectionHandlers)) delete connectionHandlers[key];
|
|
57
|
+
|
|
58
|
+
// `clearMocks: true` only clears recorded calls, not implementations, so a
|
|
59
|
+
// mockRejectedValue set by one test stays installed for every test after
|
|
60
|
+
// it. Reset connect explicitly — but not connection.on, whose factory
|
|
61
|
+
// implementation is what records into connectionHandlers.
|
|
62
|
+
(mongoose.connect as jest.Mock).mockReset();
|
|
52
63
|
});
|
|
53
64
|
|
|
54
65
|
afterAll(() => {
|
|
@@ -89,70 +100,51 @@ describe('connectDB', () => {
|
|
|
89
100
|
expect(mongoose.connect).toHaveBeenCalledTimes(1);
|
|
90
101
|
});
|
|
91
102
|
|
|
92
|
-
it('logs and rethrows when mongoose.connect fails
|
|
103
|
+
it('logs and rethrows when mongoose.connect fails', async () => {
|
|
93
104
|
process.env.MONGODB_URI = 'mongodb://env-uri';
|
|
94
105
|
const { connectDB, mongoose, logger } = await freshConnectDB();
|
|
95
106
|
(mongoose.connect as jest.Mock).mockRejectedValueOnce(new Error('ECONNREFUSED'));
|
|
96
107
|
|
|
97
|
-
await expect(connectDB('my-service'
|
|
98
|
-
expect(logger.error).toHaveBeenCalledWith('Failed to connect to MongoDB
|
|
108
|
+
await expect(connectDB('my-service')).rejects.toThrow('ECONNREFUSED');
|
|
109
|
+
expect(logger.error).toHaveBeenCalledWith('Failed to connect to MongoDB', {
|
|
99
110
|
service: 'my-service',
|
|
100
|
-
attempt: 1,
|
|
101
111
|
error: 'ECONNREFUSED'
|
|
102
112
|
});
|
|
103
113
|
});
|
|
104
114
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
await connectDB('my-service', undefined, { initialDelayMs: 0 });
|
|
113
|
-
|
|
114
|
-
expect(mongoose.connect).toHaveBeenCalledTimes(3);
|
|
115
|
-
expect(logger.warn).toHaveBeenCalledTimes(2);
|
|
116
|
-
expect(logger.info).toHaveBeenCalledWith('MongoDB connected', { service: 'my-service' });
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('gives up after maxAttempts', async () => {
|
|
115
|
+
/**
|
|
116
|
+
* The driver retries internally for serverSelectionTimeoutMS; anything that
|
|
117
|
+
* reaches us is a real fault, so one failure is one rejection. Callers exit
|
|
118
|
+
* non-zero and the container restart policy takes it from there.
|
|
119
|
+
*/
|
|
120
|
+
it('does not retry a failed connection', async () => {
|
|
120
121
|
process.env.MONGODB_URI = 'mongodb://env-uri';
|
|
121
122
|
const { connectDB, mongoose } = await freshConnectDB();
|
|
122
123
|
(mongoose.connect as jest.Mock).mockRejectedValue(new Error('auth failed'));
|
|
123
124
|
|
|
124
|
-
await expect(
|
|
125
|
-
connectDB('my-service', undefined, { maxAttempts: 3, initialDelayMs: 0 })
|
|
126
|
-
).rejects.toThrow('auth failed');
|
|
125
|
+
await expect(connectDB('my-service')).rejects.toThrow('auth failed');
|
|
127
126
|
|
|
128
|
-
expect(mongoose.connect).toHaveBeenCalledTimes(
|
|
127
|
+
expect(mongoose.connect).toHaveBeenCalledTimes(1);
|
|
129
128
|
});
|
|
130
129
|
|
|
131
|
-
it('
|
|
130
|
+
it('clears the in-flight promise so a later call can retry', async () => {
|
|
132
131
|
process.env.MONGODB_URI = 'mongodb://env-uri';
|
|
133
|
-
const { connectDB, mongoose
|
|
134
|
-
(mongoose.connect as jest.Mock)
|
|
135
|
-
.mockRejectedValueOnce(new Error('down'))
|
|
136
|
-
.mockRejectedValueOnce(new Error('down'))
|
|
137
|
-
.mockRejectedValueOnce(new Error('down'));
|
|
132
|
+
const { connectDB, mongoose } = await freshConnectDB();
|
|
133
|
+
(mongoose.connect as jest.Mock).mockRejectedValueOnce(new Error('not ready'));
|
|
138
134
|
|
|
139
|
-
await connectDB('my-service'
|
|
135
|
+
await expect(connectDB('my-service')).rejects.toThrow('not ready');
|
|
136
|
+
await connectDB('my-service');
|
|
140
137
|
|
|
141
|
-
|
|
142
|
-
expect(delays).toEqual([1, 2, 2]);
|
|
138
|
+
expect(mongoose.connect).toHaveBeenCalledTimes(2);
|
|
143
139
|
});
|
|
144
140
|
|
|
145
|
-
it('lets concurrent callers share one
|
|
141
|
+
it('lets concurrent callers share one connection attempt', async () => {
|
|
146
142
|
process.env.MONGODB_URI = 'mongodb://env-uri';
|
|
147
143
|
const { connectDB, mongoose } = await freshConnectDB();
|
|
148
|
-
(mongoose.connect as jest.Mock).mockRejectedValueOnce(new Error('not ready'));
|
|
149
144
|
|
|
150
|
-
await Promise.all([
|
|
151
|
-
connectDB('my-service', undefined, { initialDelayMs: 0 }),
|
|
152
|
-
connectDB('my-service', undefined, { initialDelayMs: 0 })
|
|
153
|
-
]);
|
|
145
|
+
await Promise.all([connectDB('my-service'), connectDB('my-service')]);
|
|
154
146
|
|
|
155
|
-
expect(mongoose.connect).toHaveBeenCalledTimes(
|
|
147
|
+
expect(mongoose.connect).toHaveBeenCalledTimes(1);
|
|
156
148
|
});
|
|
157
149
|
|
|
158
150
|
it('registers error/disconnected/reconnected handlers that log through the shared logger', async () => {
|
|
@@ -1,94 +1,58 @@
|
|
|
1
1
|
import mongoose from 'mongoose';
|
|
2
2
|
import logger from '../logging/logger';
|
|
3
3
|
|
|
4
|
-
export interface ConnectDBOptions {
|
|
5
|
-
/**
|
|
6
|
-
* How many times to try before giving up. Defaults to Infinity: a service that
|
|
7
|
-
* boots before the host's network is up must keep trying, not die once.
|
|
8
|
-
*/
|
|
9
|
-
maxAttempts?: number;
|
|
10
|
-
/** Backoff delay for the first retry. Doubles per attempt up to maxDelayMs. */
|
|
11
|
-
initialDelayMs?: number;
|
|
12
|
-
/** Ceiling for the backoff delay. */
|
|
13
|
-
maxDelayMs?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const DEFAULT_INITIAL_DELAY_MS = 1000;
|
|
17
|
-
const DEFAULT_MAX_DELAY_MS = 30000;
|
|
18
|
-
|
|
19
4
|
let connected = false;
|
|
20
5
|
/**
|
|
21
|
-
* Set while a
|
|
22
|
-
* instead of
|
|
6
|
+
* Set while a connection attempt is in flight so concurrent callers await that
|
|
7
|
+
* attempt instead of opening a second one against the same mongoose singleton.
|
|
23
8
|
*/
|
|
24
9
|
let pending: Promise<void> | null = null;
|
|
25
10
|
let listenersRegistered = false;
|
|
26
11
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Connects once and throws on failure. There is deliberately no retry loop here.
|
|
14
|
+
*
|
|
15
|
+
* MongoDB is Atlas (`mongodb+srv://`), not a sidecar container, so there is no
|
|
16
|
+
* boot race to ride out — and the driver already retries for us: `connect()`
|
|
17
|
+
* keeps doing server selection for `serverSelectionTimeoutMS` (30s by default)
|
|
18
|
+
* before it rejects, so a transient DNS or network blip is absorbed there.
|
|
19
|
+
* Anything that survives that is a real fault — bad credentials, an IP missing
|
|
20
|
+
* from the Atlas allowlist, a dead cluster — and no amount of retrying fixes it.
|
|
21
|
+
*
|
|
22
|
+
* Callers exit non-zero when this throws, which hands the problem to the
|
|
23
|
+
* container restart policy (`restart: unless-stopped`) that already supervises
|
|
24
|
+
* every service. One supervisor, and a service that can't reach its database
|
|
25
|
+
* shows up as a restarting container instead of a healthy one serving errors.
|
|
26
|
+
*/
|
|
27
|
+
export const connectDB = async (serviceName?: string, uri?: string): Promise<void> => {
|
|
34
28
|
if (connected) return;
|
|
35
29
|
if (pending) return pending;
|
|
36
30
|
|
|
37
31
|
const mongoUri = uri || process.env.MONGODB_URI;
|
|
38
|
-
// A missing URI is a config error, not a transient one — retrying can never
|
|
39
|
-
// fix it, so fail immediately rather than looping forever on a typo.
|
|
40
32
|
if (!mongoUri) {
|
|
41
33
|
throw new Error('MONGODB_URI environment variable is not set');
|
|
42
34
|
}
|
|
43
35
|
|
|
44
|
-
pending =
|
|
36
|
+
pending = connectOnce(mongoUri, serviceName).finally(() => {
|
|
45
37
|
pending = null;
|
|
46
38
|
});
|
|
47
39
|
|
|
48
40
|
return pending;
|
|
49
41
|
};
|
|
50
42
|
|
|
51
|
-
async function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
): Promise<void> {
|
|
56
|
-
const maxAttempts = options.maxAttempts ?? Infinity;
|
|
57
|
-
const initialDelayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
58
|
-
const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
59
|
-
|
|
60
|
-
for (let attempt = 1; ; attempt++) {
|
|
61
|
-
try {
|
|
62
|
-
await mongoose.connect(mongoUri);
|
|
63
|
-
connected = true;
|
|
64
|
-
|
|
65
|
-
registerConnectionListeners(serviceName);
|
|
66
|
-
logger.info('MongoDB connected', { service: serviceName });
|
|
67
|
-
return;
|
|
68
|
-
} catch (error) {
|
|
69
|
-
const message = (error as Error).message;
|
|
70
|
-
|
|
71
|
-
if (attempt >= maxAttempts) {
|
|
72
|
-
logger.error('Failed to connect to MongoDB, giving up', {
|
|
73
|
-
service: serviceName,
|
|
74
|
-
attempt,
|
|
75
|
-
error: message
|
|
76
|
-
});
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
43
|
+
async function connectOnce(mongoUri: string, serviceName: string | undefined): Promise<void> {
|
|
44
|
+
try {
|
|
45
|
+
await mongoose.connect(mongoUri);
|
|
46
|
+
connected = true;
|
|
79
47
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
error: message
|
|
89
|
-
});
|
|
90
|
-
await sleep(delayMs);
|
|
91
|
-
}
|
|
48
|
+
registerConnectionListeners(serviceName);
|
|
49
|
+
logger.info('MongoDB connected', { service: serviceName });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.error('Failed to connect to MongoDB', {
|
|
52
|
+
service: serviceName,
|
|
53
|
+
error: (error as Error).message
|
|
54
|
+
});
|
|
55
|
+
throw error;
|
|
92
56
|
}
|
|
93
57
|
}
|
|
94
58
|
|