@digitraffic/common 2026.9.4-1 → 2026.9.4-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__test__/imports.test.js +82 -304
- package/dist/__test__/infra/acl-builder.test.js +18 -0
- package/dist/__test__/infra/stack/dt-function.test.d.ts +1 -0
- package/dist/__test__/infra/stack/dt-function.test.js +70 -0
- package/dist/__test__/infra/stack/stack.test.d.ts +1 -0
- package/dist/__test__/infra/stack/stack.test.js +33 -0
- package/dist/__test__/stack/dt-function.test.js +3 -121
- package/dist/__test__/utils/lead-time-logging.test.d.ts +1 -0
- package/dist/__test__/utils/lead-time-logging.test.js +153 -0
- package/dist/aws/infra/acl-builder.d.ts +21 -2
- package/dist/aws/infra/acl-builder.js +54 -6
- package/dist/aws/infra/canaries/database-checker.js +2 -3
- package/dist/aws/infra/stack/dt-function.d.ts +19 -12
- package/dist/aws/infra/stack/dt-function.js +31 -48
- package/dist/aws/infra/stack/stack-checking-aspect.js +1 -2
- package/dist/aws/infra/stack/stack.d.ts +2 -0
- package/dist/aws/infra/stack/stack.js +9 -8
- package/dist/aws/runtime/dt-logger.d.ts +1 -0
- package/dist/aws/runtime/dt-logger.js +5 -0
- package/dist/aws/runtime/environment.d.ts +1 -0
- package/dist/aws/runtime/environment.js +1 -0
- package/dist/aws/runtime/secrets/proxy-holder.d.ts +6 -1
- package/dist/aws/runtime/secrets/proxy-holder.js +6 -4
- package/dist/aws/runtime/secrets/rds-holder.d.ts +4 -1
- package/dist/aws/runtime/secrets/rds-holder.js +6 -4
- package/dist/database/database.d.ts +1 -1
- package/dist/database/database.js +1 -1
- package/dist/utils/lead-time-logging.d.ts +10 -0
- package/dist/utils/lead-time-logging.js +33 -0
- package/package.json +1 -1
|
@@ -2,9 +2,8 @@ import { basename } from "node:path";
|
|
|
2
2
|
import { Duration } from "aws-cdk-lib";
|
|
3
3
|
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
|
4
4
|
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
5
|
-
import { ApplicationLogLevel, Architecture, AssetCode, Function as AwsFunction, Code,
|
|
6
|
-
import {
|
|
7
|
-
import { camelCase, startCase } from "es-toolkit";
|
|
5
|
+
import { ApplicationLogLevel, Architecture, AssetCode, Function as AwsFunction, Code, LoggingFormat, Runtime, SystemLogLevel, } from "aws-cdk-lib/aws-lambda";
|
|
6
|
+
import { camelCase, kebabCase, startCase } from "es-toolkit";
|
|
8
7
|
import { DatabaseEnvironmentKeys } from "../../../database/database.js";
|
|
9
8
|
import { EnvKeys } from "../../runtime/environment.js";
|
|
10
9
|
import { DtFunctionAlarms } from "./dt-function-alarms.js";
|
|
@@ -22,6 +21,7 @@ export class FunctionBuilder {
|
|
|
22
21
|
functionName;
|
|
23
22
|
environment = {};
|
|
24
23
|
vpc;
|
|
24
|
+
storageSize;
|
|
25
25
|
alarms = new DtFunctionAlarms();
|
|
26
26
|
// these will be overridden in constructor, but inspection won't see it, so set some default values.
|
|
27
27
|
code = Code.fromInline("placeholder");
|
|
@@ -30,7 +30,6 @@ export class FunctionBuilder {
|
|
|
30
30
|
securityGroups = [];
|
|
31
31
|
policyStatements = [];
|
|
32
32
|
allowedActions = [];
|
|
33
|
-
_adotTracing = false;
|
|
34
33
|
_features = {
|
|
35
34
|
databaseAccess: true,
|
|
36
35
|
secretAccess: true,
|
|
@@ -41,13 +40,7 @@ export class FunctionBuilder {
|
|
|
41
40
|
this.functionName = `${stack.configuration.shortName}-${startCase(camelCase(lambdaName)).replace(/\s/g, "")}`;
|
|
42
41
|
this.environment = {};
|
|
43
42
|
this.vpc = stack.vpc;
|
|
44
|
-
// this calls withHandler as well but with full path
|
|
45
43
|
this.withAssetCode(lambdaName);
|
|
46
|
-
// Remove path from lambda to get module name.
|
|
47
|
-
// e.g. for lambdaName "api/charging-network/v1/operators",
|
|
48
|
-
// moduleName becomes "operators" and handler becomes "operators.handler"
|
|
49
|
-
const moduleName = basename(lambdaName);
|
|
50
|
-
this.withHandler(moduleName);
|
|
51
44
|
}
|
|
52
45
|
/**
|
|
53
46
|
* Creates a new builder with defaults, using the lambdaName as a source for the lambda implementation (dist/lambdaName/lambdaName.js).
|
|
@@ -65,11 +58,22 @@ export class FunctionBuilder {
|
|
|
65
58
|
.withoutSecretAccess();
|
|
66
59
|
}
|
|
67
60
|
/**
|
|
68
|
-
*
|
|
61
|
+
* Sets the Lambda deployment package to the directory `dist/lambda/${path}` and configures
|
|
62
|
+
* the handler to `basename(path).handler`.
|
|
63
|
+
* @param path - subdirectory under `dist/lambda/` that contains the compiled Lambda code.
|
|
64
|
+
* The handler module name is derived from `basename(path)` (e.g. `"locations.handler"`).
|
|
65
|
+
* Defaults to `lambdaName`.
|
|
66
|
+
* Example: `"dump/locations"` → asset dir `dist/lambda/dump/locations/`, handler `locations.handler`.
|
|
67
|
+
* @param exclude - optional glob patterns to exclude from the asset bundle.
|
|
68
|
+
* Patterns are relative to the asset root (`dist/lambda/${path}/`) — do NOT use a leading slash.
|
|
69
|
+
* Use `**` to match files recursively.
|
|
70
|
+
* Example: `["datex2-36", "datex2-37", "statuses"]` excludes sibling lambda subdirectories
|
|
71
|
+
* that would otherwise be bundled into this lambda's deployment package.
|
|
69
72
|
*/
|
|
70
|
-
withAssetCode(path = this._name) {
|
|
71
|
-
this.code = new AssetCode(`dist/lambda/${path}
|
|
72
|
-
|
|
73
|
+
withAssetCode(path = this._name, exclude) {
|
|
74
|
+
this.code = new AssetCode(`dist/lambda/${path}`, exclude ? { exclude } : undefined);
|
|
75
|
+
const moduleName = basename(path);
|
|
76
|
+
this.withHandler(moduleName);
|
|
73
77
|
return this;
|
|
74
78
|
}
|
|
75
79
|
withCode(code) {
|
|
@@ -126,19 +130,17 @@ export class FunctionBuilder {
|
|
|
126
130
|
return this;
|
|
127
131
|
}
|
|
128
132
|
/**
|
|
129
|
-
*
|
|
133
|
+
* Set storage size for the lambda. Defaults to AWS default (512MB).
|
|
130
134
|
*/
|
|
131
|
-
|
|
132
|
-
this.
|
|
135
|
+
withStorageSize(storageSize) {
|
|
136
|
+
this.storageSize = storageSize;
|
|
133
137
|
return this;
|
|
134
138
|
}
|
|
135
139
|
/**
|
|
136
|
-
*
|
|
137
|
-
* Adds the ADOT Lambda layer, enables X-Ray active tracing,
|
|
138
|
-
* sets required environment variables, and attaches IAM permissions.
|
|
140
|
+
* Add Lambda layers.
|
|
139
141
|
*/
|
|
140
|
-
|
|
141
|
-
this.
|
|
142
|
+
withLayers(...layers) {
|
|
143
|
+
this.layers.push(...layers);
|
|
142
144
|
return this;
|
|
143
145
|
}
|
|
144
146
|
/**
|
|
@@ -219,7 +221,6 @@ export class FunctionBuilder {
|
|
|
219
221
|
}
|
|
220
222
|
: undefined;
|
|
221
223
|
const securityGroups = this.getSecurityGroups();
|
|
222
|
-
const layers = this.getLayers();
|
|
223
224
|
const createdFunction = new AwsFunction(this._stack, this.functionName, {
|
|
224
225
|
vpc: this.vpc,
|
|
225
226
|
vpcSubnets,
|
|
@@ -239,15 +240,12 @@ export class FunctionBuilder {
|
|
|
239
240
|
securityGroups,
|
|
240
241
|
environment: this.getEnvironment(),
|
|
241
242
|
description: this.description,
|
|
242
|
-
layers: layers.length > 0 ? layers : undefined,
|
|
243
|
-
|
|
243
|
+
layers: this.layers.length > 0 ? this.layers : undefined,
|
|
244
|
+
ephemeralStorageSize: this.storageSize,
|
|
244
245
|
});
|
|
245
246
|
if (this._features.secretAccess) {
|
|
246
247
|
this._stack.grantSecret(createdFunction);
|
|
247
248
|
}
|
|
248
|
-
if (this._adotTracing) {
|
|
249
|
-
createdFunction.role?.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLambdaApplicationSignalsExecutionRolePolicy"));
|
|
250
|
-
}
|
|
251
249
|
this.attachPolicies(createdFunction);
|
|
252
250
|
this.createAlarms(createdFunction);
|
|
253
251
|
return createdFunction;
|
|
@@ -270,37 +268,22 @@ export class FunctionBuilder {
|
|
|
270
268
|
throw new Error(`Lambda ${this.functionName} cannot use wildcard action "*" in withAllowedActions. Please specify explicit actions.`);
|
|
271
269
|
}
|
|
272
270
|
}
|
|
273
|
-
getLayers() {
|
|
274
|
-
const layers = [...this.layers];
|
|
275
|
-
if (this._adotTracing) {
|
|
276
|
-
const layerName = this.runtime.name.startsWith("python")
|
|
277
|
-
? "AWSOpenTelemetryDistroPython"
|
|
278
|
-
: "AWSOpenTelemetryDistroJs";
|
|
279
|
-
layers.push(LayerVersion.fromLayerVersionArn(this._stack, `${this.functionName}-AdotLayer`, `arn:aws:lambda:eu-west-1:615299751070:layer:${layerName}:1`));
|
|
280
|
-
}
|
|
281
|
-
return layers;
|
|
282
|
-
}
|
|
283
271
|
getEnvironment() {
|
|
284
|
-
let environment = {
|
|
285
|
-
|
|
272
|
+
let environment = {
|
|
273
|
+
[EnvKeys.APP_NAME]: `${this._stack.configuration.trafficType.toLowerCase()}-${kebabCase(this._stack.configuration.shortName)}`,
|
|
274
|
+
};
|
|
275
|
+
if (this._features.secretAccess && this._stack.configuration.secretId) {
|
|
286
276
|
environment = {
|
|
287
277
|
...environment,
|
|
288
278
|
[EnvKeys.SECRET_ID]: this._stack.configuration.secretId,
|
|
289
279
|
};
|
|
290
280
|
}
|
|
291
|
-
if (this._features.databaseAccess) {
|
|
281
|
+
if (this._features.databaseAccess && this._stack.configuration.shortName) {
|
|
292
282
|
environment = {
|
|
293
283
|
...environment,
|
|
294
284
|
[DatabaseEnvironmentKeys.DB_APPLICATION]: this._stack.configuration.shortName,
|
|
295
285
|
};
|
|
296
286
|
}
|
|
297
|
-
if (this._adotTracing) {
|
|
298
|
-
environment = {
|
|
299
|
-
...environment,
|
|
300
|
-
AWS_LAMBDA_EXEC_WRAPPER: "/opt/otel-instrument",
|
|
301
|
-
OTEL_AWS_APPLICATION_SIGNALS_ENABLED: "false",
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
287
|
return { ...environment, ...this.environment };
|
|
305
288
|
}
|
|
306
289
|
createAlarms(lambda) {
|
|
@@ -4,8 +4,7 @@ import { CfnFunction, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
|
4
4
|
import { LogRetention } from "aws-cdk-lib/aws-logs";
|
|
5
5
|
import { CfnBucket } from "aws-cdk-lib/aws-s3";
|
|
6
6
|
import { CfnQueue } from "aws-cdk-lib/aws-sqs";
|
|
7
|
-
import { kebabCase } from "
|
|
8
|
-
import { snakeCase } from "es-toolkit";
|
|
7
|
+
import { kebabCase, snakeCase } from "es-toolkit";
|
|
9
8
|
import { DigitrafficStack, SOLUTION_KEY } from "./stack.js";
|
|
10
9
|
const MAX_CONCURRENCY_LIMIT = 100;
|
|
11
10
|
const ALLOWED_RUNTIMES = [
|
|
@@ -20,6 +20,8 @@ export interface StackConfiguration {
|
|
|
20
20
|
readonly lambdaDbSgId?: string;
|
|
21
21
|
readonly privateSubnetIds?: string[];
|
|
22
22
|
readonly availabilityZones?: string[];
|
|
23
|
+
readonly dbWriterHost?: string;
|
|
24
|
+
readonly dbReaderHost?: string;
|
|
23
25
|
readonly trafficType: TrafficType;
|
|
24
26
|
readonly production: boolean;
|
|
25
27
|
readonly stackProps: StackProps;
|
|
@@ -3,6 +3,8 @@ import { SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2";
|
|
|
3
3
|
import { Secret } from "aws-cdk-lib/aws-secretsmanager";
|
|
4
4
|
import { Topic } from "aws-cdk-lib/aws-sns";
|
|
5
5
|
import { StringParameter } from "aws-cdk-lib/aws-ssm";
|
|
6
|
+
import { kebabCase } from "es-toolkit";
|
|
7
|
+
import { EnvKeys } from "../../runtime/environment.js";
|
|
6
8
|
import { StackCheckingAspect } from "./stack-checking-aspect.js";
|
|
7
9
|
const SSM_ROOT = "/digitraffic";
|
|
8
10
|
export const SOLUTION_KEY = "Solution";
|
|
@@ -46,14 +48,13 @@ export class DigitrafficStack extends Stack {
|
|
|
46
48
|
return this.createDefaultLambdaEnvironment(this.configuration.shortName);
|
|
47
49
|
}
|
|
48
50
|
createDefaultLambdaEnvironment(dbApplication) {
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
51
|
+
return {
|
|
52
|
+
[EnvKeys.APP_NAME]: `${this.configuration.trafficType.toLowerCase()}-${kebabCase(this.configuration.shortName)}`,
|
|
53
|
+
DB_APPLICATION: dbApplication,
|
|
54
|
+
...(this.configuration.secretId
|
|
55
|
+
? { [EnvKeys.SECRET_ID]: this.configuration.secretId }
|
|
56
|
+
: {}),
|
|
57
|
+
};
|
|
57
58
|
}
|
|
58
59
|
getSecret() {
|
|
59
60
|
if (this.secret === undefined) {
|
|
@@ -61,6 +61,7 @@ export interface LoggableType extends CustomParams {
|
|
|
61
61
|
* * the actual message (as json or as string)
|
|
62
62
|
*/
|
|
63
63
|
export declare class DtLogger {
|
|
64
|
+
readonly appName?: string;
|
|
64
65
|
readonly lambdaName?: string;
|
|
65
66
|
readonly runtime?: string;
|
|
66
67
|
readonly writeStream: Writable;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { lowerFirst, mapKeys } from "es-toolkit";
|
|
2
2
|
import { getEnvVariableOrElse } from "../../utils/utils.js";
|
|
3
|
+
import { EnvKeys } from "./environment.js";
|
|
3
4
|
/**
|
|
4
5
|
* Helper class for json-logging.
|
|
5
6
|
*
|
|
@@ -10,6 +11,7 @@ import { getEnvVariableOrElse } from "../../utils/utils.js";
|
|
|
10
11
|
* * the actual message (as json or as string)
|
|
11
12
|
*/
|
|
12
13
|
export class DtLogger {
|
|
14
|
+
appName;
|
|
13
15
|
lambdaName;
|
|
14
16
|
runtime;
|
|
15
17
|
writeStream;
|
|
@@ -19,6 +21,7 @@ export class DtLogger {
|
|
|
19
21
|
* @param {LoggerConfiguration?} [config] - Accepts configuration options @see {@link LoggerConfiguration}
|
|
20
22
|
*/
|
|
21
23
|
constructor(config) {
|
|
24
|
+
this.appName = getEnvVariableOrElse(EnvKeys.APP_NAME, undefined);
|
|
22
25
|
this.lambdaName =
|
|
23
26
|
config?.lambdaName ??
|
|
24
27
|
getEnvVariableOrElse("AWS_LAMBDA_FUNCTION_NAME", "unknown lambda name");
|
|
@@ -38,6 +41,7 @@ export class DtLogger {
|
|
|
38
41
|
const logMessage = {
|
|
39
42
|
message,
|
|
40
43
|
level: "DEBUG",
|
|
44
|
+
app: this.appName,
|
|
41
45
|
lambdaName: this.lambdaName,
|
|
42
46
|
runtime: this.runtime,
|
|
43
47
|
};
|
|
@@ -112,6 +116,7 @@ export class DtLogger {
|
|
|
112
116
|
...messageFields,
|
|
113
117
|
error,
|
|
114
118
|
stack,
|
|
119
|
+
app: this.appName,
|
|
115
120
|
lambdaName: this.lambdaName,
|
|
116
121
|
runtime: this.runtime,
|
|
117
122
|
};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import type { RdsProxySecret } from "./dbsecret.js";
|
|
2
|
+
import { SecretHolder } from "./secret-holder.js";
|
|
1
3
|
/**
|
|
2
4
|
* Holds credentials for RDS Proxy access.
|
|
3
5
|
*/
|
|
4
6
|
export declare class ProxyHolder {
|
|
5
7
|
private readonly secretHolder;
|
|
6
|
-
constructor(
|
|
8
|
+
private constructor();
|
|
9
|
+
/** Creates a new instance of ProxyHolder from given SecretHolder. */
|
|
10
|
+
static create(secretHolder: SecretHolder<RdsProxySecret>): ProxyHolder;
|
|
11
|
+
/** Creates a new instance of ProxyHolder with a new default SecretHolder(using env variable SECRET_ID). */
|
|
7
12
|
static create(): ProxyHolder;
|
|
8
13
|
setCredentials(): Promise<void>;
|
|
9
14
|
}
|
|
@@ -8,11 +8,13 @@ const RDS_PROXY_SECRET_KEYS = Object.values(RdsProxySecretKey);
|
|
|
8
8
|
*/
|
|
9
9
|
export class ProxyHolder {
|
|
10
10
|
secretHolder;
|
|
11
|
-
constructor(
|
|
12
|
-
this.secretHolder =
|
|
11
|
+
constructor(secretHolder) {
|
|
12
|
+
this.secretHolder = secretHolder;
|
|
13
13
|
}
|
|
14
|
-
static create() {
|
|
15
|
-
|
|
14
|
+
static create(secretHolder) {
|
|
15
|
+
const holder = secretHolder ??
|
|
16
|
+
new SecretHolder(getEnvVariable("SECRET_ID"), "", RDS_PROXY_SECRET_KEYS);
|
|
17
|
+
return new ProxyHolder(holder);
|
|
16
18
|
}
|
|
17
19
|
async setCredentials() {
|
|
18
20
|
const secret = await this.secretHolder.get();
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import type { RdsSecret } from "./dbsecret.js";
|
|
2
|
+
import { SecretHolder } from "./secret-holder.js";
|
|
1
3
|
/**
|
|
2
4
|
* Holds credentials for RDS access.
|
|
3
5
|
*/
|
|
4
6
|
export declare class RdsHolder {
|
|
5
7
|
private readonly secretHolder;
|
|
6
|
-
constructor(
|
|
8
|
+
private constructor();
|
|
9
|
+
static create(rdsHolder: SecretHolder<RdsSecret>): RdsHolder;
|
|
7
10
|
static create(): RdsHolder;
|
|
8
11
|
setCredentials(): Promise<void>;
|
|
9
12
|
}
|
|
@@ -8,11 +8,13 @@ const RDS_SECRET_KEYS = Object.values(RdsSecretKey);
|
|
|
8
8
|
*/
|
|
9
9
|
export class RdsHolder {
|
|
10
10
|
secretHolder;
|
|
11
|
-
constructor(
|
|
12
|
-
this.secretHolder =
|
|
11
|
+
constructor(secretHolder) {
|
|
12
|
+
this.secretHolder = secretHolder;
|
|
13
13
|
}
|
|
14
|
-
static create() {
|
|
15
|
-
|
|
14
|
+
static create(secretHolder) {
|
|
15
|
+
const holder = secretHolder ??
|
|
16
|
+
new SecretHolder(getEnvVariable("SECRET_ID"), "", RDS_SECRET_KEYS);
|
|
17
|
+
return new RdsHolder(holder);
|
|
16
18
|
}
|
|
17
19
|
async setCredentials() {
|
|
18
20
|
const secret = await this.secretHolder.get();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CustomParams } from "../aws/runtime/dt-logger.js";
|
|
2
|
+
export interface LeadTimeLogging {
|
|
3
|
+
readonly target: string;
|
|
4
|
+
readonly leadTimeMs?: number;
|
|
5
|
+
readonly processTimeMs?: number;
|
|
6
|
+
readonly extraFields?: CustomParams;
|
|
7
|
+
}
|
|
8
|
+
export type LeadTimeType = "lead-time" | "process-time" | "mqtt-time";
|
|
9
|
+
export declare function logLeadTime(name: LeadTimeType, target: string, tookMs: number, extraFields?: CustomParams): void;
|
|
10
|
+
export declare function logLeadTimes(ptl: LeadTimeLogging): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { logger } from "../aws/runtime/dt-logger-default.js";
|
|
2
|
+
export function logLeadTime(name, target, tookMs, extraFields) {
|
|
3
|
+
const method = "LeadTimeLogging.logLeadTime";
|
|
4
|
+
try {
|
|
5
|
+
logger.info({
|
|
6
|
+
...(extraFields ?? {}),
|
|
7
|
+
method,
|
|
8
|
+
customLeadTime: true,
|
|
9
|
+
customName: name,
|
|
10
|
+
customTarget: target,
|
|
11
|
+
tookMs,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
logger.error({
|
|
16
|
+
method,
|
|
17
|
+
message: "Error logging lead time",
|
|
18
|
+
error,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isSafe(value) {
|
|
23
|
+
return Number.isSafeInteger(value);
|
|
24
|
+
}
|
|
25
|
+
export function logLeadTimes(ptl) {
|
|
26
|
+
if (isSafe(ptl.leadTimeMs)) {
|
|
27
|
+
logLeadTime("lead-time", ptl.target, ptl.leadTimeMs, ptl.extraFields);
|
|
28
|
+
}
|
|
29
|
+
if (isSafe(ptl.processTimeMs)) {
|
|
30
|
+
logLeadTime("process-time", ptl.target, ptl.processTimeMs, ptl.extraFields);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=lead-time-logging.js.map
|