@samanhappy/mcphub 1.0.39 → 1.0.41
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/betterAuth.js +23 -0
- package/dist/betterAuth.js.map +1 -1
- package/dist/controllers/mcpbController.js +8 -0
- package/dist/controllers/mcpbController.js.map +1 -1
- package/dist/controllers/oauthCallbackController.js +85 -43
- package/dist/controllers/oauthCallbackController.js.map +1 -1
- package/dist/controllers/serverController.js +52 -7
- package/dist/controllers/serverController.js.map +1 -1
- package/dist/controllers/smartRoutingController.js +408 -0
- package/dist/controllers/smartRoutingController.js.map +1 -0
- package/dist/dao/CredentialBindingDao.js +5 -0
- package/dist/dao/CredentialBindingDao.js.map +1 -1
- package/dist/dao/CredentialBindingDaoDbImpl.js +7 -0
- package/dist/dao/CredentialBindingDaoDbImpl.js.map +1 -1
- package/dist/db/connection.js +38 -11
- package/dist/db/connection.js.map +1 -1
- package/dist/db/groupSchema.js +2 -2
- package/dist/db/groupSchema.js.map +1 -1
- package/dist/db/repositories/ActivityRepository.js +2 -2
- package/dist/db/repositories/ActivityRepository.js.map +1 -1
- package/dist/db/repositories/BaseRepository.js +4 -1
- package/dist/db/repositories/BaseRepository.js.map +1 -1
- package/dist/db/repositories/BearerKeyRepository.js +3 -1
- package/dist/db/repositories/BearerKeyRepository.js.map +1 -1
- package/dist/db/repositories/BuiltinPromptRepository.js +2 -2
- package/dist/db/repositories/BuiltinPromptRepository.js.map +1 -1
- package/dist/db/repositories/BuiltinResourceRepository.js +2 -2
- package/dist/db/repositories/BuiltinResourceRepository.js.map +1 -1
- package/dist/db/repositories/GroupRepository.js +2 -2
- package/dist/db/repositories/GroupRepository.js.map +1 -1
- package/dist/db/repositories/OAuthClientRepository.js +2 -2
- package/dist/db/repositories/OAuthClientRepository.js.map +1 -1
- package/dist/db/repositories/OAuthTokenRepository.js +3 -1
- package/dist/db/repositories/OAuthTokenRepository.js.map +1 -1
- package/dist/db/repositories/ServerRepository.js +2 -2
- package/dist/db/repositories/ServerRepository.js.map +1 -1
- package/dist/db/repositories/UserConfigRepository.js +2 -2
- package/dist/db/repositories/UserConfigRepository.js.map +1 -1
- package/dist/db/repositories/UserRepository.js +3 -1
- package/dist/db/repositories/UserRepository.js.map +1 -1
- package/dist/middlewares/auth.js +1 -1
- package/dist/middlewares/auth.js.map +1 -1
- package/dist/middlewares/index.js +18 -8
- package/dist/middlewares/index.js.map +1 -1
- package/dist/routes/index.js +12 -2
- package/dist/routes/index.js.map +1 -1
- package/dist/services/betterAuthConfig.js +19 -0
- package/dist/services/betterAuthConfig.js.map +1 -1
- package/dist/services/mcpOAuthProvider.js +7 -0
- package/dist/services/mcpOAuthProvider.js.map +1 -1
- package/dist/services/mcpService.js +99 -18
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/principalRuntimeService.js +1 -3
- package/dist/services/principalRuntimeService.js.map +1 -1
- package/dist/services/vectorSearchService.js +40 -17
- package/dist/services/vectorSearchService.js.map +1 -1
- package/dist/utils/oauthIssuer.js +15 -1
- package/dist/utils/oauthIssuer.js.map +1 -1
- package/dist/utils/rateLimit.js +20 -2
- package/dist/utils/rateLimit.js.map +1 -1
- package/dist/utils/requireAdmin.js +8 -0
- package/dist/utils/requireAdmin.js.map +1 -1
- package/dist/utils/serialization.js +14 -2
- package/dist/utils/serialization.js.map +1 -1
- package/dist/utils/serverConfigPersistence.js +12 -0
- package/dist/utils/serverConfigPersistence.js.map +1 -1
- package/dist/utils/smartRouting.js +69 -27
- package/dist/utils/smartRouting.js.map +1 -1
- package/dist/utils/upstreamError.js +60 -0
- package/dist/utils/upstreamError.js.map +1 -0
- package/dist/utils/withTimeout.js +25 -0
- package/dist/utils/withTimeout.js.map +1 -0
- package/frontend/dist/assets/{ActivityPage-CDKSVr2I.js → ActivityPage-adLbSuxT.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-CDKSVr2I.js.map → ActivityPage-adLbSuxT.js.map} +1 -1
- package/frontend/dist/assets/CopyClientConfigDialog-hRNdG_Hz.js +4 -0
- package/frontend/dist/assets/CopyClientConfigDialog-hRNdG_Hz.js.map +1 -0
- package/frontend/dist/assets/{CredentialsPage-DMJE4uT2.js → CredentialsPage-BSogppb6.js} +2 -2
- package/frontend/dist/assets/{CredentialsPage-DMJE4uT2.js.map → CredentialsPage-BSogppb6.js.map} +1 -1
- package/frontend/dist/assets/Dashboard-BttcAnP2.js +2 -0
- package/frontend/dist/assets/{Dashboard-Dh_39img.js.map → Dashboard-BttcAnP2.js.map} +1 -1
- package/frontend/dist/assets/{EndpointCopy-CQmwGXbD.js → EndpointCopy-CNhtxQdx.js} +2 -2
- package/frontend/dist/assets/{EndpointCopy-CQmwGXbD.js.map → EndpointCopy-CNhtxQdx.js.map} +1 -1
- package/frontend/dist/assets/GroupsPage-CQA7x2cz.js +33 -0
- package/frontend/dist/assets/{GroupsPage-BqscWSfR.js.map → GroupsPage-CQA7x2cz.js.map} +1 -1
- package/frontend/dist/assets/{LoginPage-DcrwvgS6.js → LoginPage-C4qBJzYk.js} +2 -2
- package/frontend/dist/assets/{LoginPage-DcrwvgS6.js.map → LoginPage-C4qBJzYk.js.map} +1 -1
- package/frontend/dist/assets/{LogsPage--Blkr-Ni.js → LogsPage-BXHqsBa0.js} +2 -2
- package/frontend/dist/assets/{LogsPage--Blkr-Ni.js.map → LogsPage-BXHqsBa0.js.map} +1 -1
- package/frontend/dist/assets/MarketPage-C2q7W2an.js +2 -0
- package/frontend/dist/assets/MarketPage-C2q7W2an.js.map +1 -0
- package/frontend/dist/assets/{OAuthConsentPage-DCKZIMzE.js → OAuthConsentPage-D-UU5XNF.js} +2 -2
- package/frontend/dist/assets/{OAuthConsentPage-DCKZIMzE.js.map → OAuthConsentPage-D-UU5XNF.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-DoVmnVnp.js → PromptsPage-2BBvSosU.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-DoVmnVnp.js.map → PromptsPage-2BBvSosU.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-B8udCQQR.js → ResourcesPage-DLVYyIm0.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-B8udCQQR.js.map → ResourcesPage-DLVYyIm0.js.map} +1 -1
- package/frontend/dist/assets/ServersPage-Cx9zIvKw.js +39 -0
- package/frontend/dist/assets/ServersPage-Cx9zIvKw.js.map +1 -0
- package/frontend/dist/assets/SettingsPage-pZCOi8NF.js +12 -0
- package/frontend/dist/assets/SettingsPage-pZCOi8NF.js.map +1 -0
- package/frontend/dist/assets/{StatusDot-DgDU3plH.js → StatusDot-CAv_zsld.js} +2 -2
- package/frontend/dist/assets/{StatusDot-DgDU3plH.js.map → StatusDot-CAv_zsld.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-11yhmcwY.js → ToggleGroup-ffEOMWal.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-11yhmcwY.js.map → ToggleGroup-ffEOMWal.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-BqPEkoIX.js → UsersPage-Cx6EDM_s.js} +2 -2
- package/frontend/dist/assets/{UsersPage-BqPEkoIX.js.map → UsersPage-Cx6EDM_s.js.map} +1 -1
- package/frontend/dist/assets/clipboard-_snued0J.js +2 -0
- package/frontend/dist/assets/clipboard-_snued0J.js.map +1 -0
- package/frontend/dist/assets/{icons-vendor-Bk5sng-h.js → icons-vendor-CNEaJWqW.js} +78 -63
- package/frontend/dist/assets/icons-vendor-CNEaJWqW.js.map +1 -0
- package/frontend/dist/assets/index-Dt8f_hd5.js +3 -0
- package/frontend/dist/assets/index-Dt8f_hd5.js.map +1 -0
- package/frontend/dist/assets/index-ru-OAIcF.css +1 -0
- package/frontend/dist/assets/{resourceService-D-PRt7tB.js → resourceService-BCl2XrNf.js} +2 -2
- package/frontend/dist/assets/{resourceService-D-PRt7tB.js.map → resourceService-BCl2XrNf.js.map} +1 -1
- package/frontend/dist/assets/{useCostData-B-uSfiuA.js → useCostData-D3JNFGsT.js} +2 -2
- package/frontend/dist/assets/{useCostData-B-uSfiuA.js.map → useCostData-D3JNFGsT.js.map} +1 -1
- package/frontend/dist/assets/{variableDetection-B7Ko56CL.js → variableDetection-CGuC1br1.js} +3 -3
- package/frontend/dist/assets/variableDetection-CGuC1br1.js.map +1 -0
- package/frontend/dist/index.html +3 -3
- package/package.json +4 -1
- package/frontend/dist/assets/CopyClientConfigDialog-DYiucK0d.js +0 -4
- package/frontend/dist/assets/CopyClientConfigDialog-DYiucK0d.js.map +0 -1
- package/frontend/dist/assets/Dashboard-Dh_39img.js +0 -2
- package/frontend/dist/assets/GroupsPage-BqscWSfR.js +0 -33
- package/frontend/dist/assets/MarketPage-DWv18j4V.js +0 -3
- package/frontend/dist/assets/MarketPage-DWv18j4V.js.map +0 -1
- package/frontend/dist/assets/ServersPage-OLpH1K1I.js +0 -39
- package/frontend/dist/assets/ServersPage-OLpH1K1I.js.map +0 -1
- package/frontend/dist/assets/SettingsPage-DlK0Jcta.js +0 -12
- package/frontend/dist/assets/SettingsPage-DlK0Jcta.js.map +0 -1
- package/frontend/dist/assets/clipboard-BukcCPLt.js +0 -2
- package/frontend/dist/assets/clipboard-BukcCPLt.js.map +0 -1
- package/frontend/dist/assets/icons-vendor-Bk5sng-h.js.map +0 -1
- package/frontend/dist/assets/index-D4Vy78bV.css +0 -1
- package/frontend/dist/assets/index-DhzWX9nb.js +0 -3
- package/frontend/dist/assets/index-DhzWX9nb.js.map +0 -1
- package/frontend/dist/assets/useSettingsData-Bk1AwN5F.js +0 -2
- package/frontend/dist/assets/useSettingsData-Bk1AwN5F.js.map +0 -1
- package/frontend/dist/assets/variableDetection-B7Ko56CL.js.map +0 -1
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { getSmartRoutingConfig } from '../utils/smartRouting.js';
|
|
2
|
+
import { hasCredentialTemplate } from '../utils/credentialTemplate.js';
|
|
3
|
+
import { requireAdmin } from '../utils/requireAdmin.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
import { getCredentialBindingDao, getUserDao } from '../dao/DaoFactory.js';
|
|
6
|
+
import { getAppDataSource, getDatabaseHealth, initializeDatabase, isDatabaseConnected, } from '../db/connection.js';
|
|
7
|
+
import { saveToolsAsVectorEmbeddings } from '../services/vectorSearchService.js';
|
|
8
|
+
import { getServerToolsForPrincipal, getServersInfo } from '../services/mcpService.js';
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the effective embedding model that the vector store is (or will be)
|
|
11
|
+
* persisted with. Mirrors the resolution in vectorSearchService.saveToolsAsVectorEmbeddings
|
|
12
|
+
* so the reported model matches what is actually written to vector_embeddings.
|
|
13
|
+
*/
|
|
14
|
+
const resolvePersistedEmbeddingModel = (config) => {
|
|
15
|
+
const provider = config.embeddingProvider || 'openai';
|
|
16
|
+
if (provider === 'azure_openai') {
|
|
17
|
+
return config.azureOpenaiEmbeddingModel?.trim() || 'text-embedding-3-small';
|
|
18
|
+
}
|
|
19
|
+
return config.embeddingModel?.trim() || null;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Read aggregate statistics from the vector_embeddings table.
|
|
23
|
+
* Returns null when the query fails so callers can degrade gracefully instead
|
|
24
|
+
* of returning a 500 for a purely informational endpoint.
|
|
25
|
+
*/
|
|
26
|
+
const queryVectorStoreStats = async () => {
|
|
27
|
+
try {
|
|
28
|
+
const dataSource = getAppDataSource();
|
|
29
|
+
const [overview, byModel, byServer, freshness, dims] = await Promise.all([
|
|
30
|
+
dataSource.query(`SELECT
|
|
31
|
+
count(*) FILTER (WHERE content_type = 'tool') AS tool_rows,
|
|
32
|
+
count(*) FILTER (WHERE content_type = 'server') AS server_rows,
|
|
33
|
+
count(*) AS total_rows,
|
|
34
|
+
count(DISTINCT metadata::jsonb->>'serverName') AS distinct_servers
|
|
35
|
+
FROM vector_embeddings`),
|
|
36
|
+
dataSource.query(`SELECT model,
|
|
37
|
+
count(*) FILTER (WHERE content_type = 'tool') AS tool_count,
|
|
38
|
+
count(*) FILTER (WHERE content_type = 'server') AS server_count
|
|
39
|
+
FROM vector_embeddings
|
|
40
|
+
GROUP BY model
|
|
41
|
+
ORDER BY model`),
|
|
42
|
+
dataSource.query(`SELECT metadata::jsonb->>'serverName' AS server_name, count(*) AS tool_count
|
|
43
|
+
FROM vector_embeddings
|
|
44
|
+
WHERE content_type = 'tool'
|
|
45
|
+
GROUP BY 1
|
|
46
|
+
ORDER BY tool_count DESC
|
|
47
|
+
LIMIT 500`),
|
|
48
|
+
dataSource.query(`SELECT to_char(min(updated_at), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS oldest,
|
|
49
|
+
to_char(max(updated_at), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS newest
|
|
50
|
+
FROM vector_embeddings`),
|
|
51
|
+
dataSource.query(`SELECT atttypmod AS dimensions
|
|
52
|
+
FROM pg_attribute
|
|
53
|
+
WHERE attrelid = 'vector_embeddings'::regclass
|
|
54
|
+
AND attname = 'embedding'`),
|
|
55
|
+
]);
|
|
56
|
+
const rawDimensions = dims?.[0]?.dimensions;
|
|
57
|
+
const dimValue = Number(rawDimensions);
|
|
58
|
+
const dimensions = rawDimensions !== undefined && rawDimensions !== null && Number.isFinite(dimValue)
|
|
59
|
+
? dimValue
|
|
60
|
+
: null;
|
|
61
|
+
return {
|
|
62
|
+
totalRows: Number(overview?.[0]?.total_rows ?? 0),
|
|
63
|
+
toolRows: Number(overview?.[0]?.tool_rows ?? 0),
|
|
64
|
+
serverRows: Number(overview?.[0]?.server_rows ?? 0),
|
|
65
|
+
distinctServers: Number(overview?.[0]?.distinct_servers ?? 0),
|
|
66
|
+
dimensions,
|
|
67
|
+
byModel: (byModel ?? []).map((row) => ({
|
|
68
|
+
model: String(row.model ?? ''),
|
|
69
|
+
toolCount: Number(row.tool_count ?? 0),
|
|
70
|
+
serverCount: Number(row.server_count ?? 0),
|
|
71
|
+
})),
|
|
72
|
+
byServer: (byServer ?? []).map((row) => ({
|
|
73
|
+
serverName: String(row.server_name ?? ''),
|
|
74
|
+
toolCount: Number(row.tool_count ?? 0),
|
|
75
|
+
})),
|
|
76
|
+
oldestUpdatedAt: freshness?.[0]?.oldest ?? null,
|
|
77
|
+
newestUpdatedAt: freshness?.[0]?.newest ?? null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
logger.warn('Failed to collect vector store statistics:', error);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Serializes reindex passes inside this process. Overlapping runs would
|
|
87
|
+
* interleave their DELETE and re-embed work — leaving partially indexed servers
|
|
88
|
+
* behind — and double-spend embedding quota, so a second caller is rejected
|
|
89
|
+
* with 409 instead of racing the run in flight.
|
|
90
|
+
*/
|
|
91
|
+
let reindexInFlight = false;
|
|
92
|
+
/**
|
|
93
|
+
* GET /api/smart-routing/performance
|
|
94
|
+
*
|
|
95
|
+
* Returns a read-only snapshot of smart routing health and the vector store:
|
|
96
|
+
* resolved embedding configuration, database connection state, row counts,
|
|
97
|
+
* per-model/per-server breakdown, and sync freshness.
|
|
98
|
+
*
|
|
99
|
+
* Admin-only: the dashboard renders this section behind the admin-only
|
|
100
|
+
* `settings:smart_routing` permission, and the snapshot exposes global
|
|
101
|
+
* operational state (database configuration, index coverage).
|
|
102
|
+
*/
|
|
103
|
+
export const getSmartRoutingPerformance = async (req, res) => {
|
|
104
|
+
if (!(await requireAdmin(req, res)))
|
|
105
|
+
return;
|
|
106
|
+
try {
|
|
107
|
+
const smartRoutingConfig = await getSmartRoutingConfig();
|
|
108
|
+
const databaseHealth = getDatabaseHealth();
|
|
109
|
+
// Only reach into the database when smart routing is enabled and a DB URL
|
|
110
|
+
// is configured; otherwise report the disabled/unconfigured state as-is.
|
|
111
|
+
const dbAvailable = smartRoutingConfig.enabled &&
|
|
112
|
+
Boolean(smartRoutingConfig.dbUrl || process.env.DB_URL);
|
|
113
|
+
let vectorStore = null;
|
|
114
|
+
if (dbAvailable) {
|
|
115
|
+
if (!isDatabaseConnected()) {
|
|
116
|
+
try {
|
|
117
|
+
await initializeDatabase();
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
logger.warn('Failed to initialize database for performance stats:', error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (isDatabaseConnected()) {
|
|
124
|
+
vectorStore = await queryVectorStoreStats();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Coverage is measured against every enabled server, not only the ones this
|
|
128
|
+
// principal happens to see connected: servers that require personal
|
|
129
|
+
// credentials are never connected globally, so a "connected" baseline would
|
|
130
|
+
// silently hide them from the index report.
|
|
131
|
+
let enabledServerNames = [];
|
|
132
|
+
let connectedServers = 0;
|
|
133
|
+
let personalCredentialServers = 0;
|
|
134
|
+
try {
|
|
135
|
+
const servers = await getServersInfo();
|
|
136
|
+
const enabled = servers.filter((server) => server.enabled !== false);
|
|
137
|
+
enabledServerNames = enabled.map((server) => server.name);
|
|
138
|
+
connectedServers = enabled.filter((server) => server.status === 'connected').length;
|
|
139
|
+
personalCredentialServers = enabled.filter((server) => hasCredentialTemplate(server.config)).length;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
logger.warn('Failed to resolve servers for performance stats:', error);
|
|
143
|
+
}
|
|
144
|
+
const indexedServerSet = new Set((vectorStore?.byServer ?? []).map((entry) => entry.serverName));
|
|
145
|
+
const missingIndexServers = enabledServerNames.filter((name) => !indexedServerSet.has(name));
|
|
146
|
+
const provider = smartRoutingConfig.embeddingProvider || 'openai';
|
|
147
|
+
const data = {
|
|
148
|
+
enabled: smartRoutingConfig.enabled,
|
|
149
|
+
reindexing: reindexInFlight,
|
|
150
|
+
config: {
|
|
151
|
+
provider,
|
|
152
|
+
model: resolvePersistedEmbeddingModel(smartRoutingConfig),
|
|
153
|
+
configuredDimensions: typeof smartRoutingConfig.embeddingDimensions === 'number'
|
|
154
|
+
? smartRoutingConfig.embeddingDimensions
|
|
155
|
+
: null,
|
|
156
|
+
},
|
|
157
|
+
database: {
|
|
158
|
+
connected: databaseHealth.connected,
|
|
159
|
+
healthy: databaseHealth.healthy,
|
|
160
|
+
lastError: databaseHealth.lastError,
|
|
161
|
+
},
|
|
162
|
+
vectorStore: {
|
|
163
|
+
available: vectorStore !== null,
|
|
164
|
+
...(vectorStore ?? {
|
|
165
|
+
totalRows: 0,
|
|
166
|
+
toolRows: 0,
|
|
167
|
+
serverRows: 0,
|
|
168
|
+
distinctServers: 0,
|
|
169
|
+
dimensions: null,
|
|
170
|
+
byModel: [],
|
|
171
|
+
byServer: [],
|
|
172
|
+
oldestUpdatedAt: null,
|
|
173
|
+
newestUpdatedAt: null,
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
coverage: {
|
|
177
|
+
totalServers: enabledServerNames.length,
|
|
178
|
+
connectedServers,
|
|
179
|
+
personalCredentialServers,
|
|
180
|
+
indexedServers: indexedServerSet.size,
|
|
181
|
+
missingIndexServerCount: missingIndexServers.length,
|
|
182
|
+
missingIndexServers: missingIndexServers.slice(0, 100),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const response = { success: true, data };
|
|
186
|
+
res.json(response);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
logger.error('Failed to collect smart routing performance stats:', error);
|
|
190
|
+
res.status(500).json({ success: false, message: 'Failed to collect smart routing performance stats' });
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Usernames holding a personal credential binding for a server, ordered with
|
|
195
|
+
* the server owner first so indexing favours the account that defines it.
|
|
196
|
+
*/
|
|
197
|
+
const listBindingUsernames = async (serverName, owner) => {
|
|
198
|
+
try {
|
|
199
|
+
const usernames = await getCredentialBindingDao().listUsernames(serverName);
|
|
200
|
+
const unique = [
|
|
201
|
+
...new Set(usernames.filter((name) => typeof name === 'string' && name.trim().length > 0)),
|
|
202
|
+
];
|
|
203
|
+
unique.sort((a, b) => {
|
|
204
|
+
if (a === owner)
|
|
205
|
+
return -1;
|
|
206
|
+
if (b === owner)
|
|
207
|
+
return 1;
|
|
208
|
+
return a.localeCompare(b);
|
|
209
|
+
});
|
|
210
|
+
return unique;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
logger.warn(`Failed to list credential bindings for server "${serverName}":`, error);
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Collect the union of tools that the binding principals can see for a server.
|
|
219
|
+
* Principals are resolved sequentially because each one may spawn its own
|
|
220
|
+
* upstream connection; a reindex pass should not fan out across all of them.
|
|
221
|
+
*/
|
|
222
|
+
const collectToolsForPrincipals = async (serverName, usernames) => {
|
|
223
|
+
const merged = new Map();
|
|
224
|
+
const principals = [];
|
|
225
|
+
const errors = [];
|
|
226
|
+
for (const username of usernames) {
|
|
227
|
+
try {
|
|
228
|
+
const user = await getUserDao().findByUsername(username);
|
|
229
|
+
if (!user) {
|
|
230
|
+
errors.push(`${username}: user not found`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const tools = await getServerToolsForPrincipal(serverName, {
|
|
234
|
+
username,
|
|
235
|
+
isAdmin: Boolean(user.isAdmin),
|
|
236
|
+
credentialEligible: true,
|
|
237
|
+
});
|
|
238
|
+
for (const tool of tools) {
|
|
239
|
+
if (tool?.name && !merged.has(tool.name)) {
|
|
240
|
+
merged.set(tool.name, tool);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
principals.push(username);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
errors.push(`${username}: ${error?.message ? String(error.message) : 'unknown error'}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { tools: [...merged.values()], principals, errors };
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* POST /api/smart-routing/reindex
|
|
253
|
+
*
|
|
254
|
+
* Force a full rebuild of all vector embeddings: every row in
|
|
255
|
+
* vector_embeddings is cleared, then every enabled server is re-embedded.
|
|
256
|
+
*
|
|
257
|
+
* Servers that declare a personal credential template are never connected
|
|
258
|
+
* globally — their runtime only exists for principals that bound their own
|
|
259
|
+
* credentials. Those servers are therefore indexed on behalf of the users who
|
|
260
|
+
* hold a binding (the server owner first), and the union of the tools those
|
|
261
|
+
* principals can see is what gets embedded. A server with no binding at all is
|
|
262
|
+
* reported as skipped instead of being silently dropped from the index.
|
|
263
|
+
*
|
|
264
|
+
* Progress is streamed to authenticated clients through the existing
|
|
265
|
+
* embedding-sync-progress SSE events emitted by saveToolsAsVectorEmbeddings.
|
|
266
|
+
* Response is sent only when the whole pass finishes (synchronous execution).
|
|
267
|
+
*
|
|
268
|
+
* Admin-only, and serialized: it clears a global table and spends embedding
|
|
269
|
+
* quota, so a second concurrent call is answered with 409.
|
|
270
|
+
*/
|
|
271
|
+
export const reindexSmartRouting = async (req, res) => {
|
|
272
|
+
if (!(await requireAdmin(req, res)))
|
|
273
|
+
return;
|
|
274
|
+
if (reindexInFlight) {
|
|
275
|
+
res.status(409).json({
|
|
276
|
+
success: false,
|
|
277
|
+
message: 'A reindex pass is already running',
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
reindexInFlight = true;
|
|
282
|
+
try {
|
|
283
|
+
const smartRoutingConfig = await getSmartRoutingConfig();
|
|
284
|
+
if (!smartRoutingConfig.enabled) {
|
|
285
|
+
res.status(400).json({ success: false, message: 'Smart routing is not enabled' });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (!smartRoutingConfig.dbUrl && !process.env.DB_URL) {
|
|
289
|
+
res.status(400).json({ success: false, message: 'Database (DB_URL) is not configured' });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!isDatabaseConnected()) {
|
|
293
|
+
await initializeDatabase();
|
|
294
|
+
}
|
|
295
|
+
// 1. Clear the existing index so every tool is regenerated from scratch.
|
|
296
|
+
logger.log('Reindex: clearing all vector embeddings');
|
|
297
|
+
await getAppDataSource().query('DELETE FROM vector_embeddings');
|
|
298
|
+
// 2. Re-embed every enabled server.
|
|
299
|
+
const servers = await getServersInfo();
|
|
300
|
+
const results = [];
|
|
301
|
+
let totalTools = 0;
|
|
302
|
+
let syncedServers = 0;
|
|
303
|
+
let failedServers = 0;
|
|
304
|
+
let skippedServers = 0;
|
|
305
|
+
for (const server of servers) {
|
|
306
|
+
if (server.enabled === false) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
let tools;
|
|
310
|
+
let principals;
|
|
311
|
+
if (hasCredentialTemplate(server.config)) {
|
|
312
|
+
// Personal-credential server: borrow the principals that can reach it.
|
|
313
|
+
const usernames = await listBindingUsernames(server.name, server.config?.owner);
|
|
314
|
+
if (usernames.length === 0) {
|
|
315
|
+
skippedServers += 1;
|
|
316
|
+
results.push({
|
|
317
|
+
serverName: server.name,
|
|
318
|
+
toolCount: 0,
|
|
319
|
+
ok: false,
|
|
320
|
+
skipped: true,
|
|
321
|
+
error: 'No user has bound personal credentials for this server',
|
|
322
|
+
});
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const collected = await collectToolsForPrincipals(server.name, usernames);
|
|
326
|
+
principals = collected.principals;
|
|
327
|
+
tools = collected.tools;
|
|
328
|
+
if (tools.length === 0) {
|
|
329
|
+
failedServers += 1;
|
|
330
|
+
results.push({
|
|
331
|
+
serverName: server.name,
|
|
332
|
+
toolCount: 0,
|
|
333
|
+
ok: false,
|
|
334
|
+
principals,
|
|
335
|
+
error: collected.errors.join('; ') ||
|
|
336
|
+
'No tools could be resolved with the bound personal credentials',
|
|
337
|
+
});
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
if (server.status !== 'connected') {
|
|
343
|
+
skippedServers += 1;
|
|
344
|
+
results.push({
|
|
345
|
+
serverName: server.name,
|
|
346
|
+
toolCount: 0,
|
|
347
|
+
ok: false,
|
|
348
|
+
skipped: true,
|
|
349
|
+
error: `Server is not connected (status: ${server.status ?? 'unknown'})`,
|
|
350
|
+
});
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
tools = server.tools ?? [];
|
|
354
|
+
if (tools.length === 0) {
|
|
355
|
+
skippedServers += 1;
|
|
356
|
+
results.push({
|
|
357
|
+
serverName: server.name,
|
|
358
|
+
toolCount: 0,
|
|
359
|
+
ok: false,
|
|
360
|
+
skipped: true,
|
|
361
|
+
error: 'Server exposes no tools',
|
|
362
|
+
});
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
await saveToolsAsVectorEmbeddings(server.name, tools, { reportProgress: true });
|
|
368
|
+
syncedServers += 1;
|
|
369
|
+
totalTools += tools.length;
|
|
370
|
+
results.push({
|
|
371
|
+
serverName: server.name,
|
|
372
|
+
toolCount: tools.length,
|
|
373
|
+
ok: true,
|
|
374
|
+
...(principals ? { principals } : {}),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
failedServers += 1;
|
|
379
|
+
logger.error(`Reindex failed for server "${server.name}":`, error);
|
|
380
|
+
results.push({
|
|
381
|
+
serverName: server.name,
|
|
382
|
+
toolCount: tools.length,
|
|
383
|
+
ok: false,
|
|
384
|
+
...(principals ? { principals } : {}),
|
|
385
|
+
error: error?.message ? String(error.message) : 'Unknown error',
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const data = {
|
|
390
|
+
syncedServers,
|
|
391
|
+
failedServers,
|
|
392
|
+
skippedServers,
|
|
393
|
+
totalTools,
|
|
394
|
+
results,
|
|
395
|
+
};
|
|
396
|
+
logger.log('Reindex completed', { syncedServers, failedServers, skippedServers, totalTools });
|
|
397
|
+
const response = { success: true, data };
|
|
398
|
+
res.json(response);
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
logger.error('Failed to rebuild smart routing index:', error);
|
|
402
|
+
res.status(500).json({ success: false, message: 'Failed to rebuild smart routing index' });
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
reindexInFlight = false;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
//# sourceMappingURL=smartRoutingController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smartRoutingController.js","sourceRoot":"","sources":["../../src/controllers/smartRoutingController.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,6 +36,11 @@ export class CredentialBindingDaoImpl {
|
|
|
36
36
|
return (this.readAll().find((item) => item.serverName === serverName && item.username === username) ??
|
|
37
37
|
null);
|
|
38
38
|
}
|
|
39
|
+
async listUsernames(serverName) {
|
|
40
|
+
return this.readAll()
|
|
41
|
+
.filter((item) => item.serverName === serverName)
|
|
42
|
+
.map((item) => item.username);
|
|
43
|
+
}
|
|
39
44
|
async save(binding) {
|
|
40
45
|
const bindings = this.readAll().filter((item) => item.serverName !== binding.serverName || item.username !== binding.username);
|
|
41
46
|
this.write([...bindings, binding]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CredentialBindingDao.js","sourceRoot":"","sources":["../../src/dao/CredentialBindingDao.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"CredentialBindingDao.js","sourceRoot":"","sources":["../../src/dao/CredentialBindingDao.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,13 @@ export class CredentialBindingDaoDbImpl {
|
|
|
10
10
|
async get(serverName, username) {
|
|
11
11
|
return this.repository.findOneBy({ serverName, username });
|
|
12
12
|
}
|
|
13
|
+
async listUsernames(serverName) {
|
|
14
|
+
const rows = await this.repository.find({
|
|
15
|
+
where: { serverName },
|
|
16
|
+
select: { username: true },
|
|
17
|
+
});
|
|
18
|
+
return rows.map((row) => row.username);
|
|
19
|
+
}
|
|
13
20
|
async save(binding) {
|
|
14
21
|
await this.repository.upsert(binding, ['serverName', 'username']);
|
|
15
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CredentialBindingDaoDbImpl.js","sourceRoot":"","sources":["../../src/dao/CredentialBindingDaoDbImpl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAIxE,MAAM,OAAO,0BAA0B;IACrC,IAAY,UAAU;QACpB,OAAO,gBAAgB,EAAE,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,WAAW;QACf,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,UAAkB,EAAE,QAAgB;QAC5C,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAgC;QACzC,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAkD;QAC7D,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"CredentialBindingDaoDbImpl.js","sourceRoot":"","sources":["../../src/dao/CredentialBindingDaoDbImpl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAIxE,MAAM,OAAO,0BAA0B;IACrC,IAAY,UAAU;QACpB,OAAO,gBAAgB,EAAE,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,WAAW;QACf,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,UAAkB,EAAE,QAAgB;QAC5C,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,UAAkB;QACpC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACtC,KAAK,EAAE,EAAE,UAAU,EAAE;YACrB,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;SAC3B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAgC;QACzC,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAkD;QAC7D,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;CACF"}
|
package/dist/db/connection.js
CHANGED
|
@@ -7,6 +7,7 @@ import { VectorEmbeddingSubscriber } from './subscribers/VectorEmbeddingSubscrib
|
|
|
7
7
|
import { getSmartRoutingConfig } from '../utils/smartRouting.js';
|
|
8
8
|
import { createVectorIndex } from '../services/vectorSearchService.js';
|
|
9
9
|
import { isRetryableDbError } from '../utils/dbRetry.js';
|
|
10
|
+
import { withTimeout } from '../utils/withTimeout.js';
|
|
10
11
|
import { logger } from '../utils/logger.js';
|
|
11
12
|
// Connection pool and retry configuration
|
|
12
13
|
const CONNECTION_CONFIG = {
|
|
@@ -17,8 +18,18 @@ const CONNECTION_CONFIG = {
|
|
|
17
18
|
// Automatic reconnection settings
|
|
18
19
|
maxConnectionRetries: parseInt(process.env.DB_MAX_CONNECTION_RETRIES || '5', 10),
|
|
19
20
|
connectionRetryDelayMs: parseInt(process.env.DB_CONNECTION_RETRY_DELAY || '3000', 10),
|
|
21
|
+
// Bounds pool teardown during reconnection. pg-pool's end() only completes
|
|
22
|
+
// once every client is returned, so a client stuck in an open transaction
|
|
23
|
+
// (idle in transaction) would otherwise wedge reconnection forever (#1205).
|
|
24
|
+
destroyTimeout: parseInt(process.env.DB_DESTROY_TIMEOUT || '15000', 10), // 15 seconds
|
|
25
|
+
// Postgres-side safety valves (0 = use PostgreSQL defaults / disabled). Set
|
|
26
|
+
// per-connection via the startup packet so a hung query or a session left in
|
|
27
|
+
// `idle in transaction` cannot hold a pooled connection indefinitely.
|
|
28
|
+
statementTimeout: parseInt(process.env.DB_STATEMENT_TIMEOUT || '0', 10),
|
|
29
|
+
idleInTransactionSessionTimeout: parseInt(process.env.DB_IDLE_IN_TRANSACTION_TIMEOUT || '0', 10),
|
|
20
30
|
// Health check settings
|
|
21
31
|
healthCheckIntervalMs: parseInt(process.env.DB_HEALTH_CHECK_INTERVAL || '30000', 10), // 30 seconds
|
|
32
|
+
healthCheckQueryTimeout: parseInt(process.env.DB_HEALTH_CHECK_QUERY_TIMEOUT || '5000', 10), // 5 seconds
|
|
22
33
|
enableHealthCheck: process.env.DB_ENABLE_HEALTH_CHECK !== 'false',
|
|
23
34
|
};
|
|
24
35
|
// Health check state
|
|
@@ -94,6 +105,11 @@ const getDefaultConfig = async () => {
|
|
|
94
105
|
// Keep-alive settings to detect dead connections faster
|
|
95
106
|
keepAlive: true,
|
|
96
107
|
keepAliveInitialDelayMillis: 10000,
|
|
108
|
+
// Postgres-side safety valves (0 = PostgreSQL default / disabled): a
|
|
109
|
+
// hung query or a session stuck in `idle in transaction` must not be
|
|
110
|
+
// able to hold a pooled connection indefinitely (#1205).
|
|
111
|
+
statement_timeout: CONNECTION_CONFIG.statementTimeout,
|
|
112
|
+
idle_in_transaction_session_timeout: CONNECTION_CONFIG.idleInTransactionSessionTimeout,
|
|
97
113
|
},
|
|
98
114
|
};
|
|
99
115
|
};
|
|
@@ -394,8 +410,10 @@ export const checkDatabaseHealth = async () => {
|
|
|
394
410
|
}
|
|
395
411
|
}
|
|
396
412
|
try {
|
|
397
|
-
// Simple query to check connection
|
|
398
|
-
|
|
413
|
+
// Simple query to check connection. Bounded by a short timeout so a DB
|
|
414
|
+
// that stops accepting queries cannot stall the health probe for the full
|
|
415
|
+
// pool connection timeout (#1205).
|
|
416
|
+
await withTimeout(appDataSource.query('SELECT 1'), CONNECTION_CONFIG.healthCheckQueryTimeout, `DB health check failed: connection timeout after ${CONNECTION_CONFIG.healthCheckQueryTimeout}ms`);
|
|
399
417
|
isHealthy = true;
|
|
400
418
|
lastHealthCheckError = null;
|
|
401
419
|
return true;
|
|
@@ -453,25 +471,33 @@ const attemptReconnection = () => {
|
|
|
453
471
|
if (!appDataSource) {
|
|
454
472
|
return await initializeDatabase();
|
|
455
473
|
}
|
|
456
|
-
|
|
474
|
+
let dataSource = appDataSource;
|
|
457
475
|
if (dataSource.isInitialized) {
|
|
458
476
|
try {
|
|
459
477
|
logger.log('[DB Reconnect] Closing existing connection...');
|
|
460
|
-
|
|
478
|
+
// pg-pool end() waits for every checked-out client. A client stuck
|
|
479
|
+
// in an open transaction would make destroy() hang forever, wedging
|
|
480
|
+
// the whole reconnection machinery — bound it and fall back to a
|
|
481
|
+
// fresh pool instead (#1205).
|
|
482
|
+
await withTimeout(dataSource.destroy(), CONNECTION_CONFIG.destroyTimeout, `[DB Reconnect] destroy() timed out after ${CONNECTION_CONFIG.destroyTimeout}ms`);
|
|
461
483
|
}
|
|
462
484
|
catch (closeError) {
|
|
463
485
|
logger.warn('[DB Reconnect] Error closing connection:', closeError.message);
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
486
|
+
// The old destroy may still finish later and mutate its driver and
|
|
487
|
+
// isInitialized flag. Never initialize that same instance again.
|
|
488
|
+
dataSource = new DataSource({ ...dataSource.options });
|
|
489
|
+
appDataSource = dataSource;
|
|
468
490
|
}
|
|
469
491
|
}
|
|
470
492
|
let lastError = null;
|
|
471
493
|
for (let attempt = 1; attempt <= CONNECTION_CONFIG.maxConnectionRetries; attempt++) {
|
|
472
494
|
try {
|
|
473
495
|
logger.log(`[DB Reconnect] Connection attempt ${attempt}/${CONNECTION_CONFIG.maxConnectionRetries}...`);
|
|
474
|
-
|
|
496
|
+
// Reconnect with skipSynchronize: schema DDL runs inside a
|
|
497
|
+
// transaction and can block on locks held by live traffic, leaving a
|
|
498
|
+
// connection stuck in `idle in transaction` and blocking pool
|
|
499
|
+
// teardown. The schema is already synchronized from startup (#1205).
|
|
500
|
+
await initializeWithGroupNameCheck(dataSource, { skipSynchronize: true });
|
|
475
501
|
registerPostgresVectorType(dataSource);
|
|
476
502
|
appDataSource = dataSource;
|
|
477
503
|
logger.log('[DB Reconnect] Successfully reconnected to database');
|
|
@@ -484,11 +510,12 @@ const attemptReconnection = () => {
|
|
|
484
510
|
logger.warn(`[DB Reconnect] Attempt ${attempt} failed: ${lastError.message}`);
|
|
485
511
|
if (dataSource.isInitialized) {
|
|
486
512
|
try {
|
|
487
|
-
await dataSource.destroy();
|
|
513
|
+
await withTimeout(dataSource.destroy(), CONNECTION_CONFIG.destroyTimeout, `[DB Reconnect] Cleanup destroy() timed out after ${CONNECTION_CONFIG.destroyTimeout}ms`);
|
|
488
514
|
}
|
|
489
515
|
catch (destroyError) {
|
|
490
516
|
logger.warn('[DB Reconnect] Error cleaning up partially initialized connection:', destroyError.message);
|
|
491
|
-
|
|
517
|
+
dataSource = new DataSource({ ...dataSource.options });
|
|
518
|
+
appDataSource = dataSource;
|
|
492
519
|
}
|
|
493
520
|
}
|
|
494
521
|
if (attempt < CONNECTION_CONFIG.maxConnectionRetries) {
|