@aspan-corporation/ac-shared 1.2.34 → 1.2.36

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.
@@ -14,7 +14,11 @@ export { MetricUnit } from "@aws-lambda-powertools/metrics";
14
14
  export const tracer = new Tracer();
15
15
  export const metrics = new Metrics({ namespace: "aspan-corporation" });
16
16
  export const withMiddlewares = (handler) => middy(handler)
17
- .use(injectLambdaContext(logger, { logEvent: true }))
17
+ // logEvent is deliberately false: several handlers are fronted by API
18
+ // Gateway, whose proxy event carries the caller's `Authorization` header
19
+ // (a live Cognito token). Logging the full event would persist those
20
+ // tokens to CloudWatch. Handlers log the specific fields they need.
21
+ .use(injectLambdaContext(logger, { logEvent: false }))
18
22
  .use(captureLambdaHandler(tracer))
19
23
  .use(logMetrics(metrics, { captureColdStartMetric: true }))
20
24
  .use({
@@ -1,5 +1,10 @@
1
1
  import { Logger } from "@aws-lambda-powertools/logger";
2
- import { AssumeRoleCommandInput, STSClient } from "@aws-sdk/client-sts";
2
+ import { AssumeRoleCommandInput, AssumeRoleCommandOutput, STSClient } from "@aws-sdk/client-sts";
3
+ /**
4
+ * Clear the module-scoped assume-role credential cache. Intended for tests
5
+ * only, so each case starts from a known state.
6
+ */
7
+ export declare const clearAssumeRoleCache: () => void;
3
8
  export declare class STSService {
4
9
  logger: Logger;
5
10
  client: STSClient;
@@ -9,6 +14,6 @@ export declare class STSService {
9
14
  client?: STSClient;
10
15
  });
11
16
  getCallerIdentity(): Promise<import("@aws-sdk/client-sts").GetCallerIdentityCommandOutput>;
12
- assumeRole(assumeRoleCommandInput: AssumeRoleCommandInput): Promise<import("@aws-sdk/client-sts").AssumeRoleCommandOutput>;
17
+ assumeRole(assumeRoleCommandInput: AssumeRoleCommandInput): Promise<AssumeRoleCommandOutput>;
13
18
  }
14
19
  export type { AssumeRoleCommandInput, AssumeRoleCommandOutput } from "@aws-sdk/client-sts";
@@ -1,6 +1,34 @@
1
1
  import { Logger } from "@aws-lambda-powertools/logger";
2
2
  import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
3
3
  import assert from "node:assert/strict";
4
+ // Module-scoped cache of assumed-role credentials, shared across every
5
+ // STSService instance in the same execution environment. Lambda handlers
6
+ // construct a fresh STSService per invocation (usually in a middy `before`
7
+ // hook), so an instance-level cache would never hit. Keying at module scope
8
+ // means a warm container assumes the role once and reuses the credentials
9
+ // until they are close to expiry, removing an STS round-trip (~50-150ms) from
10
+ // every invocation.
11
+ const REFRESH_BUFFER_MS = 5 * 60 * 1000;
12
+ const credentialCache = new Map();
13
+ const cacheKey = (input) => [
14
+ input.RoleArn,
15
+ input.RoleSessionName,
16
+ input.ExternalId ?? "",
17
+ input.DurationSeconds ?? "",
18
+ input.SerialNumber ?? ""
19
+ ].join("|");
20
+ const isFresh = (output) => {
21
+ const expiration = output.Credentials?.Expiration;
22
+ return (expiration instanceof Date &&
23
+ expiration.getTime() - REFRESH_BUFFER_MS > Date.now());
24
+ };
25
+ /**
26
+ * Clear the module-scoped assume-role credential cache. Intended for tests
27
+ * only, so each case starts from a known state.
28
+ */
29
+ export const clearAssumeRoleCache = () => {
30
+ credentialCache.clear();
31
+ };
4
32
  export class STSService {
5
33
  logger;
6
34
  client;
@@ -27,7 +55,20 @@ export class STSService {
27
55
  return await this.client.send(new GetCallerIdentityCommand({}));
28
56
  }
29
57
  async assumeRole(assumeRoleCommandInput) {
58
+ const key = cacheKey(assumeRoleCommandInput);
59
+ const cached = credentialCache.get(key);
60
+ if (cached && isFresh(cached)) {
61
+ this.logger.debug("assumeRole cache hit", {
62
+ roleArn: assumeRoleCommandInput.RoleArn
63
+ });
64
+ return cached;
65
+ }
30
66
  this.logger.debug("assumeRole", { assumeRoleCommandInput });
31
- return await this.client.send(new AssumeRoleCommand(assumeRoleCommandInput));
67
+ const output = await this.client.send(new AssumeRoleCommand(assumeRoleCommandInput));
68
+ // Only cache credentials that carry an expiry we can reason about.
69
+ if (output.Credentials?.Expiration) {
70
+ credentialCache.set(key, output);
71
+ }
72
+ return output;
32
73
  }
33
74
  }
@@ -144,10 +144,15 @@ export const diaryPreview = (markdown) => {
144
144
  /** Extract the media keys of photos embedded as `![alt](mediaKey)` in the body. */
145
145
  export const extractEmbeddedPhotoKeys = (markdown) => {
146
146
  const keys = new Set();
147
- const re = /!\[[^\]]*\]\(([^)]+)\)/g;
147
+ // Two destination forms (CommonMark): angle-bracketed `<…>` — used for keys
148
+ // with spaces, and which may also contain ")" (e.g. "image (2).jpg") — or a
149
+ // bare URL up to the first ")". The angle form is captured in group 1, the
150
+ // bare form in group 2; the bare form must stop at ")" so it can't swallow a
151
+ // parenthesised key, which is exactly why such keys use the angle form.
152
+ const re = /!\[[^\]]*\]\(\s*(?:<([^>]*)>|([^)\s]+))\s*\)/g;
148
153
  let m;
149
154
  while ((m = re.exec(markdown)) !== null) {
150
- const key = m[1].trim();
155
+ const key = (m[1] ?? m[2] ?? "").trim();
151
156
  // Only treat library media keys as photo links (ignore external URLs).
152
157
  if (key && !/^https?:\/\//i.test(key))
153
158
  keys.add(key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspan-corporation/ac-shared",
3
- "version": "1.2.34",
3
+ "version": "1.2.36",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "exports": {
@@ -36,12 +36,15 @@
36
36
  "clean": "rm -rf lib",
37
37
  "build": "npm run clean && npm run build:code && npm run build:types",
38
38
  "build:code": "tsc --declaration false --emitDeclarationOnly false",
39
- "build:types": "tsc --emitDeclarationOnly --declaration"
39
+ "build:types": "tsc --emitDeclarationOnly --declaration",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest"
40
42
  },
41
43
  "devDependencies": {
42
44
  "@types/aws-lambda": "^8.10.137",
43
45
  "@types/node": "^20.12.5",
44
- "typescript": "^5.4.4"
46
+ "typescript": "^5.4.4",
47
+ "vitest": "^4.1.9"
45
48
  },
46
49
  "dependencies": {
47
50
  "@aws-lambda-powertools/batch": "^2.30.2",