@63klabs/cache-data 1.2.5 → 1.2.7

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Cache Data
1
+ # 63Klabs Cache-Data
2
2
 
3
- A package for node.js applications to access and cache data from remote API endpoints or other sources using AWS S3 and DynamoDb.
3
+ A package for AWS Lambda Node.js applications to access and cache data from remote API endpoints (or other sources) utilizing AWS S3 and DynamoDb for the cache storage. Also provides a simple request handling, routing, and response logging framework for running a web service with minimal dependencies.
4
4
 
5
5
  > Note: This repository and package has moved from chadkluck to 63Klabs but is still managed by the same developer.
6
6
 
@@ -8,1259 +8,81 @@ A package for node.js applications to access and cache data from remote API endp
8
8
 
9
9
  ## Description
10
10
 
11
- For Lambda functions written in Node.js that require caching of data either of an internal process or external data sources such as APIs. It is written specifically to be used in AWS Lambda functions using the Node runtime. However, it can be used in EC2 or other environments to access S3 and DynamoDb. While out of the box it can fetch data from remote endpoint APIs, custom Data Access Objects can be written to provide caching of data from all sorts of sources including resource expensive database calls.
11
+ For AWS Lambda functions written in Node.js that require caching of data either of an internal process or external data source such as remote API endpoints. While out of the box it can fetch data from remote endpoint APIs, custom Data Access Objects can be written to provide caching of data from all sorts of sources including resource expensive database calls.
12
12
 
13
- It also has several utility functions such as one that can load sensitive data from AWS SSM Parameter Store at load time.
13
+ It has several utility functions such `DebugAndLog`, `Timer`, and SSM Parameter Store loaders.
14
14
 
15
- This package has been used in production for applications receiving over 1 million requests per week with a 75% cache-hit rate lowering latency to less than 100ms in most cases. This is a considerable improvement when faced with resource intense processes, connection pools, API rate limits, and slow endpoints.
15
+ It can be used in place of Express.js for simple web service applications as it also includes functions for handling and validating requests, routing, and client request logging.
16
+
17
+ This package has been used in production for web service applications receiving over 1 million requests per week with a 75% cache-hit rate lowering latency to less than 100ms in most cases. This is a considerable improvement when faced with resource intense processes, connection pools, API rate limits, and slow endpoints.
16
18
 
17
19
  ## Getting Started
18
20
 
19
21
  ### Requirements
20
22
 
21
- * Current supported versions of Node.js on Lambda
22
- * AWS Lambda, S3 bucket, DynamoDb table, and SSM Parameter Store
23
- * A basic understanding of CloudFormation, Lambda, S3, DynamoDb, and SSM Parameters
24
- * A basic understanding of IAM policies, especially the Lambda Execution Role, that will allow Lambda to access S3, DynamoDb, and SSM Parameter Store
25
- * Lambda function should have between 512MB and 1024MB of memory allocated. (256MB minimum). See section regarding Lambda Memory under install.
23
+ - Node >18 runtime on Lambda
24
+ - AWS Lambda, S3 bucket, DynamoDb table, and SSM Parameter Store
25
+ - A basic understanding of CloudFormation, Lambda, S3, DynamoDb, and SSM Parameters
26
+ - A basic understanding of IAM policies, especially the Lambda Execution Role, that will allow Lambda to access S3, DynamoDb, and SSM Parameter Store
27
+ - Lambda function should have between 512MB and 1024MB of memory allocated. (256MB minimum). See [Lambda Optimization: Memory Allocation](./docs/lambda-optimization/README.md#lambda-memory-allocation).
26
28
 
27
29
  ### Installing
28
30
 
29
- 1. Make sure your function is using an AWS Lambda supported version of Node and has at least 256MB allocated (512-1024MB recommended).
30
- 2. Add the cache-data environment variables to your Lambda function. Also update your Lambda's execution role to access your S3 and DynamoDb.
31
- 3. Add an S3 bucket and DynamoDb table to store your cache either in the application CloudFormation template or as separate infrastructure.
32
- 4. Install the @63klabs/cache-data package `npm install @63klabs/cache-data`
33
- 5. Add the cache code to your Lambda function
34
-
35
- #### Lambda Memory Allocation
36
-
37
- As pointed out in many online resources, including [AWS's own documentation](https://docs.aws.amazon.com/lambda/latest/operatorguide/computing-power.html), Lambda applications should be given more than the default 128MB when using network resources and processing data. I recommend trying 512MB and adjusting depending on your workload and execution experiences. See [Lower AWS Lambda bill by increasing memory by Taavi Rehemägi](https://dashbird.io/blog/lower-aws-lambda-bill-increasing-memory/).
38
-
39
- Optimal performance is somewhere between 256MB and 1024MB. 1024MB is recommended. (I have seen additional improvements by using the AWS Graviton ARM architecture in Lambda.)
40
-
41
- Example: The charts below reflect 1 million requests over a seven-day period. As you can see, the invocations remained at a high level throughout the seven-day period. There was a dramatic drop in execution time once the memory was increased from 128 to 512MB. Latency was also improved. This also reduced the number of concurrent executions taking place. (The spike in errors was due to a 3rd party endpoint being down.)
42
-
43
- ![Metrics before and after upgrade to 512MB with 1M invocations over a 7 day period](https://github.com/chadkluck/npm-chadkluck-cache-data/assets/17443749/0ec98af5-edcf-4e2a-8017-dd17b9c7a11c)
44
-
45
- If you are worried about cost, the Lambda function demonstrated above handles approximately 4.6 million requests a month, each averaging 46ms in Lambda run time. This means that the Lambda function executes a total of 211,000 seconds a month which is still within the 400,000 seconds provided by the Free Tier. If there was no free tier, the cost would have been around USD $2.00.
46
-
47
- #### Lambda Environment Variables and Execution Role
48
-
49
- Below are some typical settings for use with Cache-Data
50
-
51
- ```yaml
52
- Resources:
53
-
54
- # API Gateway
55
-
56
- WebApi:
57
- Type: AWS::Serverless::Api
58
- Properties:
59
- Name: !Sub '${APPLICATION-API}-WebApi'
60
- StageName: !Ref ApiPathBase
61
- PropagateTags: True
62
- TracingEnabled: True
63
- OpenApiVersion: 3.0.0
64
-
65
- # Lambda Function
66
-
67
- AppFunction:
68
- Type: AWS::Serverless::Function
69
- Properties:
70
- # ...
71
- Runtime: nodejs22.x
72
- MemorySize: 1028
73
- Role: !GetAtt LambdaExecutionRole.Arn
74
-
75
- # Lambda Insights and X-Ray
76
- Tracing: Active # X-Ray
77
- # Required layers for 1) XRay and Lambda Insights, and 2) AWS Secrets Manager and Parameter Store Extension
78
- Layers:
79
- - !Sub "arn:aws:lambda:${AWS::Region}:${ACCT_ID_FOR_AWS_INSIGHTS_EXT}:layer:LambdaInsightsExtension:52" # Check for latest version: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versionsx86-64.html
80
- - !Sub "arn:aws:lambda:${AWS::Region}:${ACCT_ID_FOR_AWS_PARAM_AND_SECRETS_EXT}:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11" # https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html#ps-integration-lambda-extensions-add
81
-
82
- Environment:
83
- Variables:
84
-
85
- NODE_ENV: !If [ IsProduction, "production", "development"] # Note we are past the build installation phase so devDependencies are not installed, however, we can utilize certain tools in development mode
86
- DEPLOY_ENVIRONMENT: !Ref DeployEnvironment # PROD, TEST, DEV - a different category of environment
87
- LOG_LEVEL: !If [ IsProduction, "0", "5"] # 0 for prod, 2-5 for non-prod
88
- PARAM_STORE_PATH: "/" # SSM Parameter store can use a hierarchy to organize your apps parameters
89
-
90
- # Cache-Data settings (from: https://www.npmjs.com/package/@63klabs/cache-data)
91
- CACHE_DATA_DYNAMO_DB_TABLE: !Ref CacheDataDynamoDbTable
92
- CACHE_DATA_S3_BUCKET: !Ref CacheDataS3Bucket
93
- CACHE_DATA_SECURE_DATA_ALGORITHM: !Ref CacheDataCryptSecureDataAlg
94
- CACHE_DATA_ID_HASH_ALGORITHM: !Ref CacheDataCryptIdHashAlgorithm
95
- CACHE_DATA_DYNAMO_DB_MAX_CACHE_SIZE_KB: !Ref CacheDataDbMaxCacheSizeInKB
96
- CACHE_DATA_PURGE_EXPIRED_CACHE_ENTRIES_AFTER_X_HRS: !Ref CacheDataPurgeExpiredCacheEntriesInHours
97
- CACHE_DATA_ERROR_EXP_IN_SECONDS: !Ref CacheDataErrorExpirationInSeconds
98
- CACHE_DATA_TIME_ZONE_FOR_INTERVAL: !Ref CacheDataTimeZoneForInterval
99
- CACHE_DATA_AWS_X_RAY_ON: !Ref CacheDataAWSXRayOn
100
-
101
- # -- LambdaFunction Execution Role --
102
-
103
- LambdaExecutionRole:
104
- Type: AWS::IAM::Role
105
- Properties:
106
- RoleName: !Sub "${LAMBDA_EXECUTION_ROLE_NAME}-ExecutionRole"
107
- Description: "IAM Role that allows the Lambda permission to execute and access resources"
108
- Path: /
109
-
110
- AssumeRolePolicyDocument:
111
- Statement:
112
- - Effect: Allow
113
- Principal:
114
- Service: [lambda.amazonaws.com]
115
- Action: sts:AssumeRole
116
-
117
- # These are for application monitoring via LambdaInsights and X-Ray
118
- ManagedPolicyArns:
119
- - 'arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy'
120
- - 'arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess'
121
-
122
- # These are the resources your Lambda function needs access to
123
- # Logs, SSM Parameters, DynamoDb, S3, etc.
124
- # Define specific actions such as get/put (read/write)
125
- Policies:
126
- - PolicyName: LambdaResourceAccessPolicies
127
- PolicyDocument:
128
- Statement:
129
-
130
- - Sid: LambdaAccessToWriteLogs
131
- Action:
132
- - logs:CreateLogGroup
133
- - logs:CreateLogStream
134
- - logs:PutLogEvents
135
- Effect: Allow
136
- Resource: !GetAtt AppLogGroup.Arn
137
-
138
- # cache-data Parameter Read Access (from: https://www.npmjs.com/package/@63klabs/cache-data)
139
- - Sid: LambdaAccessToSSMParameters
140
- Action:
141
- - ssm:DescribeParameters
142
- - ssm:GetParameters
143
- - ssm:GetParameter
144
- - ssm:GetParametersByPath
145
- Effect: Allow
146
- Resource:
147
- - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${ParameterStoreHierarchy}*"
148
-
149
- # cache-data S3 bucket (from: https://www.npmjs.com/package/@63klabs/cache-data)
150
- - Sid: LambdaAccessToS3BucketCacheData
151
- Action:
152
- - s3:PutObject
153
- - s3:GetObject
154
- - s3:GetObjectVersion
155
- Effect: Allow
156
- Resource: !Join [ '', [ !GetAtt CacheDataS3Bucket.Arn, '/cache/*' ] ]
157
-
158
- # cache-data DynamoDb table (from: https://www.npmjs.com/package/@63klabs/cache-data)
159
- - Sid: LambdaAccessToDynamoDBTableCacheData
160
- Action:
161
- - dynamodb:GetItem
162
- - dynamodb:Scan
163
- - dynamodb:Query
164
- - dynamodb:BatchGetItem
165
- - dynamodb:PutItem
166
- - dynamodb:UpdateItem
167
- - dynamodb:BatchWriteItem
168
- Effect: Allow
169
- Resource: !GetAtt CacheDataDynamoDbTable.Arn
170
-
171
- ```
172
-
173
- The example definition above uses the following parameters. You can either add these to your template's `Parameters` section or hard-code values for your environment variables using the specifications outlined.
174
-
175
- ```yaml
176
- Parameters:
177
-
178
-
179
- # ---------------------------------------------------------------------------
180
- # Cache-Data Parameters
181
- # From: https://www.npmjs.com/package/@63klabs/cache-data
182
-
183
- CacheDataDbMaxCacheSizeInKB:
184
- Type: Number
185
- Description: "DynamoDb does better when storing smaller pieces of data. Choose the cut-off in KB that large objects should be stored in S3 instead (10)"
186
- Default: 10
187
- MinValue: 10
188
- MaxValue: 200
189
- ConstraintDescription: "Numeric value between 10 and 200 (inclusive)"
190
- CacheDataCryptIdHashAlgorithm:
191
- Type: String
192
- Description: "Hash algorithm used for generating the URI ID to identify cached requests. This is for generating IDs, not crypto."
193
- Default: "RSA-SHA256"
194
- AllowedValues: ["RSA-SHA256", "RSA-SHA3-224", "RSA-SHA3-256", "RSA-SHA3-384", "RSA-SHA3-512"]
195
- ConstraintDescription: "Use possible hashes available from Node.js in the RSA- category (RSA-SHA256 to RSA-SM3)"
196
- CacheDataCryptSecureDataAlg:
197
- Type: String
198
- Description: "Cryptographic algorithm to use for storing sensitive cached data in S3 and DynamoDb"
199
- Default: "aes-256-cbc"
200
- AllowedValues: ["aes-256-cbc", "aes-256-cfb", "aes-256-cfb1", "aes-256-cfb8", "aes-256-ofb"]
201
- ConstraintDescription: "Use possible cipher algorithms available (crypto.getCiphers()) from Node.js in the aes-256-xxx category"
202
- CacheDataErrorExpirationInSeconds:
203
- Type: Number
204
- Description: "How long should errors be cached? This prevents retrying a service that is currently in error too often (300 is recommended)"
205
- Default: 300
206
- MinValue: 1
207
- ConstraintDescription: "Choose a value of 1 or greater"
208
- CacheDataPurgeExpiredCacheEntriesInHours:
209
- Type: Number
210
- Description: "The number of hours expired cached data should be kept before purging. Expired cache data may be used if the source returns an error."
211
- Default: 24
212
- MinValue: 1
213
- ConstraintDescription: "Choose a value of 1 or greater"
214
- CacheDataPurgeAgeOfCachedBucketObjInDays:
215
- Type: Number
216
- Description: "Similar to CacheData_PurgeExpiredCacheEntriesInHours, but for the S3 Bucket. S3 calculates from time object is created/last modified (not accessed). This should be longer than your longest cache expiration set in custom/policies. Keeping objects in S3 for too long increases storage costs. (30 is recommended)"
217
- Default: 15
218
- MinValue: 3
219
- ConstraintDescription: "Choose a value of 3 days or greater. This should be slightly longer than the longest cache expiration expected"
220
- CacheDataTimeZoneForInterval:
221
- Type: String
222
- Description: "Cache-Data may expire using an interval such as every four, six, twelve, ... hours on the hour starting at midnight. What timezone holds the midnight to calculate from?"
223
- Default: "Etc/UTC"
224
- AllowedValues: ["Etc/UTC", "America/Puerto_Rico", "America/New_York", "America/Indianapolis", "America/Chicago", "America/Denver", "America/Phoenix", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu"] # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
225
- ConstraintDescription: "Common examples for United States of America. Accepted values can be changed in the template for your region."
226
- CacheDataAWSXRayOn:
227
- Type: String
228
- Description: "Turn on AWS XRay tracing for Cache-Data"
229
- Default: "false"
230
- AllowedValues: ["true", "false"]
231
- ConstraintDescription: "Accepted values are true or false"
232
- ```
233
-
234
- #### Cache-Data DynamoDb and S3 CloudFormation Resource Templates
235
-
236
- Use the following as an example for creating your DynamoDb and S3 cache storage locations.
237
-
238
- ```yaml
239
- Resources:
240
-
241
- # ... all your other resources
242
- # ... make sure your Lambda function has between 512MB and 1024MB allocated (256MB minimum)
243
- # ... also make sure you added environment variables to your Lambda function
244
- # ... and make sure your Lambda Execution Role grants access to your DynamoDb and S3 buckets
245
-
246
- # ---------------------------------------------------------------------------
247
- # Cache-Data
248
- # From: https://www.npmjs.com/package/@63klabs/cache-data
249
- # Your Lambda function will need access via the Execution Role
250
-
251
- # -- Cache-Data DynamoDb Table --
252
-
253
- CacheDataDynamoDbTable:
254
- Type: AWS::DynamoDB::Table
255
- Description: Table to store Cache-Data.
256
- Properties:
257
- TableName: !Sub '${YOUR-DYNAMODB-TABLE}-CacheData'
258
- AttributeDefinitions:
259
- - AttributeName: "id_hash"
260
- AttributeType: "S"
261
- KeySchema:
262
- - AttributeName: "id_hash"
263
- KeyType: "HASH"
264
- TimeToLiveSpecification:
265
- AttributeName: "purge_ts"
266
- Enabled: true
267
- BillingMode: "PAY_PER_REQUEST"
268
-
269
-
270
- # -- Cache-Data S3 Bucket --
271
-
272
- CacheDataS3Bucket:
273
- Type: AWS::S3::Bucket
274
- Description: S3 Bucket to store Cache-Data too big for DynamoDb. Cache-Data stores objects in /cache directory. The application may store additional data outside of the cache directory.
275
- Properties:
276
- BucketName: !Sub "${YOUR-BUCKET-NAME}-cachedata"
277
- PublicAccessBlockConfiguration:
278
- BlockPublicAcls: true
279
- BlockPublicPolicy: true
280
- IgnorePublicAcls: true
281
- RestrictPublicBuckets: true
282
- BucketEncryption:
283
- ServerSideEncryptionConfiguration:
284
- - ServerSideEncryptionByDefault:
285
- SSEAlgorithm: AES256
286
- LifecycleConfiguration:
287
- Rules:
288
- - Id: "ExpireObjects"
289
- AbortIncompleteMultipartUpload:
290
- DaysAfterInitiation: 1
291
- ExpirationInDays: !Ref CacheDataPurgeAgeOfCachedBucketObjInDays
292
- Prefix: "cache" # this will limit this policy to YOURBUCKETNAME/cache/*
293
- NoncurrentVersionExpirationInDays: !Ref CacheDataPurgeAgeOfCachedBucketObjInDays
294
- Status: "Enabled" # Enable only if you are going to use this LifecycleConfiguration
295
-
296
- # -- Cache-Data S3 Bucket Policy --
297
-
298
- CacheDataS3BucketPolicy:
299
- Type: AWS::S3::BucketPolicy
300
- Properties:
301
- Bucket: !Ref CacheDataS3Bucket
302
- PolicyDocument:
303
- Version: "2012-10-17"
304
- Id: SecurityPolicy
305
- Statement:
306
- - Sid: "DenyNonSecureTransportAccess"
307
- Effect: Deny
308
- Principal: "*"
309
- Action: "s3:*"
310
- Resource:
311
- - !GetAtt CacheDataS3Bucket.Arn
312
- - !Join [ '', [ !GetAtt CacheDataS3Bucket.Arn, '/*' ] ]
313
- Condition:
314
- Bool:
315
- "aws:SecureTransport": false
316
-
317
- ```
318
-
319
- #### Install npm Package and Add Starter Code
320
-
321
- 1. Go to your application directory
322
- 2. Run the command `npm i @63klabs/cache-data`
323
- 3. Add `const { tools, cache, endpoint } = require('@63klabs/cache-data');` to your script
324
- 4. Add a Config class to your script. This should be placed before the handler so it only is defined during cold starts. This can also be imported from a separate script.
325
- 5. Add `Config.init()` after the Config class but before the handler.
326
- 5. Add `await tools.Config.promise();` in the handler to make sure the Config has completed.
327
-
328
- ```javascript
329
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
330
-
331
- class Config extends tools._ConfigSuperClass {
332
- static async init() {
333
- tools._ConfigSuperClass._promise = new Promise(async (resolve, reject) => {
334
- resolve(true);
335
- };
336
- };
337
-
338
- /* initialize the Config */
339
- obj.Config.init();
340
-
341
- exports.handler = async (event, context, callback) => {
342
- await tools.Config.promise();
343
- let response = {
344
- statusCode: 200,
345
- body: JSON.stringify({message: "Hello from Lambda!"}),
346
- headers: {'content-type': 'application/json'}
347
- };
348
-
349
- callback(null, response);
350
- }
351
- ```
352
-
353
- Note: `deployEnvironment` is only one of the possible runtime environment variables the script checks for. You may also use `env`, `deployEnvironment`, `environment`, or `stage`. Also note the confusion that may be had when we are talking about "environment" as it refers to both Lambda Runtime Environment Variables as well as a variable denoting a Deployment Environment (Production, Development, Testing, etc.).
354
-
355
- (Runtime Environment variables are accessed using `process.env.`_`variableName`_.)
356
-
357
- ### Usage
358
-
359
- Note: There is a sample app and tutorial that works with a CI/CD pipeline available at the repository: [serverless-webservice-template-for-pipeline-atlantis](https://github.com/chadkluck/serverless-webservice-template-for-pipeline-atlantis)
360
-
361
- #### Config
362
-
363
- ##### Parameters and Secrets
364
-
365
- Cache-Data requires an 32 character hexidecimal key to encrypt data when at rest. This can be stored in an SSM Parameter named `crypt_secureDataKey`.
366
-
367
- You have two options for storing and retrieving your SSM Parameters:
368
-
369
- 1. Using the Cache-Data SSM Parameter access function.
370
- 2. Using the AWS Parameter and Secrets Lambda Extension.
371
-
372
- Both are easily accessed using functions in the Cache-Data toolkit.
373
-
374
- ###### Option 1: Cache-Data SSM Parameter access function
375
-
376
- This runs in the Config.init() function and can be used to retrieve all of the parameters needed for your application.
377
-
378
- ```javascript
379
- class Config extends tools._ConfigSuperClass {
380
- static async init() {
381
-
382
- tools._ConfigSuperClass._promise = new Promise(async (resolve, reject) => {
383
-
384
- try {
385
-
386
- let params = await this._initParameters(
387
- [
388
- {
389
- "group": "app", // so we can do params.app.weatherapikey later
390
- "path": process.env.PARAM_STORE_PATH
391
- }
392
- ]
393
- );
394
-
395
- // You can access within init() using params.app.crypt_secureDataKey
396
-
397
- resolve(true);
398
- } catch(error) {
399
- reject(null);
400
- }
401
- });
402
- }
403
- }
404
- ```
405
-
406
- Accesses the SSM Parameter Store and places any parameters found under `/apps/my_cool_app/` into a `params.app` variable. You'll see that the cache initialization uses `params.app.crypt_secureDataKey` which is the parameter we created under `/apps/my_cool_app/crypt_secureDataKey`.
407
-
408
- New deployments and new concurrent instances will pick up changes to a Parameter value, but long-running instances will not. If you change the value of a Parameter then you need to redeploy the application in order to clear out any use of the old value.
409
-
410
- ###### Option 2: AWS Parameter and Secrets Lambda Extension
411
-
412
- This is a more robust option and works with Secrets Manager as well. It requires the installation of a Lambda layer and then use of the `CachedSecret` and/or `CachedSSMParameter` Class from the Cache-Data tool-kit.
413
-
414
- Another advantage is that unlike the previous method, this method will pick up on any Secret and Parameter value changes and begin using the new values within 5 minutes (unless you set the cache for longer).
415
-
416
- First, make sure you install the Lambda layer:
417
-
418
- ```yaml
419
- Resources:
420
-
421
- AppFunction:
422
- Type: AWS::Serverless::Function
423
- Properties:
424
- # ...
425
- Layers:
426
- - !Sub "arn:aws:lambda:${AWS::Region}:${ACCT_ID_FOR_AWS_PARAM_AND_SECRETS_EXT}:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11" # https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html#ps-integration-lambda-extensions-add
427
- ```
428
-
429
- Next, in your code, create the object to store your Parameter or Secret.
430
-
431
- ```javascript
432
- const myKey = new tools.CachedSSMParameter('appSecretKey', {refreshAfter: 1600});
433
- ```
434
-
435
- Finally, make sure you prime() and await the value.
436
-
437
- ```javascript
438
- myKey.prime(); // request in the background so you can do other things before using it.
439
-
440
- // ... do many things
441
-
442
- let password = await myKey.getValue();
443
- ```
444
-
445
- If you place it within an object you can also stringify that object and it will replace the reference with the secret. (You need to await the .prime() before processing)
446
-
447
- ```javascript
448
- myKey.prime(); // request in the background so you can do other things before using it.
449
-
450
- // ... do many things
451
- const dbconn = { username: myUsername, password: myKey };
452
-
453
- await myKey.prime();
454
- connect(JSON.parse(JSON.stringify(dbconn)));
455
-
456
- // or use toString()
457
- await myKey.prime();
458
- connect( {
459
- username: `${myUsername}`,
460
- password: `${myKey}`
461
- })
462
- ```
463
-
464
- ##### Connections, and Cache
465
-
466
- The cache object acts as an intermediary between your application and your data (whether it be a remote endpoint or other storage/process mechanism).
467
-
468
- Before you can use Parameter Store, S3, and DynamoDb for the cache, they need to be set up with the proper access granted to your Lambda function.
469
-
470
- 1. Set up an S3 bucket (Your application will store cache data in `/cache`)
471
- 2. Create a DynamoDb table
472
- 3. Create a Parameter in SSM Parameter store `/app/my_cool_app/crypt_secureDataKey` and set the secret text to a 64 character length hex value. (64 hex characters because we are using a 256 bit key and cipher (`aes-256-ofb`)in the example below)
473
- 4. Make sure you set up IAM policies to allow you Lambda function access to the S3 bucket, DynamoDb table, and SSM Parameter store.
474
-
475
- Once the S3 bucket, DynamoDb table, and SSM Parameter are set up we can focus on your Lambda function.
476
-
477
- During your application initialization (but not for each request) we need to initialize the Config object.
478
-
479
- The class below will do the following three things:
480
-
481
- 1. Bring in the secret key (and other parameters) from SSM Parameter Store.
482
- 2. Create connections with cache settings for each connection
483
- 3. Initialize the Cache
484
-
485
- This code can be put into a separate file and brought in using a `require` statement. It should be scoped to the highest level of your Lambda function and not in the request handler.
486
-
487
- ```js
488
- /* EXAMPLE USING the this._initParameters method of obtaining parameters during Config.init() */
489
-
490
- // require cache-data
491
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
492
-
493
- /**
494
- * Extends tools._ConfigSuperClass
495
- * Used to create a custom Config interface
496
- * Usage: should be placed near the top of the script file outside
497
- * of the event handler. It should be global and must be initialized.
498
- * @example
499
- * const obj = require("./classes.js");
500
- * obj.Config.init();
501
- */
502
- class Config extends tools._ConfigSuperClass {
503
-
504
- /**
505
- * This is custom inititialization code for the application. Depending
506
- * upon needs, the _init functions from the super class may be used
507
- * as needed. Init is async, and a promise is stored, allowing the
508
- * lambda function to wait until the promise is finished.
509
- */
510
- static async init() {
511
-
512
- tools._ConfigSuperClass._promise = new Promise(async (resolve, reject) => {
513
-
514
- try {
515
-
516
- let params = await this._initParameters(
517
- [
518
- {
519
- "group": "app", // so we can do params.app.weatherapikey later
520
- "path": "/apps/my_cool_app/" // process.env.PARAM_STORE_PATH // or store as a Lambda environment variable
521
- }
522
- ]
523
- );
524
-
525
- // after we have the params, we can set the connections
526
- let connections = new tools.Connections();
527
-
528
- /* NOTE: instead of hard coding connections, you could import
529
- from a connections file and then add in any additional values
530
- such as keys from the Param store
531
- */
532
-
533
- // for games demo from api.chadkluck.net
534
- connections.add( {
535
- name: "demo",
536
- host: "api.chadkluck.net",
537
- path: "/games",
538
- parameters: {},
539
- headers: {
540
- referer: "https://chadkluck.net"
541
- },
542
- cache: [
543
- {
544
- profile: "games",
545
- overrideOriginHeaderExpiration: true, // if the endpoint returns an expiration, do we ignore it for our own?
546
- defaultExpirationInSeconds: (10 * 60),// , // 10 minutes
547
- expiresIsOnInterval: true, // for example, a 10 min cache can expire on the hour, 10, 20, 30... after. 24 hour cache can expire at midnight. 6 hour cache can expire at 6am, noon, 6pm, and midnight
548
- headersToRetain: "", // what headers from the endpoint do we want to keep with the cache data?
549
- hostId: "demo", // log entry friendly (or not)
550
- pathId: "games", // log entry friendly (or not)
551
- encrypt: false // you can set this to true and it will use the key from param store and encrypt data at rest in S3 and DynamoDb
552
- }
553
- ]
554
- } );
555
-
556
- tools._ConfigSuperClass._connections = connections;
557
-
558
- // Cache settings
559
- cache.Cache.init({
560
- dynamoDbTable: "yourDynamoDbTable", // replace with the name of a DynamoDb table to store cached data
561
- s3Bucket: "yourS3Bucket", // replace with a bucket name to store cache data. Data will be stored in /cache in yourS3Bucket
562
- secureDataAlgorithm: "aes-256-ofb", // how do we encrypt data at rest
563
- secureDataKey: Buffer.from(params.app.crypt_secureDataKey, "hex"), // using the parameter from above during Config.init()
564
- //secureDataKey: new tools.CachedSSMParameter('/apps/my_cool_app/CacheData_SecureDataKey', {refreshAfter: 300}), // if using tools.CachedSSMParameter()
565
- idHashAlgorithm: "RSA-SHA3-512", // the alg used to create a unique hash identifier for requests so we can tell them apart in the cache
566
- DynamoDbMaxCacheSize_kb: 20, // data larger than this (in KB) will be stored in S3 to keep DynamoDb running efficently
567
- purgeExpiredCacheEntriesAfterXHours: 24, // expired caches hang around for a while before we purge just in case there is cause to fall back on them
568
- defaultExpirationExtensionOnErrorInSeconds: 300, // so as to not overwhelm a down endpoint, or to not cache an error for too long, how often should we check back?
569
- timeZoneForInterval: "America/Chicago" // if caching on interval, we need a timezone to account for calculating hours, days, and weeks. List: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
570
- });
571
-
572
- resolve(true);
573
- } catch (error) {
574
- tools.DebugAndLog.error("Could not initialize Config", error);
575
- reject(false);
576
- };
577
-
578
- });
579
-
580
- };
581
- };
582
- ```
583
-
584
- The `connection` code above does the following:
585
-
586
- 1. Defines the host and path (and any parameters and headers to send to a remote endpoint)
587
- 2. Defines the cache settings for that remote endpoint (note, these are only cache settings for that remote endpoint and not the overall cache)
588
-
589
- Additional connections may be added using additional `connections.add()` functions.
590
-
591
- The `cache` code does the following:
592
-
593
- 1. Sets the DynamoDb table and S3 bucket to store cached data
594
- 2. Sets the algorithm to securely encrypt data at rest in DynamoDb and S3
595
- 3. Sets the hash algorithm used to create a unique id for each unique request
596
- 4. How big of an object do we save in DynamoDb before storing it in S3? (20K objects are ideal, anything bigger is in S3)
597
- 5. How long to wait before purging expired entries (they aren't purged right away but kept in case of errors)
598
- 6. If there is an error getting fresh data, how long do we extend any existing cache? (so we can back off while endpoint is in error)
599
- 7. Set the time zone for intervals. For example, we can expire on the hour (8am, 12pm, 8pm, etc) but if we expire at the end of the day, when is the "end of the day"? Midnight where? If empty it will be UTC.
600
-
601
- Each of these are described in their own sections below.
602
-
603
- Note that it is probably best to not hard code values but instead bring them in as environment variables from your Lambda function.
604
-
605
- Next, we need to call the initialization in our application, and before the handler can be executed, make sure the promise has resolved.
606
-
607
- ```js
608
- // note that the Config object is defined in the code above
609
-
610
- /* initialize the Config */
611
- Config.init(); // we need to await completion in the async call function
612
-
613
- /**
614
- * Lambda function handler
615
- */
616
- exports.handler = async (event, context, callback) => {
617
-
618
- /* wait for CONFIG to be settled as we need it before continuing. */
619
- await Config.promise();
620
-
621
- /* Process the request and wait for result */
622
- const response = await someFunction(event, context); // some code or function that generates a response
623
-
624
- /* Send the result back to API Gateway */
625
- callback(null, response);
626
-
627
- }
628
- ```
629
-
630
- Note that you will replace `someFunction()` with your own function that will call and process the data from cache as in the example below.
631
-
632
- Once the `Config` object is initialized, the following code can be used to access data through the cache.
633
-
634
- ```js
635
- /*
636
- Note that cache object was already set by the require statement
637
- assuming:
638
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
639
- */
640
-
641
- let connection = Config.getConnection("demo"); // corresponds with the name we gave it during connections.add()
642
- let conn = connection.toObject(); // we'll "extract" the connection data. .toObject() will create a clone of the data so we can modify if need be
643
-
644
- let cacheProfile = connection.getCacheProfile("games"); // corresponds with the cache profile we gave within demo for connections.add()
645
-
646
- const cacheObj = await cache.CacheableDataAccess.getData(
647
- cacheProfile, // this is your cache profile for an endpoint, included from connection object
648
- endpoint.getDataDirectFromURI, // this is the function you want to invoke to get fresh data if the cache is stale. (conn and null will be passed to it)
649
- conn, // connection information which will be passed to endpoint.getDataDirectFromURI() to get fresh data. Also used to identify the object in cache
650
- null // this parameter can be used to pass additional data to endpoint.getDataDirectFromURI (or any other DAO)
651
- );
652
-
653
- let games = cacheObj.getBody(true); // return the data as an object (true) instead of a string (false). You could use false if you want to keep the data as a string (as in xml or html or text)
654
- ```
655
-
656
- In order to do its job it needs to:
657
-
658
- 1. Know how to access the data. We use a Connection object from Config to do this. You can think of a Connection object as all the pieces of an HTTP request. It identifies the protocol, domain, path, query string, headers, etc. (However, it doesn't have to be an HTTP request.)
659
- 2. Know the function to use to access fresh data from the remote endpoint. Using the Connection object, your can either use a built in HTTP request, or define your own method for processing an http request or other data source.
660
- 3. Know the cache policy for the data. We use a Cache object to do this. It is an object that has information on expiration, headers to save with the data, where cache data is stored, stored data encryption protocol,
661
-
662
- ### cache.CacheableDataAccess.getData() without Connection
663
-
664
- Note that you can use `cache.CacheableDataAccess.getData()` without a Connection object. You'll notice that we "extract" the connection data from `connection` using `.toObject()`. We do this not just because it creates an object that isn't a reference (thus allowing us to ad hoc modify things like path or parameters without changing the original) but also because any object with any structure may be passed (as long as your passed function is expecting it).
665
-
666
- The `cacheProfile` variable is also just an object, but must adhere to the structure outlined in the cache declaration previously shown.
667
-
668
- You can create the cache configuration and connection on the fly without the Connection object:
669
-
670
- ```js
671
- const cacheProfile ={
672
- overrideOriginHeaderExpiration: true,
673
- defaultExpirationExtensionOnErrorInSeconds: 3600,
674
- defaultExpirationInSeconds: (10 * 60), // 10 minutes
675
- expiresIsOnInterval: true,
676
- headersToRetain: ['x-data-id', 'x-data-sha1'],
677
- hostId: "example",
678
- pathId: "person",
679
- encrypt: true
680
- };
31
+ 1. Generate Secret Key to Encrypt Cache:
32
+ - Use the [key generation script](./docs/00-example-implementation/generate-put-ssm.py) during [the build](./docs/00-example-implementation/example-buildspec.yml) to establish a key to encrypt your data.
33
+ 2. Lambda CloudFormation Template:
34
+ - See [Lambda template example](./docs/00-example-implementation/example-template-lambda-function.yml)
35
+ - Node: AWS Lambda supported version of Node
36
+ - Memory: Allocate at least 256MB (512-1024MB recommended)
37
+ - Environment Variables: Add the cache-data environment variables to your Lambda function.
38
+ - Execution Role: Include access to S3 and DynamoDb in your Lambda's execution role.
39
+ 3. S3 and DynamoDb CloudFormation Template to store your cache:
40
+ - See [S3 and DynamoDb Cache Store template example](./docs/00-example-implementation/example-template-s3-and-dynamodb-cache-store.yml)
41
+ - Include in your application infrastructure template or as separate infrastructure.
42
+ 4. Install the @63klabs/cache-data package:
43
+ - `npm install @63klabs/cache-data`
44
+ 5. Add code to your Lambda function to utilize caching and other cache-data utilities:
45
+ - See [example code for index and handler](./docs/00-example-implementation/example-handler.js)
46
+ - See [example code for config initialization](./docs/00-example-implementation/example-config.js)
47
+
48
+ It is recommended that you use the quick-start method when implementing for the first time. It comes with default values and requires less CloudFormation yaml and Node code.
49
+
50
+ - [Quick Start Implementation](./docs/00-quick-start-implementation/README.md)
51
+ - [Advanced Implementation for Providing a Web Service](./docs/01-advanced-implementation-for-web-service/README.md)
52
+ - [Additional Documentation](./docs/README.md)
681
53
 
682
- const conn = {
683
- host: "api.example.com",
684
- path: "/person",
685
- parameters: {id: id, event: event },
686
- headers: {}
687
- };
688
-
689
- const cacheObj = await cache.CacheableDataAccess.getData(
690
- cacheProfile,
691
- myCustomDAO_getData,
692
- conn,
693
- null
694
- );
695
- ```
696
-
697
- ### Connections using CachedSSMParameter or CachedSecret
698
-
699
- Creating a connection is similar to above, we can add an authorization property to the conection:
700
-
701
- ```js
702
- authentication: {
703
- parameters: {
704
- apikey: new tools.CachedSSMParameter('/apps/my_cool_app/demoAPIkey', {refreshAfter: 300}),
705
- }
706
- }
707
- ```
708
-
709
- Learn more about Connection Authentication below.
710
-
711
- And when calling Cache.init(), pass a CachedSSMParameter (or CachedSecret) to the secureDataKey property:
712
-
713
- ```js
714
- secureDataKey: new tools.CachedSSMParameter('/apps/my_cool_app/CacheData_SecureDataKey', {refreshAfter: 1600}),
715
- ```
716
-
717
- ```js
718
- // for games demo from api.chadkluck.net
719
- connections.add( {
720
- name: "demo",
721
- host: "api.chadkluck.net",
722
- path: "/games",
723
- parameters: {},
724
- headers: {
725
- referer: "https://chadkluck.net"
726
- },
727
- authentication: {
728
- parameters: {
729
- apikey: new tools.CachedSSMParameter('/apps/my_cool_app/demoAPIkey', {refreshAfter: 300}), // ADDED
730
- }
731
- },
732
- cache: myCacheProfilesArray
733
- } );
734
-
735
- tools._ConfigSuperClass._connections = connections;
736
-
737
- // Cache settings
738
- cache.Cache.init({
739
- dynamoDbTable: "yourDynamoDbTable",
740
- s3Bucket: "yourS3Bucket",
741
- secureDataAlgorithm: "aes-256-ofb",
742
- secureDataKey: new tools.CachedSSMParameter('/apps/my_cool_app/CacheData_SecureDataKey', {refreshAfter: 1600}), // CHANGED FROM params.app
743
- idHashAlgorithm: "RSA-SHA3-512",
744
- DynamoDbMaxCacheSize_kb: 20,
745
- purgeExpiredCacheEntriesAfterXHours: 24,
746
- defaultExpirationExtensionOnErrorInSeconds: 300,
747
- timeZoneForInterval: "America/Chicago"
748
- });
749
-
750
- ```
751
-
752
- ### Connections Authentication
753
-
754
- You can store your authentication methods separate from the headers, parameters, and body properties. You can also use Basic authorization.
755
-
756
- Just add an `authentication` property to your connection.
757
-
758
- ```js
759
- // for games demo from api.chadkluck.net
760
- connections.add( {
761
- name: "demo",
762
- host: "api.chadkluck.net",
763
- path: "/games",
764
- headers: {
765
- referer: "https://chadkluck.net"
766
- },
767
- authentication: {
768
- parameters: {
769
- apikey: new tools.CachedSSMParameter('/apps/my_cool_app/demoAPIkey', {refreshAfter: 1600}), // ADDED
770
- }
771
- },
772
- cache: myCacheProfilesArray
773
- } );
774
-
775
- connections.add( {
776
- name: "demoauthbasic",
777
- host: "api.chadkluck.net",
778
- path: "/games",
779
- headers: {
780
- referer: "https://chadkluck.net"
781
- },
782
- authentication: {
783
- basic: {
784
- username: new tools.CachedSSMParameter('/apps/my_cool_app/demoUsername', {refreshAfter: 300}),
785
- password: new tools.CachedSSMParameter('/apps/my_cool_app/demoPassword', {refreshAfter: 300}),
786
- }
787
- },
788
- cache: myCacheProfilesArray
789
- } );
790
-
791
- connections.add( {
792
- name: "demoauthheaders",
793
- host: "api.chadkluck.net",
794
- path: "/games",
795
- headers: {
796
- referer: "https://chadkluck.net"
797
- },
798
- authentication: {
799
- headers: {
800
- 'x-api-key': new tools.CachedSSMParameter('/apps/my_cool_app/apiKey', {refreshAfter: 300})
801
- }
802
- },
803
- cache: myCacheProfilesArray
804
- } );
805
-
806
- connections.add( {
807
- name: "demoauthbody",
808
- host: "api.chadkluck.net",
809
- path: "/games",
810
- headers: {
811
- referer: "https://chadkluck.net"
812
- },
813
- authentication: {
814
- body: {
815
- 'x-api-key': new tools.CachedSSMParameter('/apps/my_cool_app/apiKey', {refreshAfter: 300}),
816
- 'account': new tools.CachedSSMParameter('/apps/my_cool_app/accountId', {refreshAfter: 3600})
817
- }
818
- },
819
- cache: myCacheProfilesArray
820
- } );
821
- ```
822
-
823
- ### Connections Options
824
-
825
- Specify a `timeout` in the connection to pass to the http_get command. Default is `8000`.
826
-
827
- Specify how duplicate parameters in a query string should be handled. This allows you to craft your query string to match what your endpoint expects when it parses the query string.
828
-
829
- ```javascript
830
- connections.add({
831
- method: "POST",
832
- host: "api.chadkluck.net",
833
- path: "/echo/",
834
- headers: headers,
835
- uri: "",
836
- protocol: "https",
837
- body: null,
838
- parameters: {
839
- greeting: "Hello",
840
- planets: ["Earth", "Mars"]
841
- },
842
- options: {
843
- timeout: 8000,
844
- separateDuplicateParameters: false, // default is false
845
- separateDuplicateParametersAppendToKey: "", // "" "[]", or "0++", "1++"
846
- combinedDuplicateParameterDelimiter: ','
847
- }
848
- })
849
- ```
850
-
851
- By default the query string used for the request will be:
852
-
853
- ```text
854
- ?greeting=Hello&planets=Earth,Mars
855
- ```
856
-
857
- However, by changing `separateDuplicateParameters` to `true` and `separateDuplicateParametersAppendToKey` to `[]`:
858
-
859
- ```text
860
- ?greeting=Hello&planets[]=Earth&planets[]=Mars
861
- ```
862
-
863
- You can also append an index to the end of the parameter:
864
-
865
- ```javascript
866
- options = {
867
- separateDuplicateParameters: true,
868
- separateDuplicateParametersAppendToKey: "0++", // "" "[]", or "0++", "1++"
869
- }
870
- // ?greeting=Hello&planets0=Earth&planets1=Mars
871
- ```
872
-
873
- Similarly, you can start at index 1 instead of 0:
874
-
875
- ```javascript
876
- options = {
877
- separateDuplicateParameters: true,
878
- separateDuplicateParametersAppendToKey: "1++", // "" "[]", or "0++", "1++"
879
- }
880
- // ?greeting=Hello&planets1=Earth&planets2=Mars
881
- ```
882
-
883
- ### tools.Timer
884
-
885
- In its simplist form we can do the following:
886
-
887
- ```js
888
- /*
889
- Assuming:
890
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
891
- */
892
-
893
- const timerTaskGetGames = new tools.Timer("Getting games", true); // We give it a name for logging, and we set to true so the timer starts right away
894
-
895
- /* A block of code we want to execute and get timing for */
896
- // do something
897
- // do something
898
-
899
- timerTaskGetGames.stop(); // if debug level is >= 3 (DebugAndLog.DIAG) it will log the elapsed time in ms
900
- ```
901
-
902
- The above code will create a timer which we can access by the variable name `timerTaskGetGames`. Since we set the second parameter to `true` it will start the timer upon creation.
903
-
904
- Then a block of code will execute.
905
-
906
- Then we stop the timer using `.stop()` and if the logging level is 3 or greater it will send a log entry with the elapsed time to the console.
907
-
908
- You are able to get the current time elapsed in milliseconds from a running Timer by calling `const ms = timerVarName.elapsed()`
909
-
910
- ### tools.DebugAndLog
911
-
912
- ```js
913
- /*
914
- Assuming:
915
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
916
- */
917
-
918
- /* increase the log level - comment out when not needed */
919
- tools.DebugAndLog.setLogLevel(5, "2022-02-28T04:59:59Z"); // we can increase the debug level with an expiration
920
-
921
- tools.DebugAndLog.debug("Hello World");
922
- tools.DebugAndLog.msg("The sky is set to be blue today");
923
- tools.DebugAndLog.diag("Temperature log:", log);
924
-
925
- try {
926
- // some code
927
- } catch (error) {
928
- tools.DebugAndLog.error("We have an error in try/catch 1", error);
929
- }
930
-
931
- try {
932
- // some code
933
- } catch (error) {
934
- tools.DebugAndLog.warn("We have an error but will log it as a warning in try/catch 2", error);
935
- }
936
- ```
937
-
938
- Before calling `Config.init()` you can set the log level using `DebugAndLog.setLogLevel()`. If you set the log level after calling `Config.init()` OR after calling any `DebugAndLog` function, you will get an error. That is because a default log level has already been set and we will not allow the changing of the log level after a script has begun.
939
-
940
- There are six (6) logging functions.
941
-
942
- ```js
943
- DebugAndLog.error(msgStr, obj); // logs at ALL logging levels
944
- DebugAndLog.warn(msgStr, obj); // logs at ALL logging levels
945
- DebugAndLog.log(msgStr, tagStr, obj); // logs at ALL logging levels
946
- DebugAndLog.msg(msgStr, obj); // logs at level 1 and above
947
- DebugAndLog.diag(msgStr, obj); // logs at level 3 and above
948
- DebugAndLog.debug(msgStr, obj); // logs at level 5
949
- ```
950
-
951
- In the above the `obj` parameter is optional and is an object you wish to log. Be careful of logging objects that may contain sensitive information.
952
-
953
- Choose the method based on how verbose you want your logging to be at various script levels.
954
-
955
- Note that `DebugAndLog.log(msgStr, tagStr)` allows you to add a tag. If a tag is not provided `LOG` will be used and your log entry will look like `[LOG] your message`.
956
-
957
- If you provide `TEMP` as a tag ('temperature' for example) then the log entry will look something like this: `[TEMP] your message`.
958
-
959
- ## Advanced
960
-
961
- The examples above should get you started.
962
-
963
- However, there are advanced uses for the Cache object such as caching processed data not from an endpoint and creating your own Data Access Object (DAO) classes.
964
-
965
- ### Caching data not from a remote endpoint
966
-
967
- Cache does not have to be from a remote endpoint.
968
-
969
- Suppose you gather data from six endpoints and process the data in a resource and time intensive process and would like to cache the result for 6 or 24 hours. You can use the Cache object to store any data from any source, either externally or internally.
970
-
971
- The function parameter passed to the Cache object is the method used to obtain data. Remember the `endpoint.getDataDirectFromURI` from the code sample above? That is just a function to return a bare bones response from an api endpoint. (You can actually extend the `endpoint.Endpoint` class and create your own DAOs that can pre and post process data before returning to your application's cache.)
972
-
973
- Instead of passing in the function `endpoint.getDataDirectFromURI` you can create any function that will grab or process data and return an object.
974
-
975
- Remember, when passing functions for another function to execute, do not include the `()` at the end.
976
-
977
- ### Creating your own Data Access Object (DAO)
978
-
979
- You can either extend `endpoint.Endpoint` or create your own.
980
-
981
- ### Sanitize and Obfuscate functions
982
-
983
- These functions attempt to scrub items labled as 'secret', 'key', 'token' and 'Authorization' from objects for logging purposes.
984
-
985
- Sanitization is also performed on objects passed to the DebugAndLog logging functions.
986
-
987
- #### Sanitize
988
-
989
- You can pass an object to sanitize for logging purposes.
990
-
991
- NOTE: This is a tool that attempts to sanitize and may miss sensitive information. Inspect the [regular expression used for performing search](https://regex101.com/r/IJp35p/3) for more information. Care should be taken when logging objects for purposes of debugging.
992
-
993
- What it attempts to do:
994
-
995
- - Finds object keys with 'secret', 'key', and 'token' in the name and obfuscates their values.
996
- - It checks string values for key:value and key=value pairs and obfuscates the value side if the key contains the words 'secret', 'key', or 'token'. For example, parameters in a query string `https://www.example.com?client=435&key=1234EXAMPLE783271234567` would produce `https://www.example.com?client=435&key=******4567`
997
- - It checks for 'Authentication' object keys and sanitizes the value.
998
- - It checks for multi-value (arrays) of object keys named with secret, key, or token such as `"Client-Secrets":[123456789,1234567890,90987654321]`
999
-
1000
- ```JavaScript
1001
- // Note: These fake secrets are hard-coded for demo/test purposes only. NEVER hard-code secrets!
1002
- const obj = {
1003
- secret: "98765-EXAMPLE-1234567890efcd",
1004
- apiKey: "123456-EXAMPLE-123456789bcea",
1005
- kbToken: "ABCD-EXAMPLE-12345678901234567890",
1006
- queryString: "?site=456&secret=12345EXAMPLE123456&b=1",
1007
- headers: {
1008
- Authorization: "Basic someBase64EXAMPLE1234567"
1009
- }
1010
- };
1011
-
1012
- console.log("My Sanitized Object", tools.sanitize(obj));
1013
- /* output: My Sanitized Object {
1014
- secret: '******efcd',
1015
- apiKey: '******bcea',
1016
- kbToken: '******7890',
1017
- queryString: '?site=456&secret=******3456&b=1',
1018
- headers: { Authorization: 'Basic ******4567' }
1019
- }
1020
- */
1021
- ```
1022
-
1023
- > It is best to avoid logging ANY data that contains sensitive information. While this function provides an extra layer of protection, it should be used sparingly for debugging purposes (not on-going logging) in non-production environments.
1024
-
1025
- #### Obfuscate
1026
-
1027
- You can pass a string to obfuscate.
1028
-
1029
- For example, `12345EXAMPLE7890` will return `******7890`.
1030
-
1031
- By default, asterisks are used to pad the left-hand side, and only 4 characters are kept on the right. The length of the string returned is not dependent on the length of the string passed in which in turn obfuscates the original length of the string. However, the right side will not reveal more than 25% of the string (it actually rounds up 1 character so a 2 character string would still reveal the final character).
1032
-
1033
- Default options can be changed by passing an options object.
1034
-
1035
- ```JavaScript
1036
- const str = "EXAMPLE1234567890123456789";
1037
-
1038
- console.log( tools.obfuscate(str) );
1039
- // output: ******6789
1040
-
1041
- const opt = { keep: 6, char: 'X', len: 16 };
1042
- console.log( tools.obfuscate(str, opt) );
1043
- // output: XXXXXXXXXX456789
1044
- ```
1045
-
1046
- ### AWS-SDK
1047
-
1048
- The @63klabs/cache-data package will automatically detect and use the correct AWS SDK based on the version of Node.
1049
-
1050
- Node 16 environments will use AWS-SDK version 2.
1051
-
1052
- Node 18+ environments will use AWS-SDK version 3.
1053
-
1054
- Note that `package.json` for @63klabs/cache-data only installs the AWS-SDK on dev environments. This is because AWS Lambda already includes the AWS-SDK without requiring installs. This makes your application lighter and ensures you are always running the most recent SDK release. Given this, that means that AWS SDK v3 is not available in Lambda functions using Node 16, and v2 is not available in Lambda Node >=18 environments.
1055
-
1056
- Because DynamoDb, S3, and SSM Parameter store are used by cache-data, only those SDKs are included. A client is provided for each along with limited number of commands. To make gets and puts easier a get and put command is mapped for DynamoDb and S3. (Uses appropriate commands underneath for V2 and V3 so your code wouldn't need to change.)
1057
-
1058
- #### `tools.AWS` Object
1059
-
1060
- When `tools` is imported, you can use the `tools.AWS` object to perform common read/write operations on S3, DynamoDb, and SSM Parameter Store.
1061
-
1062
- ```javascript
1063
- const { tools } = require('@63klabs/cache-data');
1064
-
1065
- console.log(`NODE VERSION ${tools.AWS.NODE_VER} USING AWS SDK ${tools.AWS.SDK_VER}`);
1066
- console.log(`REGION: ${tools.AWS.REGION}`); // set from Lambda environment variable AWS_REGION
1067
-
1068
- var getParams = {
1069
- Bucket: 'mybucket', // bucket name,
1070
- Key: 'hello.txt' // object to get
1071
- }
1072
-
1073
- const result = await tools.AWS.s3.get(getParams);
1074
-
1075
- let objectData = await s3Body.transformToString(); // V3: Object bodies in V3 are readable streams, so we convert to string
1076
- // let objectData = data.Body.toString('utf-8'); // V2: Object bodies are Buffers, so we convert to string
1077
- console.log(`hello.txt Body: ${objectData}`);
1078
- // outputs "hello.txt Body: Hello, World!"
1079
-
1080
- ```
1081
-
1082
- The `tools.AWS` object provides the following:
1083
-
1084
- ```js
1085
- {
1086
- NODE_VER: '20.6.0',
1087
- NODE_VER_MAJOR: 20,
1088
- NODE_VER_MINOR: 6,
1089
- NODE_VER_PATCH: 0,
1090
- NODE_VER_MAJOR_MINOR: '20.6',
1091
- NODE_VER_ARRAY: [ 20, 6, 0 ],
1092
- REGION: "us-east-1", // Set from Node environment process.env.AWS_REGION
1093
- SDK_VER: "V3",
1094
- SDK_V2: false, // if (tools.AWS.SDK_V2) { console.log('AWS SDK Version 2!'); }
1095
- SDK_V3: true, // if (tools.AWS.SDK_V3) { console.log('AWS SDK Version 3!'); }
1096
- INFO: { /* an object containing all of the properties listed above */ }
1097
- dynamo: {
1098
- client: DynamoDBDocumentClient,
1099
- put: (params) => client.send(new PutCommand(params)), // const result = await tools.AWS.dynamo.put(params);
1100
- get: (params) => client.send(new GetCommand(params)), // const result = await tools.AWS.dynamo.get(params);
1101
- scan: (params) => client.send(new ScanCommand(params)), // const result = await tools.AWS.dynamo.scan(params);
1102
- delete: (params) => client.send(new DeleteCommand(params)), // const result = await tools.AWS.dynamo.delete(params);
1103
- update: (params) => client.send(new UpdateCommand(params)), // const result = await tools.AWS.dynamo.update(params);
1104
- sdk: {
1105
- DynamoDBClient,
1106
- DynamoDBDocumentClient,
1107
- GetCommand,
1108
- PutCommand
1109
- }
1110
- },
1111
- s3: {
1112
- client: S3,
1113
- put: (params) => client.send(new PutObjectCommand(params)), // const result = await tools.AWS.s3.put(params)
1114
- get: (params) => client.send(new GetObjectCommand(params)), // const result = await tools.AWS.s3.get(params)
1115
- sdk: {
1116
- S3,
1117
- GetObjectCommand,
1118
- PutObjectCommand
1119
- }
1120
-
1121
- },
1122
- ssm: {
1123
- client: SSMClient,
1124
- getByName: (params) => client.send(new GetParametersCommand(query)), // const params = await tools.AWS.ssm.getByName(query)
1125
- getByPath: (params) => client.send(new GetParametersByPathCommand(query)), // const params = await tools.AWS.ssm.getByPath(query)
1126
- sdk: {
1127
- SSMClient,
1128
- GetParametersByPathCommand,
1129
- GetParametersCommand
1130
- }
1131
- }
1132
- }
1133
- ```
1134
-
1135
- Because Node 16 and the AWS SDK v2 are being deprecated, this documentation will mainly cover AWS SDK v3. However, `{DynamoDb, S3, SSM}` are still available when your environment is using Node 16 and AWS SDK v2 by importing `tools` from cache-data and accessing the `AWS` class. (See Using AWS SDK V2 through tools.AWS (Deprecated) below.)
1136
-
1137
- ##### Using AWS SDK V3 through tools.AWS
1138
-
1139
- To use the AWS SDK you normally have to import the proper SDKs and libraries, create a client, and then send the commands. The way this is accomplished in version 2 and version 3 of the AWS SDK is slightly different. How to use the AWS SDK is beyond the scope of this package. However, since the package uses reads and writes to S3 objects, DynamoDb tables, and SSM Parameter store, it readily makes these commands available through the `AWS` object from `tools`.
1140
-
1141
- Also, as a shortcut as you move from Node 16 and Node 18 (and above), the methods exposed will not differ as it automatically uses the correct methods for the loaded SDK.
1142
-
1143
- To use the methds you only need to pass the parameter or query object as you normally would.
1144
-
1145
- ```javascript
1146
- // Given the two parameter/query objects:
1147
-
1148
- let paramsForPut = {
1149
- TableName: 'myTable',
1150
- Item: {
1151
- 'hash_id': '8e91cef4a27',
1152
- 'episode_name': "There's No Disgrace Like Home",
1153
- 'air_date': "1990-01-28",
1154
- 'production_code': '7G04'
1155
- }
1156
- }
1157
-
1158
- let paramsForGet = {
1159
- TableName: 'myTable',
1160
- Key: {'hash_id': '8e91cef4a27'}
1161
- };
1162
- ```
1163
-
1164
- ```javascript
1165
- // Using AWS SDK V2
1166
- const { DynamoDb } = require('aws-sdk');
1167
-
1168
- const dbDocClient = new DynamoDB.DocumentClient( {region: 'us-east-1'} );
1169
-
1170
- const dbPutResult = await dbDocClient.put(paramsForNewRecord).promise();
1171
- const dbGetResult = await dbDocClient.get(paramsForGet).promise();
1172
- ```
1173
-
1174
- ```javascript
1175
- // Using AWS SDK V3
1176
- const { DynamoDBClient} = require("@aws-sdk/client-dynamodb");
1177
- const { DynamoDBDocumentClient, GetCommand, PutCommand} = require("@aws-sdk/lib-dynamodb");
1178
-
1179
- const dbClient = new DynamoDBClient({ region: AWS.REGION });
1180
- const dbDocClient = DynamoDBDocumentClient.from(dbClient);
1181
-
1182
- const dbPutResult = await dbDocClient.send(PutCommand(paramsForNewRecord));
1183
- const dbGetResult = await dbDocClient.send(GetCommand(paramsForGetRecord));
1184
- ```
1185
-
1186
- ```javascript
1187
- // Using tools to handle the SDK version and basic calls for you
1188
- const { tools } = require('@63klabs/cache-data');
1189
-
1190
- const dbPutResult = await tools.AWS.dynamodb.put(paramsForNewRecord);
1191
- const dbGetResult = await tools.AWS.dynamodb.get(paramsForGetRecrod);
1192
- ```
1193
-
1194
- Refer to the section about the tools.AWS above for the variables, methods, and SDK objects available.
1195
-
1196
- For more on creating parameter/query objects for S3, DynamoDb, and SSM Parameter Store:
1197
-
1198
- - [Amazon S3 examples using SDK for JavaScript (v3)](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/javascript_s3_code_examples.html)
1199
- - [Using the DynamoDB Document Client](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-dynamodb-utilities.html)
1200
- - []()
1201
-
1202
- ##### Import Additional Commands
1203
-
1204
- When using AWS SDK version 3, you can import additional commands and use them with the client provided by `tools.AWS`.
1205
-
1206
- ```javascript
1207
- const { tools } = require('@63klabs/cache-data');
1208
- const { DeleteObjectCommand } = require('@aws-sdk/client-s3'); // AWS SDK v3
1209
-
1210
- const command = new DeleteObjectCommand({
1211
- Bucket: "myBucket",
1212
- Key: "good-bye.txt"
1213
- });
1214
-
1215
- const response = await tools.AWS.s3.client.send(command);
1216
- ```
1217
-
1218
- #### Using AWS SDK V2 through tools.AWS (Deprecated)
54
+ ## Help
1219
55
 
1220
- Because Node 16 and the AWS SDK v2 is being deprecated, this documentation will mainly cover AWS SDK v3. However, `{DynamoDb, S3, SSM}` are still available by importing `tools` from cache-data and accessing the `AWS` class:
56
+ Make sure you have your S3 bucket, DynamoDb table, and SSM Parameter store set up. Also make sure that you have IAM policies to allow your Lambda function access to these, and CodeBuild to read and write to SSM Parameter store.
1221
57
 
1222
- ```js
1223
- // NodeJS 16 using AWS SDK v2
1224
- const {tools} = require("@63klabs/cache-data");
58
+ Review the [Documentation](./docs/README.md) which includes implementation guides, example code and templates, cache data features, and lambda optimization best practices.
1225
59
 
1226
- // using the provided S3 client
1227
- const s3result1 = await tools.AWS.s3.client.putObject(params).promise();
60
+ A full implementation example and tutorial is provided as one of the Atlantis Application Starters available through the [Atlantis Tutorials repository](https://github.com/63klabs/atlantis-tutorials). (Atlantis is a collection of templates and deployment scripts to assist in starting and automating serverless deployments using AWS SAM and CloudFormation.)
1228
61
 
1229
- // using your own client
1230
- const s3client = new tools.AWS.s3.sdk.S3();
1231
- const s3result2 = await s3Client.putObject(params).promise();
62
+ ## Security
1232
63
 
1233
- // similarly with DynamoDb
1234
- const dbResult1 = await tools.AWS.dynamo.client.put(params).promise(); // tools.AWS.dynamo.client uses DynamoDB.DocumentClient
64
+ See [SECURITY](./SECURITY.md) for information on reporting concerns.
1235
65
 
1236
- // using your own DynamoDb Document client
1237
- const dbClient = new tools.AWS.dynamo.sdk.DynamoDB.DocumentClient( {region: 'us-east-1'} );
1238
- const dbResult2 = await dbClient.put(params).promise(),
1239
- ```
66
+ ## Change Log
1240
67
 
1241
- ### AWS X-Ray
68
+ See [Change Log](CHANGELOG.md) for version history and changes.
1242
69
 
1243
- X-Ray can be enabled by making sure you have the Lambda layer installed, appropriate permissions granted to your Lambda execution policy, and setting AWS X-Ray to true in the CloudFormation Template parameters or your Lambda Environment variable.
70
+ ## Issues, Features, and Enhancements
1244
71
 
1245
- The templates and code under the installation section already have these implemented.
72
+ Visit the [Issues section of the @63Klabs Cache-Data GitHub repository](https://github.com/63klabs/cache-data) for information on reported issues, upcoming fixes and enhancements, and to submit requests.
1246
73
 
1247
- ## Help
74
+ ## License
1248
75
 
1249
- Make sure you have your S3 bucket, DynamoDb table, and SSM Parameter store set up. Also make sure that you have IAM policies to allow your Lambda function access to these.
76
+ This project is licensed under the MIT License - see the LICENSE.txt file for details
1250
77
 
1251
78
  ## Author
1252
79
 
1253
- ### Chad Kluck
80
+ ### Chad Kluck
1254
81
 
82
+ - Software, DevOps, and Developer Experience Engineer
83
+ - [AWS Certified Developer - Associate](https://www.credly.com/users/chad-kluck/badges)
1255
84
  - [Website](https://chadkluck.me/)
1256
85
  - [GitHub](https://github.com/chadkluck)
1257
- - [Mastodon: @63klabs@universeodon.com](https://universeodon.com/@63klabs)
1258
- - [X: @ChadKluck](https://x.com/chadkluck)
1259
-
1260
- ## Version History
1261
-
1262
- Refer to the [Change Log](CHANGELOG.md)
1263
-
1264
- ## License
1265
-
1266
- This project is licensed under the MIT License - see the LICENSE.txt file for details
86
+ - [GitHub (63Klabs)](https://github.com/63klabs)
87
+ - [Mastodon: @chadkluck@universeodon.com](https://universeodon.com/@chadkluck)
88
+ - [LinkedIn](https://www.linkedin.com/in/chadkluck/)