@toxplanet/pegasus-sdk 1.1.18 → 1.1.20
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/config/environment.acc.js +4 -15
- package/config/environment.dev.js +18 -29
- package/config/environment.prod.js +4 -15
- package/config/environment.qa.js +4 -15
- package/lib/chemicals.js +54 -88
- package/lib/connection.js +122 -204
- package/lib/db/index.js +3 -3
- package/package.json +2 -3
|
@@ -3,24 +3,13 @@ module.exports = {
|
|
|
3
3
|
region: 'us-east-1',
|
|
4
4
|
awsAccountId: '605134466764',
|
|
5
5
|
sourceService: 'pegasus-sdk',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
openSearchIndex: 'chemicals',
|
|
6
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:605134466764:secret:rds!cluster-9b502dde-5e2a-49db-b2c5-9801141ee40b-gkHbLm',
|
|
7
|
+
clusterArn: 'arn:aws:rds:us-east-1:605134466764:cluster:cr-chemicals-acc',
|
|
9
8
|
database: {
|
|
10
|
-
host: 'cr-chemicals-acc.cluster-czgc4c20yerz.us-east-1.rds.amazonaws.com',
|
|
11
9
|
name: 'chemicals'
|
|
12
10
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
minConnections: 0,
|
|
16
|
-
idleTimeoutMillis: 30000,
|
|
17
|
-
connectionTimeoutMillis: 5000,
|
|
18
|
-
statementTimeout: 30000,
|
|
19
|
-
queryTimeout: 30000,
|
|
20
|
-
ssl: {
|
|
21
|
-
rejectUnauthorized: false
|
|
22
|
-
}
|
|
23
|
-
},
|
|
11
|
+
openSearchEndpoint: 'https://1pbu0yqr197lq07hfcjh.us-east-1.aoss.amazonaws.com',
|
|
12
|
+
openSearchIndex: 'chemicals',
|
|
24
13
|
indexRoutes: {
|
|
25
14
|
chemicals: ['chemicals*'],
|
|
26
15
|
documents: ['documents*'],
|
|
@@ -1,29 +1,18 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
environment: 'dev',
|
|
3
|
-
region: 'us-east-1',
|
|
4
|
-
awsAccountId: '292931567094',
|
|
5
|
-
sourceService: 'pegasus-sdk',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
queryTimeout: 30000,
|
|
20
|
-
ssl: {
|
|
21
|
-
rejectUnauthorized: false
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
indexRoutes: {
|
|
25
|
-
chemicals: ['chemicals*'],
|
|
26
|
-
documents: ['documents*'],
|
|
27
|
-
search: [/^(chemicals|substances|search)/]
|
|
28
|
-
}
|
|
29
|
-
};
|
|
1
|
+
module.exports = {
|
|
2
|
+
environment: 'dev',
|
|
3
|
+
region: 'us-east-1',
|
|
4
|
+
awsAccountId: '292931567094',
|
|
5
|
+
sourceService: 'pegasus-sdk',
|
|
6
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:292931567094:secret:rds!cluster-b851c3ce-58cc-41cd-aeae-05cc7f5e031a-ZYSjiI',
|
|
7
|
+
clusterArn: 'arn:aws:rds:us-east-1:292931567094:cluster:cr-chemicals',
|
|
8
|
+
database: {
|
|
9
|
+
name: 'chemicals'
|
|
10
|
+
},
|
|
11
|
+
openSearchEndpoint: 'https://war8lk73nzswquk8dcz1.us-east-1.aoss.amazonaws.com',
|
|
12
|
+
openSearchIndex: 'chemicals',
|
|
13
|
+
indexRoutes: {
|
|
14
|
+
chemicals: ['chemicals*'],
|
|
15
|
+
documents: ['documents*'],
|
|
16
|
+
search: [/^(chemicals|substances|search)/]
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -3,24 +3,13 @@ module.exports = {
|
|
|
3
3
|
region: 'us-east-1',
|
|
4
4
|
awsAccountId: '147997144422',
|
|
5
5
|
sourceService: 'pegasus-sdk',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
openSearchIndex: 'chemicals',
|
|
6
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:147997144422:secret:rds!cluster-25483b3f-3758-43ed-9548-26c91de16c2d',
|
|
7
|
+
clusterArn: 'arn:aws:rds:us-east-1:147997144422:cluster:cr-chemicals-prod',
|
|
9
8
|
database: {
|
|
10
|
-
host: 'cr-chemicals-qa.cluster-c7gakqksq9m4.us-east-1.rds.amazonaws.com',
|
|
11
9
|
name: 'chemicals'
|
|
12
10
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
minConnections: 2,
|
|
16
|
-
idleTimeoutMillis: 120000,
|
|
17
|
-
connectionTimeoutMillis: 15000,
|
|
18
|
-
statementTimeout: 120000,
|
|
19
|
-
queryTimeout: 120000,
|
|
20
|
-
ssl: {
|
|
21
|
-
rejectUnauthorized: true
|
|
22
|
-
}
|
|
23
|
-
},
|
|
11
|
+
openSearchEndpoint: 'https://odusb11s00j5hyy5r6.us-east-1.aoss.amazonaws.com',
|
|
12
|
+
openSearchIndex: 'chemicals',
|
|
24
13
|
indexRoutes: {
|
|
25
14
|
chemicals: ['chemicals*'],
|
|
26
15
|
documents: ['documents*'],
|
package/config/environment.qa.js
CHANGED
|
@@ -3,24 +3,13 @@ module.exports = {
|
|
|
3
3
|
region: 'us-east-1',
|
|
4
4
|
awsAccountId: '147997144422',
|
|
5
5
|
sourceService: 'pegasus-sdk',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
openSearchIndex: 'chemicals',
|
|
6
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:147997144422:secret:rds!cluster-25483b3f-3758-43ed-9548-26c91de16c2d-oYjysU',
|
|
7
|
+
clusterArn: 'arn:aws:rds:us-east-1:147997144422:cluster:cr-chemicals-qa',
|
|
9
8
|
database: {
|
|
10
|
-
host: 'cr-chemicals-qa.cluster-c7gakqksq9m4.us-east-1.rds.amazonaws.com',
|
|
11
9
|
name: 'chemicals'
|
|
12
10
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
minConnections: 1,
|
|
16
|
-
idleTimeoutMillis: 60000,
|
|
17
|
-
connectionTimeoutMillis: 10000,
|
|
18
|
-
statementTimeout: 60000,
|
|
19
|
-
queryTimeout: 60000,
|
|
20
|
-
ssl: {
|
|
21
|
-
rejectUnauthorized: true
|
|
22
|
-
}
|
|
23
|
-
},
|
|
11
|
+
openSearchEndpoint: 'https://odusb11s00j5hyy5r6.us-east-1.aoss.amazonaws.com',
|
|
12
|
+
openSearchIndex: 'chemicals',
|
|
24
13
|
indexRoutes: {
|
|
25
14
|
chemicals: ['chemicals*'],
|
|
26
15
|
documents: ['documents*'],
|
package/lib/chemicals.js
CHANGED
|
@@ -21,11 +21,9 @@ class ChemicalsService {
|
|
|
21
21
|
this.sqsClient = null;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
getDb() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
return this.db;
|
|
24
|
+
async getDb() {
|
|
25
|
+
await this.connection.ensureConnected();
|
|
26
|
+
return this.connection.db;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
async sendSqlWriteFailure({ sql, parameters, error, retryCount, failedAt }) {
|
|
@@ -75,28 +73,28 @@ class ChemicalsService {
|
|
|
75
73
|
_buildChemicalUpsertSql(chemical) {
|
|
76
74
|
const sql = [
|
|
77
75
|
'INSERT INTO chemicals (source_id, chemical_name, chemical_meta, chemical_identifiers, chemical_synonyms, chemical_categories, created_at, updated_at)',
|
|
78
|
-
'VALUES (
|
|
76
|
+
'VALUES (:source_id, :chemical_name, :chemical_meta::jsonb, :chemical_identifiers::jsonb, :chemical_synonyms, :chemical_categories, :created_at, :updated_at)',
|
|
79
77
|
'ON CONFLICT (source_id) DO UPDATE SET',
|
|
80
|
-
' chemical_name =
|
|
81
|
-
' chemical_meta =
|
|
82
|
-
' chemical_identifiers =
|
|
83
|
-
' chemical_synonyms =
|
|
84
|
-
' chemical_categories =
|
|
85
|
-
' updated_at =
|
|
78
|
+
' chemical_name = :chemical_name,',
|
|
79
|
+
' chemical_meta = :chemical_meta::jsonb,',
|
|
80
|
+
' chemical_identifiers = :chemical_identifiers::jsonb,',
|
|
81
|
+
' chemical_synonyms = :chemical_synonyms,',
|
|
82
|
+
' chemical_categories = :chemical_categories,',
|
|
83
|
+
' updated_at = :updated_at'
|
|
86
84
|
].join('\n');
|
|
87
85
|
|
|
88
86
|
const serializeDate = (d) => d instanceof Date ? d.toISOString() : d;
|
|
89
87
|
|
|
90
|
-
const parameters =
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
94
|
-
'
|
|
95
|
-
'
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
|
|
88
|
+
const parameters = [
|
|
89
|
+
{ name: 'source_id', value: { stringValue: chemical.sourceId } },
|
|
90
|
+
{ name: 'chemical_name', value: { stringValue: chemical.chemicalName } },
|
|
91
|
+
{ name: 'chemical_meta', value: { stringValue: JSON.stringify(chemical.chemicalMeta ?? {}) } },
|
|
92
|
+
{ name: 'chemical_identifiers', value: { stringValue: JSON.stringify(chemical.chemicalIdentifiers ?? {}) } },
|
|
93
|
+
{ name: 'chemical_synonyms', value: { stringValue: JSON.stringify(chemical.chemicalSynonyms ?? []) } },
|
|
94
|
+
{ name: 'chemical_categories', value: { stringValue: JSON.stringify(chemical.chemicalCategories ?? []) } },
|
|
95
|
+
{ name: 'created_at', value: { stringValue: serializeDate(chemical.createdAt) } },
|
|
96
|
+
{ name: 'updated_at', value: { stringValue: serializeDate(chemical.updatedAt) } }
|
|
97
|
+
];
|
|
100
98
|
|
|
101
99
|
return { sql, parameters };
|
|
102
100
|
}
|
|
@@ -142,15 +140,6 @@ class ChemicalsService {
|
|
|
142
140
|
return { indexed: 0, errors: [], results: [] };
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
// Proactively validate the connection before any real query fires.
|
|
146
|
-
// If idle too long, this reconnects first so the real query never faces
|
|
147
|
-
// the full connectionTimeoutMillis wait on a stale pool.
|
|
148
|
-
const reconnected = await this.connection.ensureConnected();
|
|
149
|
-
if (reconnected) {
|
|
150
|
-
this.db = null; // force getDb() to bind to the fresh pool
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const db = this.getDb();
|
|
154
143
|
const results = [];
|
|
155
144
|
const errors = [];
|
|
156
145
|
|
|
@@ -183,14 +172,9 @@ class ChemicalsService {
|
|
|
183
172
|
logInfo('pegasus-sdk', `[bulkIndexFielded] Prepared chemical object: sourceId=${chemical.sourceId}, chemicalName=${chemical.chemicalName}`);
|
|
184
173
|
logInfo('pegasus-sdk', `[bulkIndexFielded] DEBUG SQL for document ${i}:\n${this._buildDebugSql(chemical)}`);
|
|
185
174
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
err.code === 'ECONNREFUSED' ||
|
|
190
|
-
err.code === 'ETIMEDOUT';
|
|
191
|
-
|
|
192
|
-
const attemptUpsert = () =>
|
|
193
|
-
db.insert(schema.chemicals)
|
|
175
|
+
const attemptUpsert = async () => {
|
|
176
|
+
const freshDb = await this.getDb();
|
|
177
|
+
return freshDb.insert(schema.chemicals)
|
|
194
178
|
.values(chemical)
|
|
195
179
|
.onConflictDoUpdate({
|
|
196
180
|
target: schema.chemicals.sourceId,
|
|
@@ -207,6 +191,7 @@ class ChemicalsService {
|
|
|
207
191
|
chemicalId: schema.chemicals.chemicalId,
|
|
208
192
|
sourceId: schema.chemicals.sourceId
|
|
209
193
|
});
|
|
194
|
+
};
|
|
210
195
|
|
|
211
196
|
let lastError = null;
|
|
212
197
|
let retryCount = 0;
|
|
@@ -215,39 +200,20 @@ class ChemicalsService {
|
|
|
215
200
|
try {
|
|
216
201
|
const [result] = await attemptUpsert();
|
|
217
202
|
logInfo('pegasus-sdk', `[bulkIndexFielded] Document ${i} indexed successfully: ${result?.chemicalId || 'no ID returned'}`);
|
|
218
|
-
this.connection.recordActivity();
|
|
219
203
|
results.push({ index: i, success: true, result });
|
|
220
204
|
continue;
|
|
221
205
|
} catch (firstErr) {
|
|
222
206
|
lastError = firstErr;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
results.push({ index: i, success: true, result });
|
|
234
|
-
continue;
|
|
235
|
-
} catch (reconnectErr) {
|
|
236
|
-
lastError = reconnectErr;
|
|
237
|
-
retryCount = 1;
|
|
238
|
-
}
|
|
239
|
-
} else {
|
|
240
|
-
logInfo('pegasus-sdk', `[bulkIndexFielded] Document ${i} first attempt failed (${firstErr.message}), retrying once`);
|
|
241
|
-
try {
|
|
242
|
-
const [result] = await attemptUpsert();
|
|
243
|
-
logInfo('pegasus-sdk', `[bulkIndexFielded] Document ${i} indexed successfully on retry: ${result?.chemicalId || 'no ID returned'}`);
|
|
244
|
-
this.connection.recordActivity();
|
|
245
|
-
results.push({ index: i, success: true, result });
|
|
246
|
-
continue;
|
|
247
|
-
} catch (retryErr) {
|
|
248
|
-
lastError = retryErr;
|
|
249
|
-
retryCount = 1;
|
|
250
|
-
}
|
|
207
|
+
logInfo('pegasus-sdk', `[bulkIndexFielded] Document ${i} first attempt failed (${firstErr.message}), retrying once`);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const [result] = await attemptUpsert();
|
|
211
|
+
logInfo('pegasus-sdk', `[bulkIndexFielded] Document ${i} indexed successfully on retry: ${result?.chemicalId || 'no ID returned'}`);
|
|
212
|
+
results.push({ index: i, success: true, result });
|
|
213
|
+
continue;
|
|
214
|
+
} catch (retryErr) {
|
|
215
|
+
lastError = retryErr;
|
|
216
|
+
retryCount = 1;
|
|
251
217
|
}
|
|
252
218
|
}
|
|
253
219
|
|
|
@@ -315,7 +281,7 @@ class ChemicalsService {
|
|
|
315
281
|
|
|
316
282
|
async createChemical(chemical) {
|
|
317
283
|
try {
|
|
318
|
-
const db = this.getDb();
|
|
284
|
+
const db = await this.getDb();
|
|
319
285
|
|
|
320
286
|
const [result] = await db
|
|
321
287
|
.insert(schema.chemicals)
|
|
@@ -342,7 +308,7 @@ class ChemicalsService {
|
|
|
342
308
|
|
|
343
309
|
async updateChemical(chemicalId, updates) {
|
|
344
310
|
try {
|
|
345
|
-
const db = this.getDb();
|
|
311
|
+
const db = await this.getDb();
|
|
346
312
|
|
|
347
313
|
const updateData = {};
|
|
348
314
|
if (updates.chemical_name) updateData.chemicalName = updates.chemical_name;
|
|
@@ -367,7 +333,7 @@ class ChemicalsService {
|
|
|
367
333
|
|
|
368
334
|
async deleteChemical(chemicalId) {
|
|
369
335
|
try {
|
|
370
|
-
const db = this.getDb();
|
|
336
|
+
const db = await this.getDb();
|
|
371
337
|
|
|
372
338
|
const [deleted] = await db
|
|
373
339
|
.delete(schema.chemicals)
|
|
@@ -383,7 +349,7 @@ class ChemicalsService {
|
|
|
383
349
|
|
|
384
350
|
async deleteBySourceId(sourceId) {
|
|
385
351
|
try {
|
|
386
|
-
const db = this.getDb();
|
|
352
|
+
const db = await this.getDb();
|
|
387
353
|
|
|
388
354
|
const [deleted] = await db
|
|
389
355
|
.delete(schema.chemicals)
|
|
@@ -399,7 +365,7 @@ class ChemicalsService {
|
|
|
399
365
|
|
|
400
366
|
async deleteCollection(collectionName) {
|
|
401
367
|
try {
|
|
402
|
-
const db = this.getDb();
|
|
368
|
+
const db = await this.getDb();
|
|
403
369
|
|
|
404
370
|
const deleted = await db
|
|
405
371
|
.delete(schema.chemicals)
|
|
@@ -415,7 +381,7 @@ class ChemicalsService {
|
|
|
415
381
|
|
|
416
382
|
async updateCollectionProperty(collectionName, propertyPath, newValue) {
|
|
417
383
|
try {
|
|
418
|
-
const db = this.getDb();
|
|
384
|
+
const db = await this.getDb();
|
|
419
385
|
const pathArray = propertyPath.split('.');
|
|
420
386
|
const valueJson = JSON.stringify(newValue);
|
|
421
387
|
|
|
@@ -437,7 +403,7 @@ class ChemicalsService {
|
|
|
437
403
|
|
|
438
404
|
async bulkUpdateProperty(filter, propertyPath, newValue) {
|
|
439
405
|
try {
|
|
440
|
-
const db = this.getDb();
|
|
406
|
+
const db = await this.getDb();
|
|
441
407
|
|
|
442
408
|
let whereCondition = sql`1=1`;
|
|
443
409
|
|
|
@@ -470,7 +436,7 @@ class ChemicalsService {
|
|
|
470
436
|
|
|
471
437
|
async getChemicalById(chemicalId) {
|
|
472
438
|
try {
|
|
473
|
-
const db = this.getDb();
|
|
439
|
+
const db = await this.getDb();
|
|
474
440
|
|
|
475
441
|
const [result] = await db
|
|
476
442
|
.select()
|
|
@@ -487,7 +453,7 @@ class ChemicalsService {
|
|
|
487
453
|
|
|
488
454
|
async getChemicalBySourceId(sourceId) {
|
|
489
455
|
try {
|
|
490
|
-
const db = this.getDb();
|
|
456
|
+
const db = await this.getDb();
|
|
491
457
|
|
|
492
458
|
const [result] = await db
|
|
493
459
|
.select()
|
|
@@ -504,7 +470,7 @@ class ChemicalsService {
|
|
|
504
470
|
|
|
505
471
|
async getChemicalsByCAS(casNumber) {
|
|
506
472
|
try {
|
|
507
|
-
const db = this.getDb();
|
|
473
|
+
const db = await this.getDb();
|
|
508
474
|
|
|
509
475
|
const results = await db
|
|
510
476
|
.select()
|
|
@@ -524,7 +490,7 @@ class ChemicalsService {
|
|
|
524
490
|
throw new Error(`Invalid identifier type: ${identifierType}`);
|
|
525
491
|
}
|
|
526
492
|
|
|
527
|
-
const db = this.getDb();
|
|
493
|
+
const db = await this.getDb();
|
|
528
494
|
|
|
529
495
|
const results = await db
|
|
530
496
|
.select()
|
|
@@ -540,7 +506,7 @@ class ChemicalsService {
|
|
|
540
506
|
|
|
541
507
|
async countByCollection(collectionName) {
|
|
542
508
|
try {
|
|
543
|
-
const db = this.getDb();
|
|
509
|
+
const db = await this.getDb();
|
|
544
510
|
|
|
545
511
|
const result = await db
|
|
546
512
|
.select({ count: sql`count(*)::int` })
|
|
@@ -556,7 +522,7 @@ class ChemicalsService {
|
|
|
556
522
|
|
|
557
523
|
async countByIdentifier(identifierValue) {
|
|
558
524
|
try {
|
|
559
|
-
const db = this.getDb();
|
|
525
|
+
const db = await this.getDb();
|
|
560
526
|
|
|
561
527
|
const searchPattern = `%${escapeLikePattern(identifierValue)}%`;
|
|
562
528
|
const result = await db
|
|
@@ -573,7 +539,7 @@ class ChemicalsService {
|
|
|
573
539
|
|
|
574
540
|
async countByCAS(casNumber) {
|
|
575
541
|
try {
|
|
576
|
-
const db = this.getDb();
|
|
542
|
+
const db = await this.getDb();
|
|
577
543
|
|
|
578
544
|
const result = await db
|
|
579
545
|
.select({ count: sql`count(*)::int` })
|
|
@@ -589,7 +555,7 @@ class ChemicalsService {
|
|
|
589
555
|
|
|
590
556
|
async getTotalSynonymCount() {
|
|
591
557
|
try {
|
|
592
|
-
const db = this.getDb();
|
|
558
|
+
const db = await this.getDb();
|
|
593
559
|
|
|
594
560
|
const result = await db
|
|
595
561
|
.select({ count: sql`sum(array_length(${schema.chemicals.chemicalSynonyms}, 1))::int` })
|
|
@@ -604,7 +570,7 @@ class ChemicalsService {
|
|
|
604
570
|
|
|
605
571
|
async getSynonymCount(synonymTerm) {
|
|
606
572
|
try {
|
|
607
|
-
const db = this.getDb();
|
|
573
|
+
const db = await this.getDb();
|
|
608
574
|
|
|
609
575
|
const result = await db
|
|
610
576
|
.select({ count: sql`count(*)::int` })
|
|
@@ -620,7 +586,7 @@ class ChemicalsService {
|
|
|
620
586
|
|
|
621
587
|
async convertIdentifier(fromIdentifier, toIdentifierType) {
|
|
622
588
|
try {
|
|
623
|
-
const db = this.getDb();
|
|
589
|
+
const db = await this.getDb();
|
|
624
590
|
|
|
625
591
|
const searchPattern = `%${escapeLikePattern(fromIdentifier)}%`;
|
|
626
592
|
const chemicals = await db
|
|
@@ -768,7 +734,7 @@ class ChemicalsService {
|
|
|
768
734
|
|
|
769
735
|
async countAll() {
|
|
770
736
|
try {
|
|
771
|
-
const db = this.getDb();
|
|
737
|
+
const db = await this.getDb();
|
|
772
738
|
const result = await db
|
|
773
739
|
.select({ count: sql`count(*)::int` })
|
|
774
740
|
.from(schema.chemicals);
|
|
@@ -781,7 +747,7 @@ class ChemicalsService {
|
|
|
781
747
|
|
|
782
748
|
async findChemicalsWithoutDocuments(collectionName, searchTerm, pageSize = 100) {
|
|
783
749
|
try {
|
|
784
|
-
const db = this.getDb();
|
|
750
|
+
const db = await this.getDb();
|
|
785
751
|
|
|
786
752
|
let whereConditions = [];
|
|
787
753
|
|
|
@@ -811,7 +777,7 @@ class ChemicalsService {
|
|
|
811
777
|
|
|
812
778
|
async countChemicalsWithoutDocuments(collectionName) {
|
|
813
779
|
try {
|
|
814
|
-
const db = this.getDb();
|
|
780
|
+
const db = await this.getDb();
|
|
815
781
|
|
|
816
782
|
const whereClause = collectionName
|
|
817
783
|
? arrayContains(schema.chemicals.chemicalCategories, [collectionName])
|
package/lib/connection.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const {
|
|
2
|
-
const {
|
|
3
|
-
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
|
|
1
|
+
const { Client: OpenSearchClient } = require('@opensearch-project/opensearch');
|
|
2
|
+
const { RDSDataClient, ExecuteStatementCommand } = require('@aws-sdk/client-rds-data');
|
|
4
3
|
const { AwsSigv4Signer } = require('@opensearch-project/opensearch/aws');
|
|
5
4
|
const { fromNodeProviderChain } = require('@aws-sdk/credential-providers');
|
|
5
|
+
const { getDrizzle, schema } = require('./db');
|
|
6
6
|
const { loadConfig } = require('../config');
|
|
7
7
|
const { logInfo, logError } = require('@toxplanet/tphelper/logging');
|
|
8
8
|
|
|
@@ -13,38 +13,16 @@ class PegasusConnection {
|
|
|
13
13
|
this.config = { ...envConfig, ...config };
|
|
14
14
|
this.environment = this.config.environment;
|
|
15
15
|
this.region = this.config.region;
|
|
16
|
-
this.
|
|
16
|
+
this.secretArn = this.config.secretArn;
|
|
17
|
+
this.clusterArn = this.config.clusterArn;
|
|
17
18
|
this.openSearchEndpoint = this.config.openSearchEndpoint;
|
|
18
19
|
this.openSearchIndex = this.config.openSearchIndex;
|
|
19
|
-
this.databaseHost = this.config.database?.host;
|
|
20
20
|
this.databaseName = this.config.database?.name;
|
|
21
21
|
|
|
22
|
-
this.
|
|
22
|
+
this.rdsDataClient = null;
|
|
23
|
+
this.db = null;
|
|
23
24
|
this.osClient = null;
|
|
24
|
-
this.secretsClient = null;
|
|
25
|
-
this.cachedSecret = null;
|
|
26
25
|
this.isConnected = false;
|
|
27
|
-
this.lastActivityAt = null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async getSecret() {
|
|
31
|
-
if (this.cachedSecret) {
|
|
32
|
-
return this.cachedSecret;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!this.secretsClient) {
|
|
36
|
-
this.secretsClient = new SecretsManagerClient({ region: this.region });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const command = new GetSecretValueCommand({ SecretId: this.secretName });
|
|
40
|
-
const response = await this.secretsClient.send(command);
|
|
41
|
-
|
|
42
|
-
if (response.SecretString) {
|
|
43
|
-
this.cachedSecret = JSON.parse(response.SecretString);
|
|
44
|
-
return this.cachedSecret;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
throw new Error(`Secret ${this.secretName} does not contain SecretString`);
|
|
48
26
|
}
|
|
49
27
|
|
|
50
28
|
async connect() {
|
|
@@ -52,52 +30,32 @@ class PegasusConnection {
|
|
|
52
30
|
return;
|
|
53
31
|
}
|
|
54
32
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const poolConfig = {
|
|
58
|
-
host: this.databaseHost,
|
|
59
|
-
port: 5432,
|
|
60
|
-
database: this.databaseName,
|
|
61
|
-
user: secret.username,
|
|
62
|
-
password: secret.password,
|
|
63
|
-
max: this.config.postgres.maxConnections,
|
|
64
|
-
min: this.config.postgres.minConnections,
|
|
65
|
-
idleTimeoutMillis: this.config.postgres.idleTimeoutMillis,
|
|
66
|
-
connectionTimeoutMillis: this.config.postgres.connectionTimeoutMillis,
|
|
67
|
-
keepAlive: true,
|
|
68
|
-
keepAliveInitialDelayMillis: 10000,
|
|
69
|
-
ssl: this.config.postgres.ssl,
|
|
70
|
-
statement_timeout: this.config.postgres.statementTimeout,
|
|
71
|
-
query_timeout: this.config.postgres.queryTimeout
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
this.pgPool = new Pool(poolConfig);
|
|
33
|
+
this.rdsDataClient = new RDSDataClient({ region: this.region });
|
|
75
34
|
|
|
76
|
-
this.
|
|
77
|
-
|
|
35
|
+
this.db = getDrizzle(this.rdsDataClient, {
|
|
36
|
+
resourceArn: this.clusterArn,
|
|
37
|
+
secretArn: this.secretArn,
|
|
38
|
+
database: this.databaseName
|
|
78
39
|
});
|
|
79
40
|
|
|
80
|
-
|
|
81
|
-
this.lastActivityAt = Date.now();
|
|
82
|
-
logInfo('pegasus-sdk', 'PostgreSQL client connected');
|
|
83
|
-
});
|
|
41
|
+
logInfo('pegasus-sdk', 'RDS Data API client initialized');
|
|
84
42
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
43
|
+
try {
|
|
44
|
+
const command = new ExecuteStatementCommand({
|
|
45
|
+
resourceArn: this.clusterArn,
|
|
46
|
+
secretArn: this.secretArn,
|
|
47
|
+
database: this.databaseName,
|
|
48
|
+
sql: 'SELECT 1'
|
|
49
|
+
});
|
|
50
|
+
await this.rdsDataClient.send(command);
|
|
51
|
+
logInfo('pegasus-sdk', 'RDS Data API connection verified and ready');
|
|
52
|
+
} catch (err) {
|
|
53
|
+
logError('pegasus-sdk', 'PegasusConnection', 'connect.verification', err);
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
98
56
|
|
|
99
57
|
if (this.openSearchEndpoint) {
|
|
100
|
-
this.osClient = new
|
|
58
|
+
this.osClient = new OpenSearchClient({
|
|
101
59
|
...AwsSigv4Signer({
|
|
102
60
|
region: this.region,
|
|
103
61
|
service: 'aoss',
|
|
@@ -113,77 +71,36 @@ class PegasusConnection {
|
|
|
113
71
|
this.isConnected = true;
|
|
114
72
|
}
|
|
115
73
|
|
|
116
|
-
async reconnect() {
|
|
117
|
-
logInfo('pegasus-sdk', 'Reconnecting PostgreSQL pool (stale connection detected)');
|
|
118
|
-
if (this.pgPool) {
|
|
119
|
-
try {
|
|
120
|
-
await this.pgPool.end();
|
|
121
|
-
} catch (err) {
|
|
122
|
-
logError('pegasus-sdk', 'PegasusConnection', 'reconnect.end', err);
|
|
123
|
-
}
|
|
124
|
-
this.pgPool = null;
|
|
125
|
-
}
|
|
126
|
-
this.isConnected = false;
|
|
127
|
-
this.lastActivityAt = null;
|
|
128
|
-
await this.connect();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
74
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*/
|
|
135
|
-
recordActivity() {
|
|
136
|
-
this.lastActivityAt = Date.now();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Proactively verify the connection is alive before executing a batch of
|
|
141
|
-
* queries. If the pool has been idle long enough that connections may have
|
|
142
|
-
* gone stale, a lightweight SELECT 1 is issued first. If that fails, the
|
|
143
|
-
* pool is rebuilt before the caller's real query fires — avoiding the full
|
|
144
|
-
* connectionTimeoutMillis wait on the real query.
|
|
75
|
+
* With the RDS Data API, there are no stale connections or pool idleness.
|
|
76
|
+
* Each call is stateless. Just ensure the client is initialized.
|
|
145
77
|
*
|
|
146
|
-
* @returns {Promise<boolean>} true if a
|
|
147
|
-
* reset any cached Drizzle db instance), false if connection is healthy.
|
|
78
|
+
* @returns {Promise<boolean>} true if a connect happened, false if already connected.
|
|
148
79
|
*/
|
|
149
80
|
async ensureConnected() {
|
|
150
|
-
if (!this.
|
|
81
|
+
if (!this.isConnected || !this.db) {
|
|
151
82
|
await this.connect();
|
|
152
83
|
return true;
|
|
153
84
|
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
154
87
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Data API is stateless, so reconnect simply means re-initializing.
|
|
90
|
+
* Called in fallback scenarios but not necessary for stale connections.
|
|
91
|
+
*/
|
|
92
|
+
async reconnect() {
|
|
93
|
+
logInfo('pegasus-sdk', 'Reconnecting RDS Data API client');
|
|
94
|
+
this.db = null;
|
|
95
|
+
this.isConnected = false;
|
|
96
|
+
await this.connect();
|
|
97
|
+
}
|
|
163
98
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.pgPool.connect(),
|
|
170
|
-
new Promise((_, reject) =>
|
|
171
|
-
setTimeout(() => reject(new Error('liveness check timeout')), 3000)
|
|
172
|
-
)
|
|
173
|
-
]);
|
|
174
|
-
await client.query('SELECT 1');
|
|
175
|
-
client.release();
|
|
176
|
-
this.lastActivityAt = Date.now();
|
|
177
|
-
logInfo('pegasus-sdk', '[ensureConnected] Liveness check passed');
|
|
178
|
-
return false;
|
|
179
|
-
} catch (err) {
|
|
180
|
-
if (client) {
|
|
181
|
-
try { client.release(true); } catch (_) {}
|
|
182
|
-
}
|
|
183
|
-
logInfo('pegasus-sdk', `[ensureConnected] Liveness check failed (${err.message}) — reconnecting pool proactively`);
|
|
184
|
-
await this.reconnect();
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
99
|
+
/**
|
|
100
|
+
* With stateless Data API, there is no meaningful "activity" tracking needed.
|
|
101
|
+
* This is a no-op for backward compatibility.
|
|
102
|
+
*/
|
|
103
|
+
recordActivity() {
|
|
187
104
|
}
|
|
188
105
|
|
|
189
106
|
async disconnect() {
|
|
@@ -191,21 +108,18 @@ class PegasusConnection {
|
|
|
191
108
|
return;
|
|
192
109
|
}
|
|
193
110
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.pgPool = null;
|
|
197
|
-
}
|
|
198
|
-
|
|
111
|
+
this.db = null;
|
|
112
|
+
this.rdsDataClient = null;
|
|
199
113
|
this.osClient = null;
|
|
200
114
|
this.isConnected = false;
|
|
201
|
-
|
|
115
|
+
logInfo('pegasus-sdk', 'RDS Data API client disconnected');
|
|
202
116
|
}
|
|
203
117
|
|
|
204
118
|
getPostgresClient() {
|
|
205
|
-
if (!this.
|
|
206
|
-
throw new Error('
|
|
119
|
+
if (!this.db) {
|
|
120
|
+
throw new Error('RDS Data API not initialized. Call connect() first.');
|
|
207
121
|
}
|
|
208
|
-
return this.
|
|
122
|
+
return this.db;
|
|
209
123
|
}
|
|
210
124
|
|
|
211
125
|
getOpenSearchClient() {
|
|
@@ -221,54 +135,57 @@ class PegasusConnection {
|
|
|
221
135
|
|
|
222
136
|
async testConnection() {
|
|
223
137
|
try {
|
|
224
|
-
if (this.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
138
|
+
if (!this.db) {
|
|
139
|
+
throw new Error('RDS Data API not initialized');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const command = new ExecuteStatementCommand({
|
|
143
|
+
resourceArn: this.clusterArn,
|
|
144
|
+
secretArn: this.secretArn,
|
|
145
|
+
database: this.databaseName,
|
|
146
|
+
sql: 'SELECT NOW() as current_time, version() as pg_version'
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const result = await this.rdsDataClient.send(command);
|
|
150
|
+
const row = result.records?.[0];
|
|
151
|
+
|
|
152
|
+
const pgStatus = {
|
|
153
|
+
connected: true,
|
|
154
|
+
timestamp: row?.[0]?.stringValue,
|
|
155
|
+
version: row?.[1]?.stringValue
|
|
156
|
+
};
|
|
237
157
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
158
|
+
let osStatus = null;
|
|
159
|
+
if (this.osClient) {
|
|
160
|
+
try {
|
|
161
|
+
const indexName = this.getOpenSearchIndex();
|
|
162
|
+
const testSearch = await this.osClient.search({
|
|
163
|
+
index: indexName,
|
|
164
|
+
body: {
|
|
165
|
+
size: 1,
|
|
166
|
+
query: {
|
|
167
|
+
match: { chemical_name: 'benzene' }
|
|
249
168
|
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
osStatus = {
|
|
172
|
+
connected: true,
|
|
173
|
+
resultsFound: testSearch.body.hits.total.value || 0
|
|
174
|
+
};
|
|
175
|
+
} catch (osError) {
|
|
176
|
+
osStatus = {
|
|
177
|
+
connected: false,
|
|
178
|
+
error: osError.message
|
|
179
|
+
};
|
|
261
180
|
}
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
postgres: pgStatus,
|
|
265
|
-
opensearch: osStatus,
|
|
266
|
-
environment: this.environment,
|
|
267
|
-
region: this.region
|
|
268
|
-
};
|
|
269
181
|
}
|
|
270
|
-
|
|
271
|
-
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
postgres: pgStatus,
|
|
185
|
+
opensearch: osStatus,
|
|
186
|
+
environment: this.environment,
|
|
187
|
+
region: this.region
|
|
188
|
+
};
|
|
272
189
|
} catch (error) {
|
|
273
190
|
return {
|
|
274
191
|
postgres: { connected: false, error: error.message },
|
|
@@ -280,33 +197,34 @@ class PegasusConnection {
|
|
|
280
197
|
}
|
|
281
198
|
|
|
282
199
|
async query(sql, params) {
|
|
283
|
-
const pool = this.getPostgresClient();
|
|
284
200
|
const start = Date.now();
|
|
285
201
|
logInfo('pegasus-sdk', `[SQL] ${sql}${params ? ` -- params: ${JSON.stringify(params)}` : ''}`);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
202
|
+
|
|
203
|
+
const command = new ExecuteStatementCommand({
|
|
204
|
+
resourceArn: this.clusterArn,
|
|
205
|
+
secretArn: this.secretArn,
|
|
206
|
+
database: this.databaseName,
|
|
207
|
+
sql,
|
|
208
|
+
parameters: params || []
|
|
209
|
+
});
|
|
290
210
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
211
|
+
const result = await this.rdsDataClient.send(command);
|
|
212
|
+
logInfo('pegasus-sdk', `[SQL] rowCount: ${result.numberOfRecordsUpdated || 0} duration: ${Date.now() - start}ms`);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
rowCount: result.numberOfRecordsUpdated || result.records?.length || 0,
|
|
216
|
+
rows: result.records || [],
|
|
217
|
+
command: result
|
|
218
|
+
};
|
|
294
219
|
}
|
|
295
220
|
|
|
296
221
|
async transaction(callback) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
try {
|
|
300
|
-
await client.query('BEGIN');
|
|
301
|
-
const result = await callback(client);
|
|
302
|
-
await client.query('COMMIT');
|
|
303
|
-
return result;
|
|
304
|
-
} catch (error) {
|
|
305
|
-
await client.query('ROLLBACK');
|
|
306
|
-
throw error;
|
|
307
|
-
} finally {
|
|
308
|
-
client.release();
|
|
222
|
+
if (!this.db) {
|
|
223
|
+
throw new Error('RDS Data API not initialized. Call connect() first.');
|
|
309
224
|
}
|
|
225
|
+
return await this.db.transaction(async (trx) => {
|
|
226
|
+
return await callback(trx);
|
|
227
|
+
});
|
|
310
228
|
}
|
|
311
229
|
}
|
|
312
230
|
|
package/lib/db/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { drizzle } = require('drizzle-orm/
|
|
1
|
+
const { drizzle } = require('drizzle-orm/aws-data-api/pg');
|
|
2
2
|
const { logInfo } = require('@toxplanet/tphelper/logging');
|
|
3
3
|
const schema = require('./schema');
|
|
4
4
|
|
|
@@ -8,8 +8,8 @@ const logger = {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
function getDrizzle(
|
|
12
|
-
return drizzle(
|
|
11
|
+
function getDrizzle(rdsDataClient, { resourceArn, secretArn, database }) {
|
|
12
|
+
return drizzle(rdsDataClient, { schema, logger, resourceArn, secretArn, database });
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toxplanet/pegasus-sdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.20",
|
|
4
4
|
"description": "SDK for migrating chemical data to Pegasus PostgreSQL + OpenSearch architecture with Elasticsearch client compatibility",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -25,10 +25,9 @@
|
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@toxplanet/tphelper": "1.2.8",
|
|
28
|
-
"pg": "^8.11.3",
|
|
29
28
|
"drizzle-orm": "^0.30.0",
|
|
30
29
|
"@opensearch-project/opensearch": "^2.5.0",
|
|
31
|
-
"@aws-sdk/client-
|
|
30
|
+
"@aws-sdk/client-rds-data": "^3.490.0",
|
|
32
31
|
"@aws-sdk/client-sqs": "^3.490.0",
|
|
33
32
|
"@aws-sdk/credential-providers": "^3.490.0"
|
|
34
33
|
},
|