@dmptool/utils 1.0.4 → 1.0.6

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/dynamo.js CHANGED
@@ -36,12 +36,6 @@ const EXTENSION_KEYS = [
36
36
  'funding_opportunity',
37
37
  'funding_project'
38
38
  ];
39
- const dynamoConfigParams = {
40
- region: process.env.AWS_REGION || 'us-west-2',
41
- maxAttempts: process.env.DYNAMO_MAX_ATTEMPTS
42
- ? parseInt(process.env.DYNAMO_MAX_ATTEMPTS)
43
- : 3,
44
- };
45
39
  class DMPToolDynamoError extends Error {
46
40
  constructor(message) {
47
41
  super(message);
@@ -49,15 +43,22 @@ class DMPToolDynamoError extends Error {
49
43
  }
50
44
  }
51
45
  // Initialize AWS SDK clients (outside the handler function)
52
- const dynamoDBClient = new client_dynamodb_1.DynamoDBClient(dynamoConfigParams);
46
+ const getDynamoDBClient = (dynamoConfigParams) => {
47
+ const { region, maxAttempts } = dynamoConfigParams;
48
+ return new client_dynamodb_1.DynamoDBClient({ region, maxAttempts });
49
+ };
53
50
  /**
54
51
  * Lightweight query just to check if the DMP exists.
55
52
  *
56
- * @param dmpId
53
+ * @param dynamoConnectionParams the DynamoDB connection parameters
54
+ * @param dmpId the DMP ID (e.g. 'doi.org/11.12345/A1B2C3D4')
57
55
  * @returns true if the DMP exists, false otherwise.
58
56
  * @throws DMPToolDynamoError if the record could not be fetched due to an error
59
57
  */
60
- const DMPExists = async (dmpId) => {
58
+ const DMPExists = async (dynamoConnectionParams, dmpId) => {
59
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0) {
60
+ throw new DMPToolDynamoError('Missing Dynamo config or DMP ID');
61
+ }
61
62
  // Very lightweight here, just returning a PK if successful
62
63
  const params = {
63
64
  KeyConditionExpression: "PK = :pk AND SK = :sk",
@@ -67,25 +68,32 @@ const DMPExists = async (dmpId) => {
67
68
  },
68
69
  ProjectExpression: "PK"
69
70
  };
71
+ dynamoConnectionParams.logger.debug(Object.assign(Object.assign({}, params), { dmpId }), 'Checking if DMP exists in DynamoDB');
70
72
  try {
71
- const response = await queryTable(params);
73
+ const response = await queryTable(dynamoConnectionParams, params);
72
74
  return !(0, general_1.isNullOrUndefined)(response)
73
75
  && Array.isArray(response.Items)
74
76
  && response.Items.length > 0;
75
77
  }
76
78
  catch (err) {
77
- throw new DMPToolDynamoError(`Unable to check if DMP exists id: ${dmpId} - ${(0, general_1.toErrorMessage)(err)}`);
79
+ const errMsg = (0, general_1.toErrorMessage)(err);
80
+ dynamoConnectionParams.logger.fatal(Object.assign(Object.assign({}, params), { dmpId, errMsg }), 'Failed to check for DMP existence');
81
+ throw new DMPToolDynamoError(`Unable to check if DMP exists id: ${dmpId} - ${errMsg}`);
78
82
  }
79
83
  };
80
84
  exports.DMPExists = DMPExists;
81
85
  /**
82
86
  * Fetch the version timestamps (including DMP_LATEST_VERSION) for the specified DMP ID.
83
87
  *
84
- * @param dmpId
88
+ * @param dynamoConnectionParams the DynamoDB connection parameters
89
+ * @param dmpId the DMP ID (e.g. 'doi.org/11.12345/A1B2C3D4')
85
90
  * @returns The timestamps as strings (e.g. '2026-11-01T13:08:19Z' or 'latest')
86
91
  * @throws DMPToolDynamoError if the records could not be fetched due to an error
87
92
  */
88
- const getDMPVersions = async (dmpId) => {
93
+ const getDMPVersions = async (dynamoConnectionParams, dmpId) => {
94
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0) {
95
+ throw new DMPToolDynamoError('Missing Dynamo Config or DMP ID');
96
+ }
89
97
  const params = {
90
98
  KeyConditionExpression: "PK = :pk AND begins_with(SK, :sk)",
91
99
  ExpressionAttributeValues: {
@@ -94,8 +102,9 @@ const getDMPVersions = async (dmpId) => {
94
102
  },
95
103
  ProjectionExpression: "PK, SK, modified"
96
104
  };
105
+ dynamoConnectionParams.logger.debug(Object.assign(Object.assign({}, params), { dmpId }), 'Fetching DMP versions from DynamoDB');
97
106
  try {
98
- const response = await queryTable(params);
107
+ const response = await queryTable(dynamoConnectionParams, params);
99
108
  if (Array.isArray(response.Items) && response.Items.length > 0) {
100
109
  const versions = [];
101
110
  for (const item of response.Items) {
@@ -113,7 +122,9 @@ const getDMPVersions = async (dmpId) => {
113
122
  return [];
114
123
  }
115
124
  catch (err) {
116
- throw new DMPToolDynamoError(`Unable to fetch DMP versions id: ${dmpId} - ${(0, general_1.toErrorMessage)(err)}`);
125
+ const errMsg = (0, general_1.toErrorMessage)(err);
126
+ dynamoConnectionParams.logger.fatal(Object.assign(Object.assign({}, params), { dmpId, errMsg }), 'Failed to fetch DMP versions');
127
+ throw new DMPToolDynamoError(`Unable to fetch DMP versions id: ${dmpId} - ${errMsg}`);
117
128
  }
118
129
  };
119
130
  exports.getDMPVersions = getDMPVersions;
@@ -121,7 +132,9 @@ exports.getDMPVersions = getDMPVersions;
121
132
  * Fetch the RDA Common Standard metadata record with DMP Tool specific extensions
122
133
  * for the specified DMP ID.
123
134
  *
124
- * @param dmpId
135
+ * @param dynamoConnectionParams the DynamoDB connection parameters
136
+ * @param domainName The domain name of the DMPTool instance (e.g. 'dmptool.org')
137
+ * @param dmpId the DMP ID (e.g. 'doi.org/11.12345/A1B2C3D4')
125
138
  * @param version The version of the DMP metadata record to persist
126
139
  * (e.g. '2026-11-01T13:08:19Z').
127
140
  * If not provided, the latest version will be used. Defaults to DMP_LATEST_VERSION.
@@ -134,7 +147,10 @@ exports.getDMPVersions = getDMPVersions;
134
147
  // Fetch the specified DMP metadata record
135
148
  // - Version is optional, if it is not provided, ALL versions will be returned
136
149
  // - If you just want the latest version, use the DMP_LATEST_VERSION constant
137
- const getDMPs = async (dmpId, version, includeExtensions = true) => {
150
+ const getDMPs = async (dynamoConnectionParams, domainName, dmpId, version, includeExtensions = true) => {
151
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0) {
152
+ throw new DMPToolDynamoError('Missing Dynamo config or DMP ID');
153
+ }
138
154
  let params = {};
139
155
  if (version) {
140
156
  params = {
@@ -154,8 +170,9 @@ const getDMPs = async (dmpId, version, includeExtensions = true) => {
154
170
  }
155
171
  };
156
172
  }
173
+ dynamoConnectionParams.logger.debug(Object.assign(Object.assign({}, params), { dmpId, version, includeExtensions }), 'Fetching DMPs from DynamoDB');
157
174
  try {
158
- const response = await queryTable(params);
175
+ const response = await queryTable(dynamoConnectionParams, params);
159
176
  if (response && response.Items && response.Items.length > 0) {
160
177
  const unmarshalled = response.Items.map(item => (0, util_dynamodb_1.unmarshall)(item));
161
178
  // sort the results by the SK (version) descending
@@ -168,7 +185,7 @@ const getDMPs = async (dmpId, version, includeExtensions = true) => {
168
185
  // merge in any DMP Tool specific extensions to the RDA Common Standard
169
186
  return await Promise.all(items.map(async (item) => {
170
187
  // Fetch the DMP Tool extensions
171
- const extensions = await getDMPExtensions(dmpId, item.SK.replace(`${DMP_VERSION_PREFIX}#`, ''));
188
+ const extensions = await getDMPExtensions(dynamoConnectionParams, domainName, dmpId, item.SK.replace(`${DMP_VERSION_PREFIX}#`, ''));
172
189
  // Destructure the Dynamo item because we don't need to return the PK and SK
173
190
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
174
191
  const { PK, SK } = item, version = __rest(item, ["PK", "SK"]);
@@ -189,7 +206,9 @@ const getDMPs = async (dmpId, version, includeExtensions = true) => {
189
206
  }
190
207
  }
191
208
  catch (err) {
192
- throw new DMPToolDynamoError(`Unable to fetch DMP id: ${dmpId}, ver: ${version} - ${(0, general_1.toErrorMessage)(err)}`);
209
+ const errMsg = (0, general_1.toErrorMessage)(err);
210
+ dynamoConnectionParams.logger.fatal(Object.assign(Object.assign({}, params), { dmpId, version, includeExtensions, errMsg }), errMsg);
211
+ throw new DMPToolDynamoError(`Unable to fetch DMP id: ${dmpId}, ver: ${version} - ${errMsg}`);
193
212
  }
194
213
  return [];
195
214
  };
@@ -197,14 +216,18 @@ exports.getDMPs = getDMPs;
197
216
  /**
198
217
  * Fetch the specified DMP Extensions metadata record
199
218
  *
200
- * @param dmpId
219
+ * @param dynamoConnectionParams the DynamoDB connection parameters
220
+ * @param dmpId the DMP ID (e.g. 'doi.org/11.12345/A1B2C3D4')
201
221
  * @param version The version of the DMP metadata record to persist
202
222
  * (e.g. '2026-11-01T13:08:19Z').
203
223
  * If not provided, the latest version will be used. Defaults to DMP_LATEST_VERSION.
204
224
  * @returns The DMP extension metadata records or an empty array if none were found.
205
225
  * @throws DMPToolDynamoError if the record could not be fetched
206
226
  */
207
- const getDMPExtensions = async (dmpId, version) => {
227
+ const getDMPExtensions = async (dynamoConnectionParams, domainName, dmpId, version) => {
228
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0) {
229
+ throw new DMPToolDynamoError('Missing Dynamo Config or DMP ID');
230
+ }
208
231
  let params = {};
209
232
  if (version) {
210
233
  params = {
@@ -224,7 +247,8 @@ const getDMPExtensions = async (dmpId, version) => {
224
247
  }
225
248
  };
226
249
  }
227
- const response = await queryTable(params);
250
+ dynamoConnectionParams.logger.debug(Object.assign(Object.assign({}, params), { dmpId, version }), 'Fetching DMP Extensions from DynamoDB');
251
+ const response = await queryTable(dynamoConnectionParams, params);
228
252
  if (response && response.Items && response.Items.length > 0) {
229
253
  const unmarshalled = response.Items.map(item => (0, util_dynamodb_1.unmarshall)(item));
230
254
  // sort the results by the SK (version) descending
@@ -237,7 +261,7 @@ const getDMPExtensions = async (dmpId, version) => {
237
261
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
238
262
  const { PK, SK } = item, extension = __rest(item, ["PK", "SK"]);
239
263
  // Fetch all the version timestamps
240
- const versions = await (0, exports.getDMPVersions)(dmpId);
264
+ const versions = await (0, exports.getDMPVersions)(dynamoConnectionParams, dmpId);
241
265
  if (Array.isArray(versions) && versions.length > 0) {
242
266
  // Return the versions sorted descending
243
267
  extension.version = versions
@@ -248,7 +272,7 @@ const getDMPExtensions = async (dmpId, version) => {
248
272
  ? ''
249
273
  : `?version=${v.modified}`;
250
274
  const dmpIdWithoutProtocol = dmpId.replace(/^https?:\/\//, '');
251
- const accessURLBase = `https://${process.env.DOMAIN_NAME}/dmps/`;
275
+ const accessURLBase = `https://${domainName}/dmps/`;
252
276
  return {
253
277
  access_url: `${accessURLBase}${dmpIdWithoutProtocol}${queryParam}`,
254
278
  version: v.modified,
@@ -265,7 +289,9 @@ const getDMPExtensions = async (dmpId, version) => {
265
289
  * This function will handle the separation of RDA Common Standard and DMP Tool
266
290
  * specific metadata.
267
291
  *
268
- * @param dmpId The DMP ID (e.g. '123456789')
292
+ * @param dynamoConnectionParams The DynamoDB connection parameters
293
+ * @param domainName The domain name of the DMPTool instance (e.g. 'dmptool.org')
294
+ * @param dmpId the DMP ID (e.g. 'doi.org/11.12345/A1B2C3D4')
269
295
  * @param dmp The DMP metadata record to persist as either an RDA Common Standard
270
296
  * or the standard with DMP Tool specific extensions.
271
297
  * @param version The version of the DMP metadata record to persist
@@ -277,14 +303,15 @@ const getDMPExtensions = async (dmpId, version) => {
277
303
  * metadata record with the DMP Tool specific extensions merged in.
278
304
  * @throws DMPToolDynamoError if the record could not be persisted
279
305
  */
280
- const createDMP = async (dmpId, dmp, version = exports.DMP_LATEST_VERSION, includeExtensions = true) => {
306
+ const createDMP = async (dynamoConnectionParams, domainName, dmpId, dmp, version = exports.DMP_LATEST_VERSION, includeExtensions = true) => {
281
307
  var _a;
282
- if (!dmpId || dmpId.trim().length === 0 || !dmp) {
283
- throw new DMPToolDynamoError('Missing DMP ID or DMP metadata record');
308
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0 || !dmp) {
309
+ throw new DMPToolDynamoError('Missing Dynamo config, DMP ID or DMP metadata record');
284
310
  }
285
311
  // If the version is LATEST, then first make sure there is not already one present!
286
- const exists = await (0, exports.DMPExists)(dmpId);
312
+ const exists = await (0, exports.DMPExists)(dynamoConnectionParams, dmpId);
287
313
  if (exists) {
314
+ dynamoConnectionParams.logger.error({ dmpId }, 'Latest version already exists');
288
315
  throw new DMPToolDynamoError('Latest version already exists');
289
316
  }
290
317
  try {
@@ -294,37 +321,45 @@ const createDMP = async (dmpId, dmp, version = exports.DMP_LATEST_VERSION, inclu
294
321
  const dmptoolExtension = pick(innerMetadata, [...EXTENSION_KEYS]);
295
322
  const rdaCommonStandard = pick(innerMetadata, Object.keys(innerMetadata).filter(k => !EXTENSION_KEYS.includes(k)));
296
323
  const newVersionItem = Object.assign(Object.assign({}, rdaCommonStandard), { PK: dmpIdToPK(dmpId), SK: versionToSK(version) });
324
+ dynamoConnectionParams.logger.debug({ dmpId, version, includeExtensions }, 'Persisting DMP to DynamoDB');
297
325
  // Insert the RDA Common Standard metadata record into the DynamoDB table
298
- await putItem((0, util_dynamodb_1.marshall)(newVersionItem, { removeUndefinedValues: true }));
326
+ await putItem(dynamoConnectionParams, (0, util_dynamodb_1.marshall)(newVersionItem, { removeUndefinedValues: true }));
299
327
  // Create the DMP Tool extensions metadata record. We ALWAYS do this even if
300
328
  // the caller does not want them returned
301
- await createDMPExtensions(dmpId, dmptoolExtension, version);
329
+ await createDMPExtensions(dynamoConnectionParams, dmpId, dmptoolExtension, version);
302
330
  // Fetch the complete DMP metadata record including the RDA Common Standard
303
331
  // and the DMP Tool extensions
304
- return (await (0, exports.getDMPs)(dmpId, exports.DMP_LATEST_VERSION, includeExtensions))[0];
332
+ return (await (0, exports.getDMPs)(dynamoConnectionParams, domainName, dmpId, exports.DMP_LATEST_VERSION, includeExtensions))[0];
305
333
  }
306
334
  catch (err) {
307
335
  // If it was a DMPToolDynamoError that bubbled up, just throw it
308
336
  if (err instanceof DMPToolDynamoError)
309
337
  throw err;
310
- throw new DMPToolDynamoError(`Unable to create DMP id: ${dmpId}, ver: ${version} - ${(0, general_1.toErrorMessage)(err)}`);
338
+ const errMsg = (0, general_1.toErrorMessage)(err);
339
+ dynamoConnectionParams.logger.fatal({ dmpId, version, includeExtensions }, errMsg);
340
+ throw new DMPToolDynamoError(`Unable to create DMP id: ${dmpId}, ver: ${version} - ${errMsg}`);
311
341
  }
312
342
  };
313
343
  exports.createDMP = createDMP;
314
344
  /**
315
345
  * Create a new DMP Extensions metadata record
316
346
  *
317
- * @param dmpId
318
- * @param dmp
347
+ * @param dynamoConnectionParams The DynamoDB connection parameters
348
+ * @param dmpId the DMP ID (e.g. 'doi.org/11.12345/A1B2C3D4')
349
+ * @param dmp The DMP Tool extensions metadata record
319
350
  * @param version The version of the DMP metadata record to persist
320
351
  * (e.g. '2026-11-01T13:08:19Z').
321
352
  * If not provided, the latest version will be used. Defaults to DMP_LATEST_VERSION.
322
353
  * @returns The persisted DMP Tool extensions metadata record.
323
354
  */
324
- const createDMPExtensions = async (dmpId, dmp, version = exports.DMP_LATEST_VERSION) => {
355
+ const createDMPExtensions = async (dynamoConnectionParams, dmpId, dmp, version = exports.DMP_LATEST_VERSION) => {
356
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0 || !dmp) {
357
+ throw new DMPToolDynamoError('Missing Dynamo config, DMP ID or DMP metadata record');
358
+ }
325
359
  const newExtensionItem = Object.assign(Object.assign({}, dmp), { PK: dmpIdToPK(dmpId), SK: versionToExtensionSK(version) });
360
+ dynamoConnectionParams.logger.debug({ dmpId, version }, 'Persisting DMP Extensions to DynamoDB');
326
361
  // Insert the DMP Tool Extensions metadata record into the DynamoDB table
327
- await putItem((0, util_dynamodb_1.marshall)(newExtensionItem, { removeUndefinedValues: true }));
362
+ await putItem(dynamoConnectionParams, (0, util_dynamodb_1.marshall)(newExtensionItem, { removeUndefinedValues: true }));
328
363
  };
329
364
  /**
330
365
  * Update the specified DMP metadata record.
@@ -341,18 +376,23 @@ const createDMPExtensions = async (dmpId, dmp, version = exports.DMP_LATEST_VERS
341
376
  * If a snapshot is made, the timestamp and link to retrieve it will appear
342
377
  * in the `versions` array
343
378
  *
344
- * @param dmp
379
+ * @param dynamoConnectionParams the DynamoDB connection parameters
380
+ * @param domainName The domain name of the DMPTool instance (e.g. 'dmptool.org')
381
+ * @param dmp The DMP metadata record to persist as either an RDA Common Standard
382
+ * or the standard with DMP Tool specific extensions.
383
+ * @param gracePeriodInMS The grace period in milliseconds to wait before creating
345
384
  * @param includeExtensions Whether or not to include the DMP Tool specific
346
385
  * extensions in the returned record. Defaults to true.
347
386
  * @returns The persisted DMP metadata record as an RDA Common Standard DMP
348
387
  * metadata record with the DMP Tool specific extensions merged in.
349
388
  * @throws DMPToolDynamoError if the record could not be persisted
350
389
  */
351
- const updateDMP = async (dmp, includeExtensions = true) => {
390
+ const updateDMP = async (dynamoConnectionParams, domainName, dmp, gracePeriodInMS = 7200000, // 2 hours in milliseconds
391
+ includeExtensions = true) => {
352
392
  var _a, _b, _c, _d, _e, _f;
353
393
  const dmpId = (_b = (_a = dmp.dmp) === null || _a === void 0 ? void 0 : _a.dmp_id) === null || _b === void 0 ? void 0 : _b.identifier;
354
- if (!dmp || !dmpId) {
355
- throw new DMPToolDynamoError('Missing DMP ID or DMP metadata record');
394
+ if (!dynamoConnectionParams || !dmp || !dmpId) {
395
+ throw new DMPToolDynamoError('Missing Dynamo config, DMP ID or DMP metadata record');
356
396
  }
357
397
  try {
358
398
  // If the metadata is nested in a top level 'dmp' property, then unwrap it
@@ -362,7 +402,7 @@ const updateDMP = async (dmp, includeExtensions = true) => {
362
402
  const rdaCommonStandard = pick(innerMetadata, Object.keys(innerMetadata).filter(k => !EXTENSION_KEYS.includes(k)));
363
403
  // Fetch the current latest version of the plan's maDMP record. Always get
364
404
  // the extensions because we need to check the provenance
365
- const latest = (await (0, exports.getDMPs)(dmpId, exports.DMP_LATEST_VERSION, true))[0];
405
+ const latest = (await (0, exports.getDMPs)(dynamoConnectionParams, domainName, dmpId, exports.DMP_LATEST_VERSION, true))[0];
366
406
  // Bail if there is no latest version (it has never been created yet or its tombstoned)
367
407
  // Or if the incoming modified timestamp is newer than the latest version's
368
408
  // modified timestamp (collision)
@@ -373,36 +413,38 @@ const updateDMP = async (dmp, includeExtensions = true) => {
373
413
  }
374
414
  const lastModified = new Date((_f = latest.dmp) === null || _f === void 0 ? void 0 : _f.modified).getTime();
375
415
  const now = Date.now();
376
- const gracePeriod = process.env.VERSION_GRACE_PERIOD
377
- ? Number(process.env.VERSION_GRACE_PERIOD)
378
- : 7200000; // 2 hours in milliseconds
416
+ const gracePeriod = gracePeriodInMS ? Number(gracePeriodInMS) : 7200000;
379
417
  // We need to version the DMP if the provenance doesn't match or the modified
380
418
  // timestamp is older than 2 hours ago
381
419
  const needToVersion = dmptoolExtension.provenance !== latest.dmp.provenance
382
420
  || (now - lastModified) > gracePeriod;
421
+ dynamoConnectionParams.logger.debug({ dmpId, lastModified, now, gracePeriod, needToVersion }, 'Determining if we need to version the DMP');
383
422
  // If it was determined that we need to version the DMP, then create a new snapshot
384
423
  // using the modified date of the current latest version
385
424
  if (needToVersion) {
386
- await (0, exports.createDMP)(dmpId, latest.dmp, latest.dmp.modified);
425
+ await (0, exports.createDMP)(dynamoConnectionParams, domainName, dmpId, latest.dmp, latest.dmp.modified);
387
426
  }
388
427
  // Updates can only ever occur on the latest version of the DMP (the Plan logic
389
428
  // should handle creating a snapshot of the original version of the DMP when
390
429
  // appropriate)
391
430
  const versionItem = Object.assign(Object.assign({}, rdaCommonStandard), { PK: dmpIdToPK(dmpId), SK: versionToSK(exports.DMP_LATEST_VERSION) });
431
+ dynamoConnectionParams.logger.debug({ dmpId, versionItem }, 'Persisting DMP to DynamoDB');
392
432
  // Insert the RDA Common Standard metadata record into the DynamoDB table
393
- await putItem((0, util_dynamodb_1.marshall)(versionItem, { removeUndefinedValues: true }));
433
+ await putItem(dynamoConnectionParams, (0, util_dynamodb_1.marshall)(versionItem, { removeUndefinedValues: true }));
394
434
  // Update the DMP Tool extensions metadata record. We ALWAYS do this even if
395
435
  // the caller does not want them returned
396
- await updateDMPExtensions(dmpId, dmptoolExtension);
436
+ await updateDMPExtensions(dynamoConnectionParams, dmpId, dmptoolExtension);
397
437
  // Fetch the complete DMP metadata record including the RDA Common Standard
398
438
  // and the DMP Tool extensions
399
- return (await (0, exports.getDMPs)(dmpId, exports.DMP_LATEST_VERSION, includeExtensions))[0];
439
+ return (await (0, exports.getDMPs)(dynamoConnectionParams, domainName, dmpId, exports.DMP_LATEST_VERSION, includeExtensions))[0];
400
440
  }
401
441
  catch (err) {
402
442
  // If it was a DMPToolDynamoError that bubbled up, just throw it
403
443
  if (err instanceof DMPToolDynamoError)
404
444
  throw err;
405
- throw new DMPToolDynamoError(`Unable to create DMP id: ${dmpId}, ver: ${exports.DMP_LATEST_VERSION} - ${(0, general_1.toErrorMessage)(err)}`);
445
+ const errMsg = (0, general_1.toErrorMessage)(err);
446
+ dynamoConnectionParams.logger.fatal({ dmpId, includeExtensions, errMsg }, 'Unable to update DMP -');
447
+ throw new DMPToolDynamoError(`Unable to update DMP id: ${dmpId}, ver: ${exports.DMP_LATEST_VERSION} - ${errMsg}`);
406
448
  }
407
449
  };
408
450
  exports.updateDMP = updateDMP;
@@ -410,20 +452,27 @@ exports.updateDMP = updateDMP;
410
452
  * Update the specified DMP Extensions metadata record
411
453
  * We always update the latest version of the DMP metadata record. Historical versions are immutable.
412
454
  *
413
- * @param dmpId
455
+ * @param dynamoConnectionParams The DynamoDB connection parameters
456
+ * @param dmpId the DMP ID (e.g. 'doi.org/11.12345/A1B2C3D4')
414
457
  * @param dmp
415
458
  * @returns The persisted DMP Tool extensions metadata record.
416
459
  */
417
- const updateDMPExtensions = async (dmpId, dmp) => {
460
+ const updateDMPExtensions = async (dynamoConnectionParams, dmpId, dmp) => {
461
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0 || !dmp) {
462
+ throw new DMPToolDynamoError('Missing Dynamo config, DMP ID or DMP metadata record');
463
+ }
418
464
  // Updates can only ever occur on the latest version of the DMP (the Plan logic
419
465
  // should handle creating a snapshot of the original version of the DMP when appropriate)
420
466
  const extensionItem = Object.assign(Object.assign({}, dmp), { PK: dmpIdToPK(dmpId), SK: versionToExtensionSK(exports.DMP_LATEST_VERSION) });
421
- await putItem((0, util_dynamodb_1.marshall)(extensionItem, { removeUndefinedValues: true }));
467
+ dynamoConnectionParams.logger.debug({ dmpId }, 'Persisting DMP Extensions to DynamoDB');
468
+ await putItem(dynamoConnectionParams, (0, util_dynamodb_1.marshall)(extensionItem, { removeUndefinedValues: true }));
422
469
  };
423
470
  /**
424
471
  * Create a Tombstone for the specified DMP metadata record
425
472
  * (registered/published DMPs only!)
426
473
  *
474
+ * @param dynamoConnectionParams The DynamoDB connection parameters
475
+ * @param domainName The domain name of the DMPTool instance (e.g. 'dmptool.org')
427
476
  * @param dmpId The DMP ID (e.g. '11.12345/A1B2C3')
428
477
  * @param includeExtensions Whether or not to include the DMP Tool specific
429
478
  * extensions in the returned record. Defaults to true.
@@ -431,12 +480,16 @@ const updateDMPExtensions = async (dmpId, dmp) => {
431
480
  * metadata record with the DMP Tool specific extensions merged in.
432
481
  * @throws DMPToolDynamoError if a tombstone could not be created
433
482
  */
434
- const tombstoneDMP = async (dmpId, includeExtensions = true) => {
483
+ const tombstoneDMP = async (dynamoConnectionParams, domainName, dmpId, includeExtensions = true) => {
435
484
  var _a, _b;
485
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0) {
486
+ throw new DMPToolDynamoError('Missing Dynamo config or DMP ID');
487
+ }
436
488
  // Get the latest version of the DMP including the extensions because we need
437
489
  // to check the registered status
438
- const dmp = (await (0, exports.getDMPs)(dmpId, exports.DMP_LATEST_VERSION, true))[0];
490
+ const dmp = (await (0, exports.getDMPs)(dynamoConnectionParams, domainName, dmpId, exports.DMP_LATEST_VERSION, true))[0];
439
491
  if (!dmp) {
492
+ dynamoConnectionParams.logger.error({ dmpId }, 'Unable to find current latest version of the DMP');
440
493
  throw new DMPToolDynamoError(`Unable to find DMP id: ${dmpId}, ver: ${exports.DMP_LATEST_VERSION}`);
441
494
  }
442
495
  // If the DMP has been registered (aka published) we can create a tombstone
@@ -448,30 +501,34 @@ const tombstoneDMP = async (dmpId, includeExtensions = true) => {
448
501
  const rdaCommonStandard = pick(innerMetadata, Object.keys(innerMetadata).filter(k => !EXTENSION_KEYS.includes(k)));
449
502
  const now = (0, general_1.convertMySQLDateTimeToRFC3339)(new Date());
450
503
  if ((0, general_1.isNullOrUndefined)(now)) {
504
+ dynamoConnectionParams.logger.error({ dmpId }, 'Unable to create modified date');
451
505
  throw new DMPToolDynamoError('Unable to create modified date');
452
506
  }
453
507
  const versionItem = Object.assign(Object.assign({}, rdaCommonStandard), { PK: dmpIdToPK(dmpId), SK: versionToSK(exports.DMP_TOMBSTONE_VERSION), title: `OBSOLETE: ${(_b = dmp.dmp) === null || _b === void 0 ? void 0 : _b.title}`, modified: now });
454
508
  try {
455
509
  // Update the RDA Common Standard metadata record
456
- await putItem((0, util_dynamodb_1.marshall)(versionItem, { removeUndefinedValues: true }));
457
- await deleteItem({
510
+ await putItem(dynamoConnectionParams, (0, util_dynamodb_1.marshall)(versionItem, { removeUndefinedValues: true }));
511
+ await deleteItem(dynamoConnectionParams, {
458
512
  PK: { S: dmpIdToPK(dmpId) },
459
513
  SK: { S: versionToSK(exports.DMP_LATEST_VERSION) }
460
514
  });
461
515
  // Tombstone the DMP Tool Extensions metadata record. We ALWAYS do this even
462
516
  // if the caller does not want them returned
463
- await tombstoneDMPExtensions(dmpId, dmptoolExtension);
517
+ await tombstoneDMPExtensions(dynamoConnectionParams, dmpId, dmptoolExtension);
464
518
  // Fetch the complete DMP metadata record including the RDA Common Standard
465
519
  // and the DMP Tool extensions
466
- return (await (0, exports.getDMPs)(dmpId, exports.DMP_TOMBSTONE_VERSION, includeExtensions))[0];
520
+ return (await (0, exports.getDMPs)(dynamoConnectionParams, domainName, dmpId, exports.DMP_TOMBSTONE_VERSION, includeExtensions))[0];
467
521
  }
468
522
  catch (err) {
469
523
  if (err instanceof DMPToolDynamoError)
470
524
  throw err;
471
- throw new DMPToolDynamoError(`Unable to tombstone id: ${dmpId}, ver: ${exports.DMP_LATEST_VERSION} - ${(0, general_1.toErrorMessage)(err)}`);
525
+ const errMsg = (0, general_1.toErrorMessage)(err);
526
+ dynamoConnectionParams.logger.fatal({ dmpId, errMsg }, 'Unable to tombstone DMP');
527
+ throw new DMPToolDynamoError(`Unable to tombstone id: ${dmpId}, ver: ${exports.DMP_LATEST_VERSION} - ${errMsg}`);
472
528
  }
473
529
  }
474
530
  else {
531
+ dynamoConnectionParams.logger.warn({ dmpId }, 'Unable to tombstone an unregistered DMP');
475
532
  throw new DMPToolDynamoError(`Unable to tombstone DMP id: ${dmpId} because it is not registered/published`);
476
533
  }
477
534
  };
@@ -480,20 +537,26 @@ exports.tombstoneDMP = tombstoneDMP;
480
537
  * Add a tombstone date to the specified DMP Extensions metadata record
481
538
  * (registered/published DMPs only!)
482
539
  *
540
+ * @param dynamoConnectionParams The DynamoDB connection parameters
483
541
  * @param dmpId The DMP ID (e.g. '11.12345/A1B2C3')
484
542
  * @param dmp The DMP Tool specific extensions record to update.
485
543
  * @throws DMPToolDynamoError if the tombstone date could not be added
486
544
  */
487
- const tombstoneDMPExtensions = async (dmpId, dmp) => {
545
+ const tombstoneDMPExtensions = async (dynamoConnectionParams, dmpId, dmp) => {
546
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0 || !dmp) {
547
+ throw new DMPToolDynamoError('Missing Dynamo config, DMP ID or DMP metadata record');
548
+ }
488
549
  const now = (0, general_1.convertMySQLDateTimeToRFC3339)(new Date());
489
550
  if (!now) {
551
+ dynamoConnectionParams.logger.error({ dmpId }, 'Unable to create tombstone date');
490
552
  throw new DMPToolDynamoError('Unable to create tombstone date');
491
553
  }
492
554
  const extensionItem = Object.assign(Object.assign({}, dmp), { PK: dmpIdToPK(dmpId), SK: versionToExtensionSK(exports.DMP_TOMBSTONE_VERSION), tombstoned: now });
555
+ dynamoConnectionParams.logger.debug({ dmpId }, 'Tombstoning DMP Extensions in DynamoDB');
493
556
  // Update the DMP Tool Extensions metadata record
494
- await putItem((0, util_dynamodb_1.marshall)(extensionItem, { removeUndefinedValues: true }));
557
+ await putItem(dynamoConnectionParams, (0, util_dynamodb_1.marshall)(extensionItem, { removeUndefinedValues: true }));
495
558
  // Then delete the old latest version
496
- await deleteItem({
559
+ await deleteItem(dynamoConnectionParams, {
497
560
  PK: { S: dmpIdToPK(dmpId) },
498
561
  SK: { S: versionToExtensionSK(exports.DMP_LATEST_VERSION) }
499
562
  });
@@ -502,6 +565,8 @@ const tombstoneDMPExtensions = async (dmpId, dmp) => {
502
565
  * Delete the specified DMP metadata record and any associated DMP Tool extension records.
503
566
  * This will NOT work on DMPs that have been registered/published.
504
567
  *
568
+ * @param dynamoConnectionParams The DynamoDB connection parameters
569
+ * @param domainName The domain name of the DMPTool instance (e.g. 'dmptool.org')
505
570
  * @param dmpId The DMP ID (e.g. '11.12345/A1B2C3')
506
571
  * @param includeExtensions Whether or not to include the DMP Tool specific extensions
507
572
  * in the returned record. Defaults to true.
@@ -509,10 +574,13 @@ const tombstoneDMPExtensions = async (dmpId, dmp) => {
509
574
  * record with the DMP Tool specific extensions merged in.
510
575
  * @throws DMPToolDynamoError if the record could not be deleted
511
576
  */
512
- const deleteDMP = async (dmpId, includeExtensions = true) => {
577
+ const deleteDMP = async (dynamoConnectionParams, domainName, dmpId, includeExtensions = true) => {
578
+ if (!dynamoConnectionParams || !dmpId || dmpId.trim().length === 0) {
579
+ throw new DMPToolDynamoError('Missing Dynamo config or DMP ID');
580
+ }
513
581
  // Get the latest version of the DMP. Always get the extensions because we need
514
582
  // to check the registered status
515
- const dmps = await (0, exports.getDMPs)(dmpId, exports.DMP_LATEST_VERSION, true);
583
+ const dmps = await (0, exports.getDMPs)(dynamoConnectionParams, domainName, dmpId, exports.DMP_LATEST_VERSION, true);
516
584
  if (Array.isArray(dmps) && dmps.length > 0) {
517
585
  const latest = dmps[0];
518
586
  // If the caller wants just the RDA Common Standard metadata, then reload the
@@ -524,45 +592,63 @@ const deleteDMP = async (dmpId, includeExtensions = true) => {
524
592
  // If the latest version was found, and it has NOT been registered/published
525
593
  if (latest && !latest.dmp.registered) {
526
594
  try {
595
+ dynamoConnectionParams.logger.debug({ dmpId }, 'Deleting DMP from DynamoDB');
527
596
  // Delete all records with that DMP ID
528
- await deleteItem({ PK: { S: dmpIdToPK(dmpId) } });
597
+ await deleteItem(dynamoConnectionParams, { PK: { S: dmpIdToPK(dmpId) } });
529
598
  return toReturn;
530
599
  }
531
600
  catch (err) {
532
- throw new DMPToolDynamoError(`Unable to delete id: ${dmpId}, ver: ${exports.DMP_LATEST_VERSION} - ${(0, general_1.toErrorMessage)(err)}`);
601
+ const errMsg = (0, general_1.toErrorMessage)(err);
602
+ dynamoConnectionParams.logger.fatal({ dmpId, errMsg }, 'Unable to delete DMP');
603
+ throw new DMPToolDynamoError(`Unable to delete id: ${dmpId}, ver: ${exports.DMP_LATEST_VERSION} - ${errMsg}`);
533
604
  }
534
605
  }
535
606
  else {
607
+ dynamoConnectionParams.logger.error({ dmpId }, 'Unable to delete an unregistered DMP');
536
608
  throw new DMPToolDynamoError(`Unable to delete id: ${dmpId} because it does not exist or is registered`);
537
609
  }
538
610
  }
539
611
  else {
612
+ dynamoConnectionParams.logger.error({ dmpId }, 'Unable to find current latest version of the DMP');
540
613
  throw new DMPToolDynamoError(`Unable to find id: ${dmpId}, ver: ${exports.DMP_LATEST_VERSION}`);
541
614
  }
542
615
  };
543
616
  exports.deleteDMP = deleteDMP;
544
617
  /**
545
618
  * Scan the specified DynamoDB table using the specified criteria
546
- * @param table
547
- * @param params
619
+ *
620
+ * @param dynamoConnectionParams the DynamoDB connection parameters
621
+ * @param params the query parameters
548
622
  * @returns an array of DynamoDB items
549
623
  */
550
624
  // We're not currently using it, but did not want to remove it just in case
551
625
  // we need it in the future
552
626
  //
553
627
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
554
- const scanTable = async (table, params) => {
628
+ const scanTable = async (dynamoConnectionParams, params) => {
629
+ if (!dynamoConnectionParams || !params) {
630
+ throw new DMPToolDynamoError('Missing Dynamo config or params');
631
+ }
555
632
  let items = [];
556
633
  let lastEvaluatedKey;
557
634
  // Query the DynamoDB index table for all DMP metadata (with pagination)
558
635
  do {
559
- const command = new client_dynamodb_1.ScanCommand(Object.assign({ TableName: table, ExclusiveStartKey: lastEvaluatedKey, ConsistentRead: false, ReturnConsumedCapacity: 'TOTAL' }, params));
560
- const response = await dynamoDBClient.send(command);
561
- // Collect items and update the pagination key
562
- items = items.concat(response.Items || []);
563
- // LastEvaluatedKey is the position of the end cursor from the query that was just run
564
- // when it is undefined, then the query reached the end of the results.
565
- lastEvaluatedKey = response === null || response === void 0 ? void 0 : response.LastEvaluatedKey;
636
+ const command = new client_dynamodb_1.ScanCommand(Object.assign({ TableName: dynamoConnectionParams.tableName, ExclusiveStartKey: lastEvaluatedKey, ConsistentRead: false, ReturnConsumedCapacity: 'TOTAL' }, params));
637
+ try {
638
+ const dynamoDBClient = getDynamoDBClient(dynamoConnectionParams);
639
+ dynamoConnectionParams.logger.debug({ table: dynamoConnectionParams.tableName, params }, 'Scanning DynamoDB table');
640
+ const response = await dynamoDBClient.send(command);
641
+ // Collect items and update the pagination key
642
+ items = items.concat(response.Items || []);
643
+ // LastEvaluatedKey is the position of the end cursor from the query that was just run
644
+ // when it is undefined, then the query reached the end of the results.
645
+ lastEvaluatedKey = response === null || response === void 0 ? void 0 : response.LastEvaluatedKey;
646
+ }
647
+ catch (error) {
648
+ const errMsg = (0, general_1.toErrorMessage)(error);
649
+ dynamoConnectionParams.logger.fatal(Object.assign(Object.assign(Object.assign({}, params), dynamoConnectionParams), { errMsg }), 'Unable to scan DynamoDB table');
650
+ throw new DMPToolDynamoError(`Unable to scan DynamoDB table - ${errMsg}`);
651
+ }
566
652
  } while (lastEvaluatedKey);
567
653
  // Deserialize and split items into multiple files if necessary
568
654
  return items;
@@ -570,23 +656,27 @@ const scanTable = async (table, params) => {
570
656
  /**
571
657
  * Query the specified DynamoDB table using the specified criteria
572
658
  *
659
+ * @param dynamoConnectionParams the DynamoDB connection parameters
573
660
  * @param params
574
661
  * @returns an array of DynamoDB items
575
662
  */
576
- const queryTable = async (params = {}) => {
663
+ const queryTable = async (dynamoConnectionParams, params = {}) => {
577
664
  // Query the DynamoDB index table for all DMP metadata (with pagination)
578
- const command = new client_dynamodb_1.QueryCommand(Object.assign({ TableName: process.env.DYNAMODB_TABLE_NAME, ConsistentRead: false, ReturnConsumedCapacity: 'TOTAL' }, params));
665
+ const command = new client_dynamodb_1.QueryCommand(Object.assign({ TableName: dynamoConnectionParams.tableName, ConsistentRead: false, ReturnConsumedCapacity: 'TOTAL' }, params));
666
+ const dynamoDBClient = getDynamoDBClient(dynamoConnectionParams);
579
667
  return await dynamoDBClient.send(command);
580
668
  };
581
669
  /**
582
670
  * Create/Update an item in the specified DynamoDB table
583
671
  *
584
- * @param item
672
+ * @param dynamoConnectionParams the DynamoDB connection parameters
673
+ * @param item the item to insert/update
585
674
  */
586
- const putItem = async (item) => {
675
+ const putItem = async (dynamoConnectionParams, item) => {
676
+ const dynamoDBClient = getDynamoDBClient(dynamoConnectionParams);
587
677
  // Delete the item from the DynamoDB table
588
678
  await dynamoDBClient.send(new client_dynamodb_1.PutItemCommand({
589
- TableName: process.env.DYNAMO_TABLE_NAME,
679
+ TableName: dynamoConnectionParams.tableName,
590
680
  ReturnConsumedCapacity: 'TOTAL',
591
681
  Item: item
592
682
  }));
@@ -595,12 +685,14 @@ const putItem = async (item) => {
595
685
  /**
596
686
  * Delete an item from the specified DynamoDB table
597
687
  *
598
- * @param key
688
+ * @param dynamoConnectionParams the DynamoDB connection parameters
689
+ * @param key the partition (and sort key if applicable) of the item to delete
599
690
  */
600
- const deleteItem = async (key) => {
691
+ const deleteItem = async (dynamoConnectionParams, key) => {
692
+ const dynamoDBClient = getDynamoDBClient(dynamoConnectionParams);
601
693
  // Delete the item from the DynamoDB table
602
694
  await dynamoDBClient.send(new client_dynamodb_1.DeleteItemCommand({
603
- TableName: process.env.DYNAMO_TABLE_NAME,
695
+ TableName: dynamoConnectionParams.tableName,
604
696
  ReturnConsumedCapacity: 'TOTAL',
605
697
  Key: key
606
698
  }));
@@ -1,3 +1,4 @@
1
+ import { Logger } from 'pino';
1
2
  export interface PutEventResponse {
2
3
  status: number;
3
4
  message: string;
@@ -6,8 +7,9 @@ export interface PutEventResponse {
6
7
  /**
7
8
  * Publishes an event to EventBridge.
8
9
  *
10
+ * @param logger The logger to use for logging.
9
11
  * @param source The name of the caller (e.g. the Lambda Function or Application Function)
10
12
  * @param detailType The type of event (resources typically watch for specific types of events)
11
13
  * @param detail The payload of the event (will be accessible to the invoked resource)
12
14
  */
13
- export declare const putEvent: (source: string, detailType: string, details: Record<string, unknown>) => Promise<PutEventResponse>;
15
+ export declare const putEvent: (logger: Logger, busName: string, source: string, detailType: string, details: Record<string, unknown>, region?: string) => Promise<PutEventResponse>;