@aligent/aws-wrappers 0.0.1
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/CLAUDE.md +172 -0
- package/README.md +286 -0
- package/docs/classes/DynamoDBService.md +353 -0
- package/docs/classes/S3Service.md +389 -0
- package/docs/classes/SNSService.md +95 -0
- package/docs/classes/SQSService.md +162 -0
- package/docs/classes/SSMService.md +157 -0
- package/docs/classes/SecretsManagerService.md +114 -0
- package/docs/classes/StepFunctionsService.md +134 -0
- package/docs/modules.md +15 -0
- package/package.json +32 -0
- package/src/dynamodb/dynamodb.d.ts +127 -0
- package/src/dynamodb/dynamodb.js +308 -0
- package/src/index.d.ts +7 -0
- package/src/index.js +17 -0
- package/src/s3/s3.d.ts +131 -0
- package/src/s3/s3.js +244 -0
- package/src/secrets-manager/secrets-manager.d.ts +78 -0
- package/src/secrets-manager/secrets-manager.js +152 -0
- package/src/sfn/sfn.d.ts +38 -0
- package/src/sfn/sfn.js +74 -0
- package/src/sns/sns.d.ts +48 -0
- package/src/sns/sns.js +110 -0
- package/src/sqs/sqs.d.ts +60 -0
- package/src/sqs/sqs.js +134 -0
- package/src/ssm/ssm.d.ts +84 -0
- package/src/ssm/ssm.js +144 -0
- package/src/util/redact.d.ts +18 -0
- package/src/util/redact.js +29 -0
- package/src/util/truncate.d.ts +15 -0
- package/src/util/truncate.js +36 -0
- package/tsconfig.lib.tsbuildinfo +1 -0
package/src/sns/sns.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SNSService = void 0;
|
|
4
|
+
const logger_1 = require("@aws-lambda-powertools/logger");
|
|
5
|
+
const client_sns_1 = require("@aws-sdk/client-sns");
|
|
6
|
+
const aws_xray_sdk_core_1 = require("aws-xray-sdk-core");
|
|
7
|
+
const redact_1 = require("../util/redact");
|
|
8
|
+
const truncate_1 = require("../util/truncate");
|
|
9
|
+
const PUBLISH_BATCH_LIMIT = 10;
|
|
10
|
+
const SNS_MESSAGE_MAX_BYTES = 256 * 1024;
|
|
11
|
+
const SNS_SUBJECT_MAX_CHARS = 100;
|
|
12
|
+
/**
|
|
13
|
+
* Fields safe to log at INFO level for `publish`. Omits `Message`, `Subject`,
|
|
14
|
+
* `MessageAttributes`, and `PhoneNumber` — payloads, user-visible content
|
|
15
|
+
* (subjects often carry order numbers / customer names), and PII recipient
|
|
16
|
+
* identifiers. `POWERTOOLS_LOG_LEVEL=DEBUG` unlocks the full input.
|
|
17
|
+
*/
|
|
18
|
+
const PUBLISH_SAFE_FIELDS = [
|
|
19
|
+
'TopicArn',
|
|
20
|
+
'TargetArn',
|
|
21
|
+
'MessageStructure',
|
|
22
|
+
'MessageGroupId',
|
|
23
|
+
'MessageDeduplicationId',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Wrapper around the AWS SNS client providing structured Powertools logging
|
|
27
|
+
* and X-Ray tracing by default.
|
|
28
|
+
*/
|
|
29
|
+
class SNSService {
|
|
30
|
+
client;
|
|
31
|
+
logger;
|
|
32
|
+
truncate;
|
|
33
|
+
/**
|
|
34
|
+
* @param opts.logger - Optional Powertools logger. Defaults to `new Logger()`,
|
|
35
|
+
* which picks up `POWERTOOLS_SERVICE_NAME` from the environment.
|
|
36
|
+
* @param opts.client - Optional pre-configured `SNSClient`. When supplied,
|
|
37
|
+
* the wrapper does not apply X-Ray instrumentation.
|
|
38
|
+
* @param opts.truncate - When `true`, oversized `Message` / `Subject` are
|
|
39
|
+
* truncated (byte-safe / codepoint-safe) before sending instead of failing
|
|
40
|
+
* at the SDK. Defaults to `false` — the SDK throws on oversize, which is
|
|
41
|
+
* usually the right failure mode. Each `publish` call can override via
|
|
42
|
+
* its own `truncate` option.
|
|
43
|
+
*/
|
|
44
|
+
constructor(opts) {
|
|
45
|
+
this.client = opts?.client ?? (0, aws_xray_sdk_core_1.captureAWSv3Client)(new client_sns_1.SNSClient());
|
|
46
|
+
this.logger = opts?.logger ?? new logger_1.Logger();
|
|
47
|
+
this.truncate = opts?.truncate ?? false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Publish a single message to an SNS topic.
|
|
51
|
+
*
|
|
52
|
+
* At INFO level the log line includes only routing / dedup metadata; see
|
|
53
|
+
* `PUBLISH_SAFE_FIELDS` for the list. Setting `POWERTOOLS_LOG_LEVEL=DEBUG`
|
|
54
|
+
* unlocks the full input.
|
|
55
|
+
*
|
|
56
|
+
* @param input - PublishCommandInput including TopicArn and Message.
|
|
57
|
+
*/
|
|
58
|
+
async publish(input, opts) {
|
|
59
|
+
const shouldTruncate = opts?.truncate ?? this.truncate;
|
|
60
|
+
const effective = shouldTruncate ? this.applyTruncation(input) : input;
|
|
61
|
+
this.logger.info('Publishing SNS message', {
|
|
62
|
+
input: (0, redact_1.filterFieldsForLogLevel)(this.logger, effective, PUBLISH_SAFE_FIELDS),
|
|
63
|
+
});
|
|
64
|
+
return this.client.send(new client_sns_1.PublishCommand(effective));
|
|
65
|
+
}
|
|
66
|
+
applyTruncation(input) {
|
|
67
|
+
const truncated = [];
|
|
68
|
+
let Message = input.Message;
|
|
69
|
+
if (Message !== undefined && Buffer.byteLength(Message, 'utf8') > SNS_MESSAGE_MAX_BYTES) {
|
|
70
|
+
Message = (0, truncate_1.truncateUtf8)(Message, SNS_MESSAGE_MAX_BYTES);
|
|
71
|
+
truncated.push('Message');
|
|
72
|
+
}
|
|
73
|
+
let Subject = input.Subject;
|
|
74
|
+
if (Subject !== undefined && Array.from(Subject).length > SNS_SUBJECT_MAX_CHARS) {
|
|
75
|
+
Subject = (0, truncate_1.truncateCodepoints)(Subject, SNS_SUBJECT_MAX_CHARS);
|
|
76
|
+
truncated.push('Subject');
|
|
77
|
+
}
|
|
78
|
+
if (truncated.length > 0) {
|
|
79
|
+
this.logger.warn('Truncated SNS publish input', { fields: truncated });
|
|
80
|
+
}
|
|
81
|
+
return { ...input, Message, Subject };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Publish a batch of messages to an SNS topic. The SNS API caps
|
|
85
|
+
* PublishBatch at 10 entries per request, so this method auto-chunks
|
|
86
|
+
* the caller's `PublishBatchRequestEntries` and sends one request per
|
|
87
|
+
* chunk, returning the array of outputs.
|
|
88
|
+
* @param input - PublishBatchCommandInput including TopicArn and entries.
|
|
89
|
+
*/
|
|
90
|
+
async publishBatch(input) {
|
|
91
|
+
const entries = input.PublishBatchRequestEntries ?? [];
|
|
92
|
+
// Inline DEBUG check rather than `filterFieldsForLogLevel` because the
|
|
93
|
+
// safe log shape includes the computed `entryCount`, which isn't a key
|
|
94
|
+
// on `PublishBatchCommandInput`.
|
|
95
|
+
const isDebug = this.logger.getLevelName() === 'DEBUG';
|
|
96
|
+
this.logger.info('Publishing SNS message batch', {
|
|
97
|
+
input: isDebug ? input : { TopicArn: input.TopicArn, entryCount: entries.length },
|
|
98
|
+
});
|
|
99
|
+
const results = [];
|
|
100
|
+
for (let i = 0; i < entries.length; i += PUBLISH_BATCH_LIMIT) {
|
|
101
|
+
const chunk = entries.slice(i, i + PUBLISH_BATCH_LIMIT);
|
|
102
|
+
results.push(await this.client.send(new client_sns_1.PublishBatchCommand({
|
|
103
|
+
...input,
|
|
104
|
+
PublishBatchRequestEntries: chunk,
|
|
105
|
+
})));
|
|
106
|
+
}
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.SNSService = SNSService;
|
package/src/sqs/sqs.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Logger } from '@aws-lambda-powertools/logger';
|
|
2
|
+
import { DeleteMessageBatchCommandInput, DeleteMessageBatchCommandOutput, DeleteMessageCommandInput, DeleteMessageCommandOutput, Message, ReceiveMessageCommandInput, SendMessageBatchCommandInput, SendMessageBatchCommandOutput, SendMessageCommandInput, SendMessageCommandOutput, SQSClient } from '@aws-sdk/client-sqs';
|
|
3
|
+
/**
|
|
4
|
+
* Wrapper around the AWS SQS client providing structured Powertools logging
|
|
5
|
+
* and X-Ray tracing by default.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SQSService {
|
|
8
|
+
private readonly client;
|
|
9
|
+
private readonly logger;
|
|
10
|
+
private readonly truncate;
|
|
11
|
+
/**
|
|
12
|
+
* @param opts.logger - Optional Powertools logger. Defaults to `new Logger()`,
|
|
13
|
+
* which picks up `POWERTOOLS_SERVICE_NAME` from the environment.
|
|
14
|
+
* @param opts.client - Optional pre-configured `SQSClient`. When supplied,
|
|
15
|
+
* the wrapper does not apply X-Ray instrumentation.
|
|
16
|
+
* @param opts.truncate - When `true`, oversized `MessageBody` is truncated
|
|
17
|
+
* (byte-safe) before sending instead of failing at the SDK. Defaults to
|
|
18
|
+
* `false`. Each `sendMessage` call can override via its own `truncate`
|
|
19
|
+
* option.
|
|
20
|
+
*/
|
|
21
|
+
constructor(opts?: {
|
|
22
|
+
logger?: Logger;
|
|
23
|
+
client?: SQSClient;
|
|
24
|
+
truncate?: boolean;
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Send a single message to an SQS queue.
|
|
28
|
+
*
|
|
29
|
+
* At INFO level the log line includes only queue routing / FIFO metadata;
|
|
30
|
+
* see `SEND_MESSAGE_SAFE_FIELDS`. `POWERTOOLS_LOG_LEVEL=DEBUG` unlocks the
|
|
31
|
+
* full input.
|
|
32
|
+
*/
|
|
33
|
+
sendMessage(input: SendMessageCommandInput, opts?: {
|
|
34
|
+
truncate?: boolean;
|
|
35
|
+
}): Promise<SendMessageCommandOutput>;
|
|
36
|
+
private applyTruncation;
|
|
37
|
+
/**
|
|
38
|
+
* Receive messages from an SQS queue. Returns an empty array when no
|
|
39
|
+
* messages are available. No automatic deletion is performed — visibility
|
|
40
|
+
* timeout semantics are the caller's responsibility.
|
|
41
|
+
* @returns The `Messages` array from the response, or `[]` if absent.
|
|
42
|
+
*/
|
|
43
|
+
receiveMessages(input: ReceiveMessageCommandInput): Promise<Message[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Delete a single message from an SQS queue.
|
|
46
|
+
*/
|
|
47
|
+
deleteMessage(input: DeleteMessageCommandInput): Promise<DeleteMessageCommandOutput>;
|
|
48
|
+
/**
|
|
49
|
+
* Send a batch of messages to an SQS queue. The SQS API caps
|
|
50
|
+
* SendMessageBatch at 10 entries per request, so this method auto-chunks
|
|
51
|
+
* the caller's entries and sends one request per chunk.
|
|
52
|
+
*/
|
|
53
|
+
sendMessageBatch(input: SendMessageBatchCommandInput): Promise<SendMessageBatchCommandOutput[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Delete a batch of messages from an SQS queue. The SQS API caps
|
|
56
|
+
* DeleteMessageBatch at 10 entries per request, so this method auto-chunks
|
|
57
|
+
* the caller's entries and sends one request per chunk.
|
|
58
|
+
*/
|
|
59
|
+
deleteMessageBatch(input: DeleteMessageBatchCommandInput): Promise<DeleteMessageBatchCommandOutput[]>;
|
|
60
|
+
}
|
package/src/sqs/sqs.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SQSService = void 0;
|
|
4
|
+
const logger_1 = require("@aws-lambda-powertools/logger");
|
|
5
|
+
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
6
|
+
const aws_xray_sdk_core_1 = require("aws-xray-sdk-core");
|
|
7
|
+
const redact_1 = require("../util/redact");
|
|
8
|
+
const truncate_1 = require("../util/truncate");
|
|
9
|
+
const SQS_BATCH_LIMIT = 10;
|
|
10
|
+
const SQS_MESSAGE_BODY_MAX_BYTES = 256 * 1024;
|
|
11
|
+
/**
|
|
12
|
+
* Fields safe to log at INFO level for `sendMessage`. Omits `MessageBody` and
|
|
13
|
+
* `MessageAttributes` — both carry payload content.
|
|
14
|
+
* `POWERTOOLS_LOG_LEVEL=DEBUG` unlocks the full input.
|
|
15
|
+
*/
|
|
16
|
+
const SEND_MESSAGE_SAFE_FIELDS = [
|
|
17
|
+
'QueueUrl',
|
|
18
|
+
'DelaySeconds',
|
|
19
|
+
'MessageGroupId',
|
|
20
|
+
'MessageDeduplicationId',
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Wrapper around the AWS SQS client providing structured Powertools logging
|
|
24
|
+
* and X-Ray tracing by default.
|
|
25
|
+
*/
|
|
26
|
+
class SQSService {
|
|
27
|
+
client;
|
|
28
|
+
logger;
|
|
29
|
+
truncate;
|
|
30
|
+
/**
|
|
31
|
+
* @param opts.logger - Optional Powertools logger. Defaults to `new Logger()`,
|
|
32
|
+
* which picks up `POWERTOOLS_SERVICE_NAME` from the environment.
|
|
33
|
+
* @param opts.client - Optional pre-configured `SQSClient`. When supplied,
|
|
34
|
+
* the wrapper does not apply X-Ray instrumentation.
|
|
35
|
+
* @param opts.truncate - When `true`, oversized `MessageBody` is truncated
|
|
36
|
+
* (byte-safe) before sending instead of failing at the SDK. Defaults to
|
|
37
|
+
* `false`. Each `sendMessage` call can override via its own `truncate`
|
|
38
|
+
* option.
|
|
39
|
+
*/
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
this.client = opts?.client ?? (0, aws_xray_sdk_core_1.captureAWSv3Client)(new client_sqs_1.SQSClient());
|
|
42
|
+
this.logger = opts?.logger ?? new logger_1.Logger();
|
|
43
|
+
this.truncate = opts?.truncate ?? false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Send a single message to an SQS queue.
|
|
47
|
+
*
|
|
48
|
+
* At INFO level the log line includes only queue routing / FIFO metadata;
|
|
49
|
+
* see `SEND_MESSAGE_SAFE_FIELDS`. `POWERTOOLS_LOG_LEVEL=DEBUG` unlocks the
|
|
50
|
+
* full input.
|
|
51
|
+
*/
|
|
52
|
+
async sendMessage(input, opts) {
|
|
53
|
+
const shouldTruncate = opts?.truncate ?? this.truncate;
|
|
54
|
+
const effective = shouldTruncate ? this.applyTruncation(input) : input;
|
|
55
|
+
this.logger.info('Sending SQS message', {
|
|
56
|
+
input: (0, redact_1.filterFieldsForLogLevel)(this.logger, effective, SEND_MESSAGE_SAFE_FIELDS),
|
|
57
|
+
});
|
|
58
|
+
return this.client.send(new client_sqs_1.SendMessageCommand(effective));
|
|
59
|
+
}
|
|
60
|
+
applyTruncation(input) {
|
|
61
|
+
const truncated = [];
|
|
62
|
+
let MessageBody = input.MessageBody;
|
|
63
|
+
if (MessageBody !== undefined &&
|
|
64
|
+
Buffer.byteLength(MessageBody, 'utf8') > SQS_MESSAGE_BODY_MAX_BYTES) {
|
|
65
|
+
MessageBody = (0, truncate_1.truncateUtf8)(MessageBody, SQS_MESSAGE_BODY_MAX_BYTES);
|
|
66
|
+
truncated.push('MessageBody');
|
|
67
|
+
}
|
|
68
|
+
if (truncated.length > 0) {
|
|
69
|
+
this.logger.warn('Truncated SQS sendMessage input', { fields: truncated });
|
|
70
|
+
}
|
|
71
|
+
return { ...input, MessageBody };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Receive messages from an SQS queue. Returns an empty array when no
|
|
75
|
+
* messages are available. No automatic deletion is performed — visibility
|
|
76
|
+
* timeout semantics are the caller's responsibility.
|
|
77
|
+
* @returns The `Messages` array from the response, or `[]` if absent.
|
|
78
|
+
*/
|
|
79
|
+
async receiveMessages(input) {
|
|
80
|
+
this.logger.info('Receiving SQS messages', { input });
|
|
81
|
+
const response = await this.client.send(new client_sqs_1.ReceiveMessageCommand(input));
|
|
82
|
+
return response.Messages ?? [];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Delete a single message from an SQS queue.
|
|
86
|
+
*/
|
|
87
|
+
async deleteMessage(input) {
|
|
88
|
+
this.logger.info('Deleting SQS message', { input });
|
|
89
|
+
return this.client.send(new client_sqs_1.DeleteMessageCommand(input));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Send a batch of messages to an SQS queue. The SQS API caps
|
|
93
|
+
* SendMessageBatch at 10 entries per request, so this method auto-chunks
|
|
94
|
+
* the caller's entries and sends one request per chunk.
|
|
95
|
+
*/
|
|
96
|
+
async sendMessageBatch(input) {
|
|
97
|
+
const entries = input.Entries ?? [];
|
|
98
|
+
// Inline DEBUG check rather than `filterFieldsForLogLevel` because the
|
|
99
|
+
// safe log shape includes the computed `entryCount`, which isn't a key
|
|
100
|
+
// on `SendMessageBatchCommandInput`.
|
|
101
|
+
const isDebug = this.logger.getLevelName() === 'DEBUG';
|
|
102
|
+
this.logger.info('Sending SQS message batch', {
|
|
103
|
+
input: isDebug ? input : { QueueUrl: input.QueueUrl, entryCount: entries.length },
|
|
104
|
+
});
|
|
105
|
+
const results = [];
|
|
106
|
+
for (let i = 0; i < entries.length; i += SQS_BATCH_LIMIT) {
|
|
107
|
+
const chunk = entries.slice(i, i + SQS_BATCH_LIMIT);
|
|
108
|
+
results.push(await this.client.send(new client_sqs_1.SendMessageBatchCommand({ ...input, Entries: chunk })));
|
|
109
|
+
}
|
|
110
|
+
return results;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Delete a batch of messages from an SQS queue. The SQS API caps
|
|
114
|
+
* DeleteMessageBatch at 10 entries per request, so this method auto-chunks
|
|
115
|
+
* the caller's entries and sends one request per chunk.
|
|
116
|
+
*/
|
|
117
|
+
async deleteMessageBatch(input) {
|
|
118
|
+
const entries = input.Entries ?? [];
|
|
119
|
+
// Inline DEBUG check rather than `filterFieldsForLogLevel` because the
|
|
120
|
+
// safe log shape includes the computed `entryCount`, which isn't a key
|
|
121
|
+
// on `DeleteMessageBatchCommandInput`.
|
|
122
|
+
const isDebug = this.logger.getLevelName() === 'DEBUG';
|
|
123
|
+
this.logger.info('Deleting SQS message batch', {
|
|
124
|
+
input: isDebug ? input : { QueueUrl: input.QueueUrl, entryCount: entries.length },
|
|
125
|
+
});
|
|
126
|
+
const results = [];
|
|
127
|
+
for (let i = 0; i < entries.length; i += SQS_BATCH_LIMIT) {
|
|
128
|
+
const chunk = entries.slice(i, i + SQS_BATCH_LIMIT);
|
|
129
|
+
results.push(await this.client.send(new client_sqs_1.DeleteMessageBatchCommand({ ...input, Entries: chunk })));
|
|
130
|
+
}
|
|
131
|
+
return results;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.SQSService = SQSService;
|
package/src/ssm/ssm.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Logger } from '@aws-lambda-powertools/logger';
|
|
2
|
+
import { Parameter, PutParameterCommandInput, PutParameterCommandOutput, SSMClient } from '@aws-sdk/client-ssm';
|
|
3
|
+
/**
|
|
4
|
+
* Wrapper around the AWS SSM Parameter Store client providing structured
|
|
5
|
+
* Powertools logging and X-Ray tracing by default. All read operations enable
|
|
6
|
+
* `WithDecryption` — callers needing plaintext should use `SSMClient`
|
|
7
|
+
* directly.
|
|
8
|
+
*
|
|
9
|
+
* Write operations (`putParameter`, `deleteParameter`) are exposed for
|
|
10
|
+
* convenience but should be used with care: parameter lifecycle is usually
|
|
11
|
+
* managed by IaC (CDK / Terraform). Prefer IaC for anything that exists at
|
|
12
|
+
* deploy time; reserve runtime writes for values that genuinely need to
|
|
13
|
+
* mutate within the application.
|
|
14
|
+
*/
|
|
15
|
+
export declare class SSMService {
|
|
16
|
+
private readonly client;
|
|
17
|
+
private readonly logger;
|
|
18
|
+
/**
|
|
19
|
+
* @param opts.logger - Optional Powertools logger. Defaults to `new Logger()`,
|
|
20
|
+
* which picks up `POWERTOOLS_SERVICE_NAME` from the environment.
|
|
21
|
+
* @param opts.client - Optional pre-configured `SSMClient`. When supplied,
|
|
22
|
+
* the wrapper does not apply X-Ray instrumentation.
|
|
23
|
+
*/
|
|
24
|
+
constructor(opts?: {
|
|
25
|
+
logger?: Logger;
|
|
26
|
+
client?: SSMClient;
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Fetch a single SSM parameter's value.
|
|
30
|
+
* @param name - The parameter name (or ARN).
|
|
31
|
+
* @returns The parameter value, or `undefined` if the parameter has no
|
|
32
|
+
* value set.
|
|
33
|
+
*/
|
|
34
|
+
getParameter(name: string): Promise<string | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* Fetch multiple SSM parameters in a single request. Callers supply an
|
|
37
|
+
* alias-to-path record, and the returned record is keyed by the same
|
|
38
|
+
* aliases — so the SSM path is only mentioned at the call site and the
|
|
39
|
+
* destructured names are whatever the caller wants to use locally:
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const { username, password } = await ssm.getParameters({
|
|
43
|
+
* username: '/myapp/db/username',
|
|
44
|
+
* password: '/myapp/db/password',
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @param aliases - Record mapping each desired local alias to its SSM
|
|
49
|
+
* parameter name (or ARN).
|
|
50
|
+
* @returns A record keyed by the same aliases, mapping each to the
|
|
51
|
+
* parameter's value, or `undefined` when the parameter is missing or has
|
|
52
|
+
* no value.
|
|
53
|
+
*/
|
|
54
|
+
getParameters<K extends string>(aliases: Record<K, string>): Promise<Record<K, string | undefined>>;
|
|
55
|
+
/**
|
|
56
|
+
* Create or update an SSM parameter. The log line omits `Value` to avoid
|
|
57
|
+
* leaking secret material.
|
|
58
|
+
*
|
|
59
|
+
* Prefer IaC (CDK / Terraform) for parameter lifecycle — use this for
|
|
60
|
+
* runtime values only.
|
|
61
|
+
*/
|
|
62
|
+
putParameter(input: PutParameterCommandInput): Promise<PutParameterCommandOutput>;
|
|
63
|
+
/**
|
|
64
|
+
* Delete an SSM parameter by name.
|
|
65
|
+
*
|
|
66
|
+
* Prefer IaC (CDK / Terraform) for parameter lifecycle — use this for
|
|
67
|
+
* runtime cleanup only.
|
|
68
|
+
*/
|
|
69
|
+
deleteParameter(name: string): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Fetch all parameters under an SSM hierarchy path, auto-paginating across
|
|
72
|
+
* all pages. `Recursive` defaults to `true` (overriding the AWS SDK
|
|
73
|
+
* default of `false`) to match the typical "load all config under
|
|
74
|
+
* `/myapp/`" use case.
|
|
75
|
+
* @param path - The parameter path prefix (e.g. `/myapp/`).
|
|
76
|
+
* @param opts.recursive - Whether to recurse into nested paths. Defaults
|
|
77
|
+
* to `true`.
|
|
78
|
+
* @returns The full `Parameter` objects (including `Version`,
|
|
79
|
+
* `LastModifiedDate`, etc.).
|
|
80
|
+
*/
|
|
81
|
+
getParametersByPath(path: string, opts?: {
|
|
82
|
+
recursive?: boolean;
|
|
83
|
+
}): Promise<Parameter[]>;
|
|
84
|
+
}
|
package/src/ssm/ssm.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SSMService = void 0;
|
|
4
|
+
const logger_1 = require("@aws-lambda-powertools/logger");
|
|
5
|
+
const client_ssm_1 = require("@aws-sdk/client-ssm");
|
|
6
|
+
const aws_xray_sdk_core_1 = require("aws-xray-sdk-core");
|
|
7
|
+
const redact_1 = require("../util/redact");
|
|
8
|
+
/**
|
|
9
|
+
* Fields safe to log at INFO level for `putParameter`. Omits `Value` (the
|
|
10
|
+
* parameter content itself). `POWERTOOLS_LOG_LEVEL=DEBUG` unlocks the full
|
|
11
|
+
* input.
|
|
12
|
+
*/
|
|
13
|
+
const PUT_PARAMETER_SAFE_FIELDS = [
|
|
14
|
+
'Name',
|
|
15
|
+
'Type',
|
|
16
|
+
'Description',
|
|
17
|
+
'KeyId',
|
|
18
|
+
'Overwrite',
|
|
19
|
+
'AllowedPattern',
|
|
20
|
+
'Tags',
|
|
21
|
+
'Tier',
|
|
22
|
+
'Policies',
|
|
23
|
+
'DataType',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Wrapper around the AWS SSM Parameter Store client providing structured
|
|
27
|
+
* Powertools logging and X-Ray tracing by default. All read operations enable
|
|
28
|
+
* `WithDecryption` — callers needing plaintext should use `SSMClient`
|
|
29
|
+
* directly.
|
|
30
|
+
*
|
|
31
|
+
* Write operations (`putParameter`, `deleteParameter`) are exposed for
|
|
32
|
+
* convenience but should be used with care: parameter lifecycle is usually
|
|
33
|
+
* managed by IaC (CDK / Terraform). Prefer IaC for anything that exists at
|
|
34
|
+
* deploy time; reserve runtime writes for values that genuinely need to
|
|
35
|
+
* mutate within the application.
|
|
36
|
+
*/
|
|
37
|
+
class SSMService {
|
|
38
|
+
client;
|
|
39
|
+
logger;
|
|
40
|
+
/**
|
|
41
|
+
* @param opts.logger - Optional Powertools logger. Defaults to `new Logger()`,
|
|
42
|
+
* which picks up `POWERTOOLS_SERVICE_NAME` from the environment.
|
|
43
|
+
* @param opts.client - Optional pre-configured `SSMClient`. When supplied,
|
|
44
|
+
* the wrapper does not apply X-Ray instrumentation.
|
|
45
|
+
*/
|
|
46
|
+
constructor(opts) {
|
|
47
|
+
this.client = opts?.client ?? (0, aws_xray_sdk_core_1.captureAWSv3Client)(new client_ssm_1.SSMClient());
|
|
48
|
+
this.logger = opts?.logger ?? new logger_1.Logger();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetch a single SSM parameter's value.
|
|
52
|
+
* @param name - The parameter name (or ARN).
|
|
53
|
+
* @returns The parameter value, or `undefined` if the parameter has no
|
|
54
|
+
* value set.
|
|
55
|
+
*/
|
|
56
|
+
async getParameter(name) {
|
|
57
|
+
this.logger.info('Fetching SSM parameter', { input: { name } });
|
|
58
|
+
const response = await this.client.send(new client_ssm_1.GetParameterCommand({ Name: name, WithDecryption: true }));
|
|
59
|
+
return response.Parameter?.Value;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Fetch multiple SSM parameters in a single request. Callers supply an
|
|
63
|
+
* alias-to-path record, and the returned record is keyed by the same
|
|
64
|
+
* aliases — so the SSM path is only mentioned at the call site and the
|
|
65
|
+
* destructured names are whatever the caller wants to use locally:
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* const { username, password } = await ssm.getParameters({
|
|
69
|
+
* username: '/myapp/db/username',
|
|
70
|
+
* password: '/myapp/db/password',
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @param aliases - Record mapping each desired local alias to its SSM
|
|
75
|
+
* parameter name (or ARN).
|
|
76
|
+
* @returns A record keyed by the same aliases, mapping each to the
|
|
77
|
+
* parameter's value, or `undefined` when the parameter is missing or has
|
|
78
|
+
* no value.
|
|
79
|
+
*/
|
|
80
|
+
async getParameters(aliases) {
|
|
81
|
+
this.logger.info('Fetching SSM parameters', { input: { aliases } });
|
|
82
|
+
const response = await this.client.send(new client_ssm_1.GetParametersCommand({
|
|
83
|
+
Names: Object.values(aliases),
|
|
84
|
+
WithDecryption: true,
|
|
85
|
+
}));
|
|
86
|
+
const byPath = new Map();
|
|
87
|
+
for (const parameter of response.Parameters ?? []) {
|
|
88
|
+
if (parameter.Name !== undefined)
|
|
89
|
+
byPath.set(parameter.Name, parameter.Value);
|
|
90
|
+
}
|
|
91
|
+
const result = {};
|
|
92
|
+
for (const alias of Object.keys(aliases)) {
|
|
93
|
+
result[alias] = byPath.get(aliases[alias]);
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create or update an SSM parameter. The log line omits `Value` to avoid
|
|
99
|
+
* leaking secret material.
|
|
100
|
+
*
|
|
101
|
+
* Prefer IaC (CDK / Terraform) for parameter lifecycle — use this for
|
|
102
|
+
* runtime values only.
|
|
103
|
+
*/
|
|
104
|
+
async putParameter(input) {
|
|
105
|
+
this.logger.info('Putting SSM parameter', {
|
|
106
|
+
input: (0, redact_1.filterFieldsForLogLevel)(this.logger, input, PUT_PARAMETER_SAFE_FIELDS),
|
|
107
|
+
});
|
|
108
|
+
return this.client.send(new client_ssm_1.PutParameterCommand(input));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Delete an SSM parameter by name.
|
|
112
|
+
*
|
|
113
|
+
* Prefer IaC (CDK / Terraform) for parameter lifecycle — use this for
|
|
114
|
+
* runtime cleanup only.
|
|
115
|
+
*/
|
|
116
|
+
async deleteParameter(name) {
|
|
117
|
+
this.logger.info('Deleting SSM parameter', { input: { name } });
|
|
118
|
+
await this.client.send(new client_ssm_1.DeleteParameterCommand({ Name: name }));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Fetch all parameters under an SSM hierarchy path, auto-paginating across
|
|
122
|
+
* all pages. `Recursive` defaults to `true` (overriding the AWS SDK
|
|
123
|
+
* default of `false`) to match the typical "load all config under
|
|
124
|
+
* `/myapp/`" use case.
|
|
125
|
+
* @param path - The parameter path prefix (e.g. `/myapp/`).
|
|
126
|
+
* @param opts.recursive - Whether to recurse into nested paths. Defaults
|
|
127
|
+
* to `true`.
|
|
128
|
+
* @returns The full `Parameter` objects (including `Version`,
|
|
129
|
+
* `LastModifiedDate`, etc.).
|
|
130
|
+
*/
|
|
131
|
+
async getParametersByPath(path, opts) {
|
|
132
|
+
const recursive = opts?.recursive ?? true;
|
|
133
|
+
this.logger.info('Fetching SSM parameters by path', {
|
|
134
|
+
input: { path, recursive },
|
|
135
|
+
});
|
|
136
|
+
const paginator = (0, client_ssm_1.paginateGetParametersByPath)({ client: this.client }, { Path: path, Recursive: recursive, WithDecryption: true });
|
|
137
|
+
const parameters = [];
|
|
138
|
+
for await (const page of paginator) {
|
|
139
|
+
parameters.push(...(page.Parameters ?? []));
|
|
140
|
+
}
|
|
141
|
+
return parameters;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.SSMService = SSMService;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Logger } from '@aws-lambda-powertools/logger';
|
|
2
|
+
/**
|
|
3
|
+
* Return a log-safe projection of `input` based on the logger's configured level.
|
|
4
|
+
*
|
|
5
|
+
* At `DEBUG`, the full input is returned unchanged — operators who set
|
|
6
|
+
* `POWERTOOLS_LOG_LEVEL=DEBUG` (or call `logger.setLogLevel('DEBUG')`) have
|
|
7
|
+
* explicitly opted into seeing everything, including payloads, secret material
|
|
8
|
+
* and PII.
|
|
9
|
+
*
|
|
10
|
+
* At any other level, only the fields listed in `safeFields` are included.
|
|
11
|
+
* Missing fields are silently skipped — the result type narrows to
|
|
12
|
+
* `Pick<T, K>` accordingly.
|
|
13
|
+
*
|
|
14
|
+
* Used across the package so that the "what's safe to log at INFO" decision
|
|
15
|
+
* lives in one place. See `packages/aws-wrappers/CLAUDE.md` ("Logging") for
|
|
16
|
+
* the design rationale and conventions on building the safe-field lists.
|
|
17
|
+
*/
|
|
18
|
+
export declare function filterFieldsForLogLevel<T extends object, K extends keyof T>(logger: Logger, input: T, safeFields: readonly K[]): T | Pick<T, K>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterFieldsForLogLevel = filterFieldsForLogLevel;
|
|
4
|
+
/**
|
|
5
|
+
* Return a log-safe projection of `input` based on the logger's configured level.
|
|
6
|
+
*
|
|
7
|
+
* At `DEBUG`, the full input is returned unchanged — operators who set
|
|
8
|
+
* `POWERTOOLS_LOG_LEVEL=DEBUG` (or call `logger.setLogLevel('DEBUG')`) have
|
|
9
|
+
* explicitly opted into seeing everything, including payloads, secret material
|
|
10
|
+
* and PII.
|
|
11
|
+
*
|
|
12
|
+
* At any other level, only the fields listed in `safeFields` are included.
|
|
13
|
+
* Missing fields are silently skipped — the result type narrows to
|
|
14
|
+
* `Pick<T, K>` accordingly.
|
|
15
|
+
*
|
|
16
|
+
* Used across the package so that the "what's safe to log at INFO" decision
|
|
17
|
+
* lives in one place. See `packages/aws-wrappers/CLAUDE.md` ("Logging") for
|
|
18
|
+
* the design rationale and conventions on building the safe-field lists.
|
|
19
|
+
*/
|
|
20
|
+
function filterFieldsForLogLevel(logger, input, safeFields) {
|
|
21
|
+
if (logger.getLevelName() === 'DEBUG')
|
|
22
|
+
return input;
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const key of safeFields) {
|
|
25
|
+
if (key in input)
|
|
26
|
+
out[key] = input[key];
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncate a UTF-8 string to fit within `maxBytes` when encoded as UTF-8.
|
|
3
|
+
*
|
|
4
|
+
* Walks back from the cut point to the start of the previous codepoint, so the
|
|
5
|
+
* result is never a malformed UTF-8 sequence. Returns the input unchanged when
|
|
6
|
+
* it already fits.
|
|
7
|
+
*/
|
|
8
|
+
export declare function truncateUtf8(str: string, maxBytes: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Truncate a string to at most `maxChars` Unicode codepoints. Splits the
|
|
11
|
+
* string via `Array.from` so surrogate pairs (emoji, supplementary-plane
|
|
12
|
+
* characters) are not split in the middle. Returns the input unchanged when
|
|
13
|
+
* it already fits.
|
|
14
|
+
*/
|
|
15
|
+
export declare function truncateCodepoints(str: string, maxChars: number): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.truncateUtf8 = truncateUtf8;
|
|
4
|
+
exports.truncateCodepoints = truncateCodepoints;
|
|
5
|
+
/**
|
|
6
|
+
* Truncate a UTF-8 string to fit within `maxBytes` when encoded as UTF-8.
|
|
7
|
+
*
|
|
8
|
+
* Walks back from the cut point to the start of the previous codepoint, so the
|
|
9
|
+
* result is never a malformed UTF-8 sequence. Returns the input unchanged when
|
|
10
|
+
* it already fits.
|
|
11
|
+
*/
|
|
12
|
+
function truncateUtf8(str, maxBytes) {
|
|
13
|
+
const buf = Buffer.from(str, 'utf8');
|
|
14
|
+
if (buf.length <= maxBytes)
|
|
15
|
+
return str;
|
|
16
|
+
let end = maxBytes;
|
|
17
|
+
while (end > 0) {
|
|
18
|
+
const byte = buf[end];
|
|
19
|
+
if (byte === undefined || (byte & 0xc0) !== 0x80)
|
|
20
|
+
break;
|
|
21
|
+
end--;
|
|
22
|
+
}
|
|
23
|
+
return buf.toString('utf8', 0, end);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Truncate a string to at most `maxChars` Unicode codepoints. Splits the
|
|
27
|
+
* string via `Array.from` so surrogate pairs (emoji, supplementary-plane
|
|
28
|
+
* characters) are not split in the middle. Returns the input unchanged when
|
|
29
|
+
* it already fits.
|
|
30
|
+
*/
|
|
31
|
+
function truncateCodepoints(str, maxChars) {
|
|
32
|
+
const codepoints = Array.from(str);
|
|
33
|
+
if (codepoints.length <= maxChars)
|
|
34
|
+
return str;
|
|
35
|
+
return codepoints.slice(0, maxChars).join('');
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"5.9.3"}
|