@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.
Files changed (30) hide show
  1. package/dist/__test__/imports.test.js +82 -304
  2. package/dist/__test__/infra/acl-builder.test.js +18 -0
  3. package/dist/__test__/infra/stack/dt-function.test.d.ts +1 -0
  4. package/dist/__test__/infra/stack/dt-function.test.js +70 -0
  5. package/dist/__test__/infra/stack/stack.test.d.ts +1 -0
  6. package/dist/__test__/infra/stack/stack.test.js +33 -0
  7. package/dist/__test__/stack/dt-function.test.js +3 -121
  8. package/dist/__test__/utils/lead-time-logging.test.d.ts +1 -0
  9. package/dist/__test__/utils/lead-time-logging.test.js +153 -0
  10. package/dist/aws/infra/acl-builder.d.ts +21 -2
  11. package/dist/aws/infra/acl-builder.js +54 -6
  12. package/dist/aws/infra/canaries/database-checker.js +2 -3
  13. package/dist/aws/infra/stack/dt-function.d.ts +19 -12
  14. package/dist/aws/infra/stack/dt-function.js +31 -48
  15. package/dist/aws/infra/stack/stack-checking-aspect.js +1 -2
  16. package/dist/aws/infra/stack/stack.d.ts +2 -0
  17. package/dist/aws/infra/stack/stack.js +9 -8
  18. package/dist/aws/runtime/dt-logger.d.ts +1 -0
  19. package/dist/aws/runtime/dt-logger.js +5 -0
  20. package/dist/aws/runtime/environment.d.ts +1 -0
  21. package/dist/aws/runtime/environment.js +1 -0
  22. package/dist/aws/runtime/secrets/proxy-holder.d.ts +6 -1
  23. package/dist/aws/runtime/secrets/proxy-holder.js +6 -4
  24. package/dist/aws/runtime/secrets/rds-holder.d.ts +4 -1
  25. package/dist/aws/runtime/secrets/rds-holder.js +6 -4
  26. package/dist/database/database.d.ts +1 -1
  27. package/dist/database/database.js +1 -1
  28. package/dist/utils/lead-time-logging.d.ts +10 -0
  29. package/dist/utils/lead-time-logging.js +33 -0
  30. 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, LayerVersion, LoggingFormat, Runtime, SystemLogLevel, Tracing, } from "aws-cdk-lib/aws-lambda";
6
- import { ManagedPolicy } from "aws-cdk-lib/aws-iam";
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
- * Use AssetCode from given path(dist/lambda/${path}). Default path is lambdaName. Also calls withHandler with the same value.
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
- this.withHandler(path);
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
- * Add Lambda layers.
133
+ * Set storage size for the lambda. Defaults to AWS default (512MB).
130
134
  */
131
- withLayers(...layers) {
132
- this.layers.push(...layers);
135
+ withStorageSize(storageSize) {
136
+ this.storageSize = storageSize;
133
137
  return this;
134
138
  }
135
139
  /**
136
- * Enable ADOT (AWS Distro for OpenTelemetry) tracing for this Lambda.
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
- withAdotTracing() {
141
- this._adotTracing = true;
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
- tracing: this._adotTracing ? Tracing.ACTIVE : undefined,
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
- if (this._features.secretAccess) {
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 "change-case";
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 this.configuration.secretId
50
- ? {
51
- SECRET_ID: this.configuration.secretId,
52
- DB_APPLICATION: dbApplication,
53
- }
54
- : {
55
- DB_APPLICATION: dbApplication,
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,4 +1,5 @@
1
1
  export declare enum EnvKeys {
2
+ APP_NAME = "APP_NAME",
2
3
  AWS_REGION = "AWS_REGION",
3
4
  SECRET_ID = "SECRET_ID",
4
5
  SECRET_OVERRIDE_AWS_REGION = "SECRET_OVERRIDE_AWS_REGION"
@@ -1,5 +1,6 @@
1
1
  export var EnvKeys;
2
2
  (function (EnvKeys) {
3
+ EnvKeys["APP_NAME"] = "APP_NAME";
3
4
  EnvKeys["AWS_REGION"] = "AWS_REGION";
4
5
  EnvKeys["SECRET_ID"] = "SECRET_ID";
5
6
  EnvKeys["SECRET_OVERRIDE_AWS_REGION"] = "SECRET_OVERRIDE_AWS_REGION";
@@ -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(secretId: string);
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(secretId) {
12
- this.secretHolder = new SecretHolder(secretId, "", RDS_PROXY_SECRET_KEYS);
11
+ constructor(secretHolder) {
12
+ this.secretHolder = secretHolder;
13
13
  }
14
- static create() {
15
- return new ProxyHolder(getEnvVariable("SECRET_ID"));
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(secretId: string);
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(secretId) {
12
- this.secretHolder = new SecretHolder(secretId, "", RDS_SECRET_KEYS);
11
+ constructor(secretHolder) {
12
+ this.secretHolder = secretHolder;
13
13
  }
14
- static create() {
15
- return new RdsHolder(getEnvVariable("SECRET_ID"));
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();
@@ -1,4 +1,4 @@
1
- import { type IDatabase, type ITask } from "pg-promise";
1
+ import type { IDatabase, ITask } from "pg-promise";
2
2
  export declare enum DatabaseEnvironmentKeys {
3
3
  DB_USER = "DB_USER",
4
4
  DB_PASS = "DB_PASS",
@@ -1,4 +1,4 @@
1
- import pgpImport, {} from "pg-promise";
1
+ import pgpImport from "pg-promise";
2
2
  import { logger } from "../aws/runtime/dt-logger-default.js";
3
3
  import { logException } from "../utils/logging.js";
4
4
  import { getEnvVariable, getEnvVariableOrElse } from "../utils/utils.js";
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitraffic/common",
3
- "version": "2026.9.4-1",
3
+ "version": "2026.9.4-2",
4
4
  "private": false,
5
5
  "description": "",
6
6
  "repository": {