@flipboxlabs/aws-audit-sdk 1.1.0

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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/audits.d.ts +188 -0
  4. package/dist/audits.d.ts.map +1 -0
  5. package/dist/audits.js +225 -0
  6. package/dist/batch/batch-processor.d.ts +81 -0
  7. package/dist/batch/batch-processor.d.ts.map +1 -0
  8. package/dist/batch/batch-processor.js +102 -0
  9. package/dist/batch/index.d.ts +2 -0
  10. package/dist/batch/index.d.ts.map +1 -0
  11. package/dist/batch/index.js +1 -0
  12. package/dist/config.d.ts +65 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +44 -0
  15. package/dist/constants.d.ts +147 -0
  16. package/dist/constants.d.ts.map +1 -0
  17. package/dist/constants.js +191 -0
  18. package/dist/events/batch-handler.d.ts +62 -0
  19. package/dist/events/batch-handler.d.ts.map +1 -0
  20. package/dist/events/batch-handler.js +92 -0
  21. package/dist/events/bus.d.ts +79 -0
  22. package/dist/events/bus.d.ts.map +1 -0
  23. package/dist/events/bus.js +86 -0
  24. package/dist/events/index.d.ts +3 -0
  25. package/dist/events/index.d.ts.map +1 -0
  26. package/dist/events/index.js +2 -0
  27. package/dist/index.d.ts +9 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +8 -0
  30. package/dist/middleware/middy.d.ts +66 -0
  31. package/dist/middleware/middy.d.ts.map +1 -0
  32. package/dist/middleware/middy.js +105 -0
  33. package/dist/repository.d.ts +421 -0
  34. package/dist/repository.d.ts.map +1 -0
  35. package/dist/repository.js +486 -0
  36. package/dist/repository.utils.d.ts +37 -0
  37. package/dist/repository.utils.d.ts.map +1 -0
  38. package/dist/repository.utils.js +70 -0
  39. package/dist/schema/audit.d.ts +202 -0
  40. package/dist/schema/audit.d.ts.map +1 -0
  41. package/dist/schema/audit.js +48 -0
  42. package/dist/schema/common.d.ts +128 -0
  43. package/dist/schema/common.d.ts.map +1 -0
  44. package/dist/schema/common.js +148 -0
  45. package/dist/schema/index.d.ts +27 -0
  46. package/dist/schema/index.d.ts.map +1 -0
  47. package/dist/schema/index.js +26 -0
  48. package/dist/schema/log.d.ts +211 -0
  49. package/dist/schema/log.d.ts.map +1 -0
  50. package/dist/schema/log.js +179 -0
  51. package/dist/schema/model.d.ts +110 -0
  52. package/dist/schema/model.d.ts.map +1 -0
  53. package/dist/schema/model.js +86 -0
  54. package/dist/schema/service.d.ts +189 -0
  55. package/dist/schema/service.d.ts.map +1 -0
  56. package/dist/schema/service.js +89 -0
  57. package/dist/schema/storage.d.ts +105 -0
  58. package/dist/schema/storage.d.ts.map +1 -0
  59. package/dist/schema/storage.js +63 -0
  60. package/dist/service.d.ts +285 -0
  61. package/dist/service.d.ts.map +1 -0
  62. package/dist/service.js +237 -0
  63. package/dist/test-config.d.ts +56 -0
  64. package/dist/test-config.d.ts.map +1 -0
  65. package/dist/test-config.js +24 -0
  66. package/dist/types.d.ts +40 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +1 -0
  69. package/dist/utils.d.ts +231 -0
  70. package/dist/utils.d.ts.map +1 -0
  71. package/dist/utils.js +313 -0
  72. package/package.json +85 -0
@@ -0,0 +1,231 @@
1
+ import { EventType } from "@aws-lambda-powertools/batch";
2
+ import type { EventSourceDataClassTypes } from "@aws-lambda-powertools/batch/types";
3
+ import type { PutEventsRequestEntry } from "@aws-sdk/client-eventbridge";
4
+ import type { DynamoDBRecord, KinesisStreamRecord, SQSRecord } from "aws-lambda";
5
+ import type { MessageWithAuditOverride } from "./batch/batch-processor.js";
6
+ import type { LogAuditInput } from "./schema/log.js";
7
+ import type { EventBridgeEvent } from "./schema/model.js";
8
+ /**
9
+ * Generates a random UUID for use as a trace identifier.
10
+ *
11
+ * @returns A random UUID string
12
+ * @deprecated Not currently used - consider using X-Ray trace IDs instead
13
+ */
14
+ export declare function generateTraceId(): string;
15
+ /**
16
+ * Generates a unique, time-sortable audit ID using KSUID.
17
+ *
18
+ * KSUIDs (K-Sortable Unique Identifiers) are globally unique and
19
+ * naturally sort by creation time, making them ideal for audit logs.
20
+ *
21
+ * @returns A KSUID string
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const id = generateAuditId();
26
+ * // Returns something like "2KQHp1vF5AufI6Fp4pJUJYBCWKg"
27
+ * ```
28
+ */
29
+ export declare function generateAuditId(): string;
30
+ /**
31
+ * Builds an audit entry from an SQS record with optional overrides.
32
+ *
33
+ * Automatically extracts trace information from the SQS message body
34
+ * if the audit doesn't already have a trace ID set.
35
+ *
36
+ * @param sqsRecord - The SQS record containing the message
37
+ * @param audit - The base audit entry to build from
38
+ * @param overrides - Optional overrides to merge into the audit
39
+ * @returns The complete audit entry with trace information
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const audit = buildAuditFromSQSRecord(sqsRecord, {
44
+ * operation: 'processOrder',
45
+ * target: { app: 'OrderService', type: 'Order', id: '123' },
46
+ * });
47
+ * ```
48
+ */
49
+ export declare function buildAuditFromSQSRecord(sqsRecord: SQSRecord, audit: LogAuditInput, overrides?: Partial<LogAuditInput>): LogAuditInput;
50
+ /**
51
+ * Builds an audit entry by deeply merging the base audit with overrides.
52
+ *
53
+ * Messages from both the base audit and overrides are concatenated
54
+ * with a space separator.
55
+ *
56
+ * @param audit - The base audit entry
57
+ * @param overrides - Optional overrides to merge into the audit
58
+ * @returns The merged audit entry
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const audit = buildAudit(
63
+ * { operation: 'create', message: 'Creating user' },
64
+ * { message: 'with admin role', status: 'success' }
65
+ * );
66
+ * // Result: { operation: 'create', message: 'Creating user with admin role', status: 'success' }
67
+ * ```
68
+ */
69
+ export declare function buildAudit(audit: LogAuditInput, overrides?: Partial<LogAuditInput>): LogAuditInput;
70
+ /**
71
+ * Input type for EventBridge event normalization.
72
+ */
73
+ type Input = Pick<PutEventsRequestEntry, "Detail" | "DetailType" | "Source">;
74
+ /**
75
+ * Normalizes an EventBridge PutEvents request entry to a simplified event format.
76
+ *
77
+ * Converts the AWS SDK format (Detail, DetailType, Source) to the internal
78
+ * event format (detail, detail-type, source).
79
+ *
80
+ * @param input - The EventBridge PutEvents request entry
81
+ * @returns The normalized event object
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const event = normalizeEventBridgetInput({
86
+ * Detail: '{"orderId": "123"}',
87
+ * DetailType: 'OrderCreated',
88
+ * Source: 'orders.service',
89
+ * });
90
+ * // Returns: { detail: '{"orderId": "123"}', 'detail-type': 'OrderCreated', source: 'orders.service' }
91
+ * ```
92
+ */
93
+ export declare function normalizeEventBridgetInput(input: Input): EventBridgeEvent;
94
+ /**
95
+ * Extracts audit overrides from a batch processor result.
96
+ *
97
+ * Handles various result formats from batch record handlers:
98
+ * - Objects with `_audit` property for explicit overrides
99
+ * - Objects with `message` property for simple message extraction
100
+ * - Non-objects or empty objects return empty overrides
101
+ *
102
+ * When both root message and audit override message exist, they are
103
+ * concatenated with a space separator.
104
+ *
105
+ * @param message - The result from a batch record handler
106
+ * @returns Partial audit input with extracted overrides
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * // With audit overrides
111
+ * extractOverridesFromBatchProcessor({
112
+ * message: 'Processed',
113
+ * _audit: { status: 'success', tier: 3 }
114
+ * });
115
+ * // Returns: { status: 'success', tier: 3, message: 'Processed' }
116
+ *
117
+ * // With just message
118
+ * extractOverridesFromBatchProcessor({ message: 'Done' });
119
+ * // Returns: { message: 'Done' }
120
+ *
121
+ * // With non-object
122
+ * extractOverridesFromBatchProcessor('success');
123
+ * // Returns: {}
124
+ * ```
125
+ */
126
+ export declare function extractOverridesFromBatchProcessor(message: unknown | MessageWithAuditOverride): Partial<LogAuditInput>;
127
+ /**
128
+ * Normalizes an event source record body to an EventBridge event format.
129
+ *
130
+ * Currently only supports SQS event type. For unsupported event types,
131
+ * returns undefined.
132
+ *
133
+ * @param eventType - The type of event source (SQS, Kinesis, DynamoDB)
134
+ * @param record - The event source record
135
+ * @returns The normalized EventBridge event, or undefined for unsupported types
136
+ */
137
+ export declare function normalizeEventBridgetEventBody(eventType: keyof typeof EventType, record: EventSourceDataClassTypes): EventBridgeEvent | undefined;
138
+ /**
139
+ * Extracts EventBridge event data from an SQS record body.
140
+ *
141
+ * Parses the SQS message body and extracts the EventBridge-specific
142
+ * fields (detail, detail-type, source).
143
+ *
144
+ * @param record - The SQS record containing the message
145
+ * @returns The extracted EventBridge event data
146
+ */
147
+ export declare function normalizeSQSEventBody(record: SQSRecord): EventBridgeEvent | undefined;
148
+ /**
149
+ * Parses a trace ID into its component parts.
150
+ *
151
+ * Trace IDs follow the format "id:stage" where:
152
+ * - id: The unique trace identifier
153
+ * - stage: The processing stage number (defaults to 0 if not present)
154
+ *
155
+ * @param traceId - The trace ID string to parse
156
+ * @returns An object with `id` and `stage` properties
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * getTraceParts("abc-123:5");
161
+ * // Returns: { id: "abc-123", stage: 5 }
162
+ *
163
+ * getTraceParts("abc-123");
164
+ * // Returns: { id: "abc-123", stage: 0 }
165
+ * ```
166
+ */
167
+ export declare function getTraceParts(traceId: string): {
168
+ id: string;
169
+ stage: number;
170
+ };
171
+ /**
172
+ * Extracts the unique record ID for audit correlation based on event type.
173
+ *
174
+ * Each event source has a stable unique identifier that persists across retries:
175
+ * - SQS: `messageId` - unique per message, stable across retry deliveries
176
+ * - Kinesis: `eventID` - unique per record in the stream
177
+ * - DynamoDB Streams: `eventID` - unique per stream record
178
+ *
179
+ * @param eventType - The type of event source (SQS, KinesisDataStreams, DynamoDBStreams)
180
+ * @param record - The event source record
181
+ * @returns The unique record ID, or undefined for unsupported event types
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * // SQS record
186
+ * const id = getRecordId("SQS", sqsRecord);
187
+ * // Returns: sqsRecord.messageId
188
+ *
189
+ * // Kinesis record
190
+ * const id = getRecordId("KinesisDataStreams", kinesisRecord);
191
+ * // Returns: kinesisRecord.eventID
192
+ * ```
193
+ */
194
+ export declare function getRecordId(eventType: keyof typeof EventType, record: SQSRecord | KinesisStreamRecord | DynamoDBRecord): string | undefined;
195
+ /**
196
+ * Gets the approximate receive count from an SQS record.
197
+ *
198
+ * The receive count indicates how many times the message has been delivered:
199
+ * - 1 = First delivery (not a retry)
200
+ * - 2+ = Retry delivery
201
+ *
202
+ * @param record - The SQS record
203
+ * @returns The receive count as a number
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * const count = getReceiveCount(sqsRecord);
208
+ * if (count > 1) {
209
+ * console.log(`This is retry attempt ${count - 1}`);
210
+ * }
211
+ * ```
212
+ */
213
+ export declare function getReceiveCount(record: SQSRecord): number;
214
+ /**
215
+ * Checks if an SQS message is being retried.
216
+ *
217
+ * A message is considered a retry if it has been received more than once.
218
+ *
219
+ * @param record - The SQS record
220
+ * @returns True if this is a retry delivery, false if first delivery
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * if (isRetry(sqsRecord)) {
225
+ * console.log("Processing retry...");
226
+ * }
227
+ * ```
228
+ */
229
+ export declare function isRetry(record: SQSRecord): boolean;
230
+ export {};
231
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACzD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,KAAK,EACX,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAqBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CACtC,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,aAAa,EACpB,SAAS,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAChC,aAAa,CAWf;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CACzB,KAAK,EAAE,aAAa,EACpB,SAAS,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAChC,aAAa,CAIf;AAED;;GAEG;AACH,KAAK,KAAK,GAAG,IAAI,CAAC,qBAAqB,EAAE,QAAQ,GAAG,YAAY,GAAG,QAAQ,CAAC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,KAAK,GAAG,gBAAgB,CAMzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kCAAkC,CACjD,OAAO,EAAE,OAAO,GAAG,wBAAwB,GACzC,OAAO,CAAC,aAAa,CAAC,CA6BxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC7C,SAAS,EAAE,MAAM,OAAO,SAAS,EACjC,MAAM,EAAE,yBAAyB,GAC/B,gBAAgB,GAAG,SAAS,CAK9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,SAAS,GACf,gBAAgB,GAAG,SAAS,CAQ9B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAO5E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAC1B,SAAS,EAAE,MAAM,OAAO,SAAS,EACjC,MAAM,EAAE,SAAS,GAAG,mBAAmB,GAAG,cAAc,GACtD,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEzD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAElD"}
package/dist/utils.js ADDED
@@ -0,0 +1,313 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { EventType } from "@aws-lambda-powertools/batch";
3
+ import KSUID from "ksuid";
4
+ import merge from "lodash.merge";
5
+ /**
6
+ * Generates a random UUID for use as a trace identifier.
7
+ *
8
+ * @returns A random UUID string
9
+ * @deprecated Not currently used - consider using X-Ray trace IDs instead
10
+ */
11
+ export function generateTraceId() {
12
+ return randomUUID();
13
+ }
14
+ /**
15
+ * Generates a unique, time-sortable audit ID using KSUID.
16
+ *
17
+ * KSUIDs (K-Sortable Unique Identifiers) are globally unique and
18
+ * naturally sort by creation time, making them ideal for audit logs.
19
+ *
20
+ * @returns A KSUID string
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const id = generateAuditId();
25
+ * // Returns something like "2KQHp1vF5AufI6Fp4pJUJYBCWKg"
26
+ * ```
27
+ */
28
+ export function generateAuditId() {
29
+ return `${KSUID.randomSync().string}`;
30
+ }
31
+ /**
32
+ * Increments the stage number in a trace ID.
33
+ *
34
+ * Trace IDs follow the format "id:stage" where stage is an integer.
35
+ * If no stage exists, it defaults to 1 before incrementing.
36
+ *
37
+ * @param trace - The trace in "id:stage" format
38
+ * @returns The trace with incremented stage number
39
+ *
40
+ * @internal
41
+ */
42
+ function incrementTraceStage(traceId) {
43
+ const [id, stage] = traceId.split(":");
44
+ const nextStage = Number(stage == null ? 1 : stage) + 1;
45
+ return [id, nextStage].join(":");
46
+ }
47
+ /**
48
+ * Builds an audit entry from an SQS record with optional overrides.
49
+ *
50
+ * Automatically extracts trace information from the SQS message body
51
+ * if the audit doesn't already have a trace ID set.
52
+ *
53
+ * @param sqsRecord - The SQS record containing the message
54
+ * @param audit - The base audit entry to build from
55
+ * @param overrides - Optional overrides to merge into the audit
56
+ * @returns The complete audit entry with trace information
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const audit = buildAuditFromSQSRecord(sqsRecord, {
61
+ * operation: 'processOrder',
62
+ * target: { app: 'OrderService', type: 'Order', id: '123' },
63
+ * });
64
+ * ```
65
+ */
66
+ export function buildAuditFromSQSRecord(sqsRecord, audit, overrides) {
67
+ // If a trace isn't set, try to set it from the sqs record
68
+ if (!audit.trace) {
69
+ const { detail } = JSON.parse(sqsRecord.body);
70
+ if (detail.transaction?.trace) {
71
+ audit.trace = incrementTraceStage(detail.transaction.trace);
72
+ }
73
+ }
74
+ return buildAudit(audit, overrides);
75
+ }
76
+ /**
77
+ * Builds an audit entry by deeply merging the base audit with overrides.
78
+ *
79
+ * Messages from both the base audit and overrides are concatenated
80
+ * with a space separator.
81
+ *
82
+ * @param audit - The base audit entry
83
+ * @param overrides - Optional overrides to merge into the audit
84
+ * @returns The merged audit entry
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const audit = buildAudit(
89
+ * { operation: 'create', message: 'Creating user' },
90
+ * { message: 'with admin role', status: 'success' }
91
+ * );
92
+ * // Result: { operation: 'create', message: 'Creating user with admin role', status: 'success' }
93
+ * ```
94
+ */
95
+ export function buildAudit(audit, overrides) {
96
+ const message = [audit.message, overrides?.message].filter(Boolean).join(" ");
97
+ return merge(audit, overrides || {}, { message });
98
+ }
99
+ /**
100
+ * Normalizes an EventBridge PutEvents request entry to a simplified event format.
101
+ *
102
+ * Converts the AWS SDK format (Detail, DetailType, Source) to the internal
103
+ * event format (detail, detail-type, source).
104
+ *
105
+ * @param input - The EventBridge PutEvents request entry
106
+ * @returns The normalized event object
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const event = normalizeEventBridgetInput({
111
+ * Detail: '{"orderId": "123"}',
112
+ * DetailType: 'OrderCreated',
113
+ * Source: 'orders.service',
114
+ * });
115
+ * // Returns: { detail: '{"orderId": "123"}', 'detail-type': 'OrderCreated', source: 'orders.service' }
116
+ * ```
117
+ */
118
+ export function normalizeEventBridgetInput(input) {
119
+ return {
120
+ detail: input.Detail,
121
+ "detail-type": input.DetailType,
122
+ source: input.Source,
123
+ };
124
+ }
125
+ /**
126
+ * Extracts audit overrides from a batch processor result.
127
+ *
128
+ * Handles various result formats from batch record handlers:
129
+ * - Objects with `_audit` property for explicit overrides
130
+ * - Objects with `message` property for simple message extraction
131
+ * - Non-objects or empty objects return empty overrides
132
+ *
133
+ * When both root message and audit override message exist, they are
134
+ * concatenated with a space separator.
135
+ *
136
+ * @param message - The result from a batch record handler
137
+ * @returns Partial audit input with extracted overrides
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * // With audit overrides
142
+ * extractOverridesFromBatchProcessor({
143
+ * message: 'Processed',
144
+ * _audit: { status: 'success', tier: 3 }
145
+ * });
146
+ * // Returns: { status: 'success', tier: 3, message: 'Processed' }
147
+ *
148
+ * // With just message
149
+ * extractOverridesFromBatchProcessor({ message: 'Done' });
150
+ * // Returns: { message: 'Done' }
151
+ *
152
+ * // With non-object
153
+ * extractOverridesFromBatchProcessor('success');
154
+ * // Returns: {}
155
+ * ```
156
+ */
157
+ export function extractOverridesFromBatchProcessor(message) {
158
+ if (message && typeof message === "object") {
159
+ // Get the root message
160
+ const rootMessage = "message" in message && typeof message.message === "string"
161
+ ? message.message
162
+ : undefined;
163
+ // Is there an audit override?
164
+ if ("_audit" in message) {
165
+ const auditOverrides = message._audit && typeof message._audit === "object"
166
+ ? message._audit
167
+ : {};
168
+ return {
169
+ ...auditOverrides,
170
+ message: [rootMessage, auditOverrides?.message]
171
+ .filter(Boolean)
172
+ .join(" "),
173
+ };
174
+ }
175
+ if (rootMessage) {
176
+ return { message: rootMessage };
177
+ }
178
+ }
179
+ return {};
180
+ }
181
+ /**
182
+ * Normalizes an event source record body to an EventBridge event format.
183
+ *
184
+ * Currently only supports SQS event type. For unsupported event types,
185
+ * returns undefined.
186
+ *
187
+ * @param eventType - The type of event source (SQS, Kinesis, DynamoDB)
188
+ * @param record - The event source record
189
+ * @returns The normalized EventBridge event, or undefined for unsupported types
190
+ */
191
+ export function normalizeEventBridgetEventBody(eventType, record) {
192
+ switch (eventType) {
193
+ case EventType.SQS:
194
+ return normalizeSQSEventBody(record);
195
+ }
196
+ }
197
+ /**
198
+ * Extracts EventBridge event data from an SQS record body.
199
+ *
200
+ * Parses the SQS message body and extracts the EventBridge-specific
201
+ * fields (detail, detail-type, source).
202
+ *
203
+ * @param record - The SQS record containing the message
204
+ * @returns The extracted EventBridge event data
205
+ */
206
+ export function normalizeSQSEventBody(record) {
207
+ const bodyObject = JSON.parse(record.body);
208
+ return {
209
+ detail: bodyObject?.detail,
210
+ "detail-type": bodyObject?.["detail-type"],
211
+ source: bodyObject?.source,
212
+ };
213
+ }
214
+ /**
215
+ * Parses a trace ID into its component parts.
216
+ *
217
+ * Trace IDs follow the format "id:stage" where:
218
+ * - id: The unique trace identifier
219
+ * - stage: The processing stage number (defaults to 0 if not present)
220
+ *
221
+ * @param traceId - The trace ID string to parse
222
+ * @returns An object with `id` and `stage` properties
223
+ *
224
+ * @example
225
+ * ```typescript
226
+ * getTraceParts("abc-123:5");
227
+ * // Returns: { id: "abc-123", stage: 5 }
228
+ *
229
+ * getTraceParts("abc-123");
230
+ * // Returns: { id: "abc-123", stage: 0 }
231
+ * ```
232
+ */
233
+ export function getTraceParts(traceId) {
234
+ const [id, stage] = traceId.split(":");
235
+ return {
236
+ id,
237
+ stage: Number(stage == null ? 0 : stage),
238
+ };
239
+ }
240
+ /**
241
+ * Extracts the unique record ID for audit correlation based on event type.
242
+ *
243
+ * Each event source has a stable unique identifier that persists across retries:
244
+ * - SQS: `messageId` - unique per message, stable across retry deliveries
245
+ * - Kinesis: `eventID` - unique per record in the stream
246
+ * - DynamoDB Streams: `eventID` - unique per stream record
247
+ *
248
+ * @param eventType - The type of event source (SQS, KinesisDataStreams, DynamoDBStreams)
249
+ * @param record - The event source record
250
+ * @returns The unique record ID, or undefined for unsupported event types
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * // SQS record
255
+ * const id = getRecordId("SQS", sqsRecord);
256
+ * // Returns: sqsRecord.messageId
257
+ *
258
+ * // Kinesis record
259
+ * const id = getRecordId("KinesisDataStreams", kinesisRecord);
260
+ * // Returns: kinesisRecord.eventID
261
+ * ```
262
+ */
263
+ export function getRecordId(eventType, record) {
264
+ switch (eventType) {
265
+ case EventType.SQS:
266
+ return record.messageId;
267
+ case EventType.KinesisDataStreams:
268
+ return record.eventID;
269
+ case EventType.DynamoDBStreams:
270
+ return record.eventID;
271
+ default:
272
+ return undefined;
273
+ }
274
+ }
275
+ /**
276
+ * Gets the approximate receive count from an SQS record.
277
+ *
278
+ * The receive count indicates how many times the message has been delivered:
279
+ * - 1 = First delivery (not a retry)
280
+ * - 2+ = Retry delivery
281
+ *
282
+ * @param record - The SQS record
283
+ * @returns The receive count as a number
284
+ *
285
+ * @example
286
+ * ```typescript
287
+ * const count = getReceiveCount(sqsRecord);
288
+ * if (count > 1) {
289
+ * console.log(`This is retry attempt ${count - 1}`);
290
+ * }
291
+ * ```
292
+ */
293
+ export function getReceiveCount(record) {
294
+ return Number(record.attributes.ApproximateReceiveCount);
295
+ }
296
+ /**
297
+ * Checks if an SQS message is being retried.
298
+ *
299
+ * A message is considered a retry if it has been received more than once.
300
+ *
301
+ * @param record - The SQS record
302
+ * @returns True if this is a retry delivery, false if first delivery
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * if (isRetry(sqsRecord)) {
307
+ * console.log("Processing retry...");
308
+ * }
309
+ * ```
310
+ */
311
+ export function isRetry(record) {
312
+ return getReceiveCount(record) > 1;
313
+ }
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@flipboxlabs/aws-audit-sdk",
3
+ "version": "1.1.0",
4
+ "description": "AWS Audit SDK - Core SDK for audit logging and event management",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./batch": {
13
+ "types": "./dist/batch/index.d.ts",
14
+ "import": "./dist/batch/index.js"
15
+ },
16
+ "./config": {
17
+ "types": "./dist/config.d.ts",
18
+ "import": "./dist/config.js"
19
+ },
20
+ "./middleware": {
21
+ "types": "./dist/middleware/middy.d.ts",
22
+ "import": "./dist/middleware/middy.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "dependencies": {
30
+ "@aws-lambda-powertools/batch": "^2.32.0",
31
+ "@aws-lambda-powertools/commons": "^2.32.0",
32
+ "@aws-lambda-powertools/logger": "^2.32.0",
33
+ "@aws-sdk/client-dynamodb": "^3.750.0",
34
+ "@aws-sdk/client-eventbridge": "^3.750.0",
35
+ "@aws-sdk/util-dynamodb": "^3.750.0",
36
+ "ksuid": "^3.0.0",
37
+ "lodash.merge": "^4.6.2",
38
+ "zod": "^4.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@tsconfig/node22": "^22.0.1",
42
+ "@types/aws-lambda": "^8.10.156",
43
+ "@types/lodash.merge": "^4.6.9",
44
+ "@types/node": "^22.15.0",
45
+ "@vitest/coverage-v8": "^2.1.1",
46
+ "ts-node": "^10.9.2",
47
+ "typescript": "^5.9.3",
48
+ "vitest": "^2.1.1"
49
+ },
50
+ "engines": {
51
+ "node": ">=24"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/flipboxlabs/aws-audit.git",
56
+ "directory": "sdk"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/flipboxlabs/aws-audit/issues"
60
+ },
61
+ "homepage": "https://github.com/flipboxlabs/aws-audit#readme",
62
+ "license": "MIT",
63
+ "keywords": [
64
+ "aws",
65
+ "audit",
66
+ "logging",
67
+ "eventbridge",
68
+ "dynamodb",
69
+ "lambda",
70
+ "serverless"
71
+ ],
72
+ "scripts": {
73
+ "build": "tsc -p ./tsconfig.build.json",
74
+ "check-types": "tsc --noEmit -p ./tsconfig.json",
75
+ "lint": "npx biome lint",
76
+ "lint:fix": "npx biome lint --fix",
77
+ "format": "npx biome format",
78
+ "format:fix": "npx biome format --fix",
79
+ "pre-commit": "pnpm run check-types && pnpm run format && pnpm run lint",
80
+ "test": "vitest run",
81
+ "test:watch": "vitest",
82
+ "test:coverage": "vitest run --coverage"
83
+ },
84
+ "types": "./dist/index.d.ts"
85
+ }