@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.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/audits.d.ts +188 -0
- package/dist/audits.d.ts.map +1 -0
- package/dist/audits.js +225 -0
- package/dist/batch/batch-processor.d.ts +81 -0
- package/dist/batch/batch-processor.d.ts.map +1 -0
- package/dist/batch/batch-processor.js +102 -0
- package/dist/batch/index.d.ts +2 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +1 -0
- package/dist/config.d.ts +65 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/constants.d.ts +147 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +191 -0
- package/dist/events/batch-handler.d.ts +62 -0
- package/dist/events/batch-handler.d.ts.map +1 -0
- package/dist/events/batch-handler.js +92 -0
- package/dist/events/bus.d.ts +79 -0
- package/dist/events/bus.d.ts.map +1 -0
- package/dist/events/bus.js +86 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +2 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/middleware/middy.d.ts +66 -0
- package/dist/middleware/middy.d.ts.map +1 -0
- package/dist/middleware/middy.js +105 -0
- package/dist/repository.d.ts +421 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +486 -0
- package/dist/repository.utils.d.ts +37 -0
- package/dist/repository.utils.d.ts.map +1 -0
- package/dist/repository.utils.js +70 -0
- package/dist/schema/audit.d.ts +202 -0
- package/dist/schema/audit.d.ts.map +1 -0
- package/dist/schema/audit.js +48 -0
- package/dist/schema/common.d.ts +128 -0
- package/dist/schema/common.d.ts.map +1 -0
- package/dist/schema/common.js +148 -0
- package/dist/schema/index.d.ts +27 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +26 -0
- package/dist/schema/log.d.ts +211 -0
- package/dist/schema/log.d.ts.map +1 -0
- package/dist/schema/log.js +179 -0
- package/dist/schema/model.d.ts +110 -0
- package/dist/schema/model.d.ts.map +1 -0
- package/dist/schema/model.js +86 -0
- package/dist/schema/service.d.ts +189 -0
- package/dist/schema/service.d.ts.map +1 -0
- package/dist/schema/service.js +89 -0
- package/dist/schema/storage.d.ts +105 -0
- package/dist/schema/storage.d.ts.map +1 -0
- package/dist/schema/storage.js +63 -0
- package/dist/service.d.ts +285 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +237 -0
- package/dist/test-config.d.ts +56 -0
- package/dist/test-config.d.ts.map +1 -0
- package/dist/test-config.js +24 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +231 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +313 -0
- package/package.json +85 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type { Logger } from "@aws-lambda-powertools/logger";
|
|
2
|
+
import type { AuditConfig } from "./config.js";
|
|
3
|
+
import { AuditEventBus } from "./events/bus.js";
|
|
4
|
+
import { AuditRepository } from "./repository.js";
|
|
5
|
+
import type { Audit } from "./schema/audit.js";
|
|
6
|
+
import type { Pagination } from "./schema/model.js";
|
|
7
|
+
import { type UpsertAuditInput } from "./schema/service.js";
|
|
8
|
+
import type { InferApp, InferResourceType } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Typed identifiers for locating audit records.
|
|
11
|
+
* Uses config-derived App and ResourceType types for strict typing.
|
|
12
|
+
*/
|
|
13
|
+
export type TypedIdentifiers<C extends AuditConfig> = {
|
|
14
|
+
/** Tenant/organization identifier for multi-tenancy support (optional) */
|
|
15
|
+
tenantId?: string;
|
|
16
|
+
/** Unique audit record identifier */
|
|
17
|
+
id: string | number;
|
|
18
|
+
/** Application that owns this audit record */
|
|
19
|
+
app: InferApp<C>;
|
|
20
|
+
/** Optional resource identifier within the application */
|
|
21
|
+
resourceId?: string | number;
|
|
22
|
+
/** Type of resource being audited */
|
|
23
|
+
resourceType: InferResourceType<C>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Typed options for listing audit items by resource.
|
|
27
|
+
* Uses config-derived App and ResourceType types for strict typing.
|
|
28
|
+
*/
|
|
29
|
+
export type TypedListItemsOptions<C extends AuditConfig> = {
|
|
30
|
+
/** Tenant/organization identifier for multi-tenancy support (optional) */
|
|
31
|
+
tenantId?: string;
|
|
32
|
+
/** Resource identification */
|
|
33
|
+
resource: {
|
|
34
|
+
/** Type of the resource */
|
|
35
|
+
type: InferResourceType<C>;
|
|
36
|
+
/** Unique identifier of the resource */
|
|
37
|
+
id: string;
|
|
38
|
+
};
|
|
39
|
+
/** Application owning the resource */
|
|
40
|
+
app: InferApp<C>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Typed options for listing audit items by trace ID.
|
|
44
|
+
* Uses config-derived App and ResourceType types for strict typing.
|
|
45
|
+
*/
|
|
46
|
+
export type TypedListTraceItems<C extends AuditConfig> = {
|
|
47
|
+
/** Tenant/organization identifier for multi-tenancy support (optional) */
|
|
48
|
+
tenantId?: string;
|
|
49
|
+
/** Trace ID to query for related audit records */
|
|
50
|
+
trace: string;
|
|
51
|
+
/** Optional application filter */
|
|
52
|
+
app?: InferApp<C>;
|
|
53
|
+
/** Optional resource filter */
|
|
54
|
+
resource?: {
|
|
55
|
+
/** Filter by resource type */
|
|
56
|
+
type?: InferResourceType<C>;
|
|
57
|
+
/** Filter by resource ID */
|
|
58
|
+
id?: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* High-level service for managing audit records with typed App and ResourceType.
|
|
63
|
+
*
|
|
64
|
+
* Provides a business logic layer over the AuditRepository for CRUD operations
|
|
65
|
+
* and coordinates with EventBridge for event-driven notifications.
|
|
66
|
+
*
|
|
67
|
+
* Features:
|
|
68
|
+
* - Type-safe app and resourceType parameters derived from config
|
|
69
|
+
* - Automatic creation of related resource audit entries
|
|
70
|
+
* - Schema validation on upsert operations
|
|
71
|
+
* - EventBridge integration for audit event notifications
|
|
72
|
+
* - Paginated list queries with filtering
|
|
73
|
+
*
|
|
74
|
+
* @typeParam C - The audit config type for type inference
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const config = defineAuditConfig({
|
|
79
|
+
* apps: ['Orders', 'Inventory'] as const,
|
|
80
|
+
* resourceTypes: ['Order', 'Product'] as const,
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* const service = new AuditService(logger, config);
|
|
84
|
+
*
|
|
85
|
+
* // Upsert an audit with related resources
|
|
86
|
+
* await service.upsertItem({
|
|
87
|
+
* operation: 'createOrder',
|
|
88
|
+
* target: { app: 'Orders', type: 'Order', id: '123' },
|
|
89
|
+
* resources: [
|
|
90
|
+
* { app: 'Inventory', type: 'Product', id: '456' },
|
|
91
|
+
* ],
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* // Retrieve a specific audit - app and resourceType are typed!
|
|
95
|
+
* const audit = await service.getItem({
|
|
96
|
+
* id: 'audit-123',
|
|
97
|
+
* app: 'Orders', // TypeScript knows valid values
|
|
98
|
+
* resourceType: 'Order', // Autocomplete works
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare class AuditService<C extends AuditConfig> {
|
|
103
|
+
private readonly logger;
|
|
104
|
+
readonly events: null | undefined | AuditEventBus;
|
|
105
|
+
private readonly storage;
|
|
106
|
+
private readonly config;
|
|
107
|
+
/**
|
|
108
|
+
* Creates a new AuditService instance.
|
|
109
|
+
*
|
|
110
|
+
* @param logger - Logger instance for error logging
|
|
111
|
+
* @param config - Audit configuration for type inference
|
|
112
|
+
* @param storage - Repository for DynamoDB operations (defaults to new AuditRepository)
|
|
113
|
+
* @param events - EventBridge bus for audit notifications (defaults to new AuditEventBus, can be null/undefined to disable)
|
|
114
|
+
*/
|
|
115
|
+
constructor(logger: Logger, config: C, storage?: AuditRepository<C>, events?: null | undefined | AuditEventBus);
|
|
116
|
+
/**
|
|
117
|
+
* Retrieves a single audit record by its identifiers.
|
|
118
|
+
*
|
|
119
|
+
* @param identifiers - The audit identifiers (id, app, resourceType)
|
|
120
|
+
* @returns The audit record
|
|
121
|
+
* @throws Error if the audit record is not found
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const audit = await service.getItem({
|
|
126
|
+
* id: 'audit-123',
|
|
127
|
+
* app: 'MyApp',
|
|
128
|
+
* resourceType: 'User',
|
|
129
|
+
* });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
getItem(identifiers: Omit<TypedIdentifiers<C>, "resourceId">): Promise<Audit>;
|
|
133
|
+
/**
|
|
134
|
+
* Creates or updates an audit record and its related resource entries.
|
|
135
|
+
*
|
|
136
|
+
* For each resource in the `resources` array that has an ID:
|
|
137
|
+
* - Creates a separate audit entry with the resource as the target
|
|
138
|
+
* - Links it to the parent audit via a generated composite ID
|
|
139
|
+
* - Sets the original target as the source
|
|
140
|
+
* - Inherits rerunable status from the parent or event presence
|
|
141
|
+
*
|
|
142
|
+
* When an audit with the same ID already exists (retry scenario):
|
|
143
|
+
* - Appends the current execution to the `attempts` array
|
|
144
|
+
* - Preserves the original `createdAt` timestamp
|
|
145
|
+
* - Increments the attempt number automatically
|
|
146
|
+
*
|
|
147
|
+
* After storage, publishes an "Upserted" event to EventBridge (if events is configured).
|
|
148
|
+
*
|
|
149
|
+
* @param input - The audit data to upsert
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* await service.upsertItem({
|
|
154
|
+
* operation: 'processPayment',
|
|
155
|
+
* target: { app: 'Payments', type: 'Transaction', id: 'txn-123' },
|
|
156
|
+
* status: 'success',
|
|
157
|
+
* resources: [
|
|
158
|
+
* { app: 'Users', type: 'User', id: 'user-456' },
|
|
159
|
+
* { app: 'Orders', type: 'Order', id: 'order-789' },
|
|
160
|
+
* ],
|
|
161
|
+
* });
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
upsertItem(input: UpsertAuditInput): Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Lists audit records with filtering and pagination.
|
|
167
|
+
*
|
|
168
|
+
* Queries audits based on resource type, app, and optional resource ID.
|
|
169
|
+
* Results are paginated and can be filtered by various criteria.
|
|
170
|
+
*
|
|
171
|
+
* @param params - Query parameters for filtering
|
|
172
|
+
* @param pagination - Optional pagination settings
|
|
173
|
+
* @returns Paginated collection of audit records
|
|
174
|
+
* @throws Re-throws any storage errors after logging
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```typescript
|
|
178
|
+
* const { items, pagination } = await service.listItems({
|
|
179
|
+
* resource: { type: 'Order', id: '123' },
|
|
180
|
+
* app: 'Orders',
|
|
181
|
+
* });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
listItems(params: TypedListItemsOptions<C>, pagination?: Pagination): Promise<{
|
|
185
|
+
items: {
|
|
186
|
+
id: string;
|
|
187
|
+
status: "success" | "warn" | "fail" | "skip";
|
|
188
|
+
tier: number;
|
|
189
|
+
target: {
|
|
190
|
+
app: string;
|
|
191
|
+
type: string;
|
|
192
|
+
id?: string | number | undefined;
|
|
193
|
+
};
|
|
194
|
+
operation: string;
|
|
195
|
+
tenantId?: string | undefined;
|
|
196
|
+
source?: {
|
|
197
|
+
app: string;
|
|
198
|
+
type: string;
|
|
199
|
+
id?: string | number | undefined;
|
|
200
|
+
} | undefined;
|
|
201
|
+
context?: Record<string, string | number | boolean | {
|
|
202
|
+
[key: string]: string | number | boolean | /*elided*/ any;
|
|
203
|
+
}> | undefined;
|
|
204
|
+
message?: string | undefined;
|
|
205
|
+
rerunable?: boolean | undefined;
|
|
206
|
+
trace?: string | undefined;
|
|
207
|
+
event?: {
|
|
208
|
+
detail: string | undefined;
|
|
209
|
+
source?: string | undefined;
|
|
210
|
+
"detail-type"?: string | undefined;
|
|
211
|
+
} | undefined;
|
|
212
|
+
error?: any;
|
|
213
|
+
attempts?: {
|
|
214
|
+
number: number;
|
|
215
|
+
status: "success" | "warn" | "fail" | "skip";
|
|
216
|
+
at: string;
|
|
217
|
+
error?: any;
|
|
218
|
+
}[] | undefined;
|
|
219
|
+
}[];
|
|
220
|
+
pagination?: {
|
|
221
|
+
pageSize?: string | number | null | undefined;
|
|
222
|
+
nextToken?: string | null | undefined;
|
|
223
|
+
} | undefined;
|
|
224
|
+
}>;
|
|
225
|
+
/**
|
|
226
|
+
* Lists audit records by trace ID with pagination.
|
|
227
|
+
*
|
|
228
|
+
* Retrieves all audits associated with a distributed trace,
|
|
229
|
+
* ordered by their stage in the processing pipeline.
|
|
230
|
+
*
|
|
231
|
+
* @param params - Query parameters including the trace ID
|
|
232
|
+
* @param pagination - Optional pagination settings
|
|
233
|
+
* @returns Paginated collection of trace-related audit records
|
|
234
|
+
* @throws Re-throws any storage errors after logging
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* const { items } = await service.listTraceItems({
|
|
239
|
+
* trace: 'trace-abc-123',
|
|
240
|
+
* });
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
listTraceItems(params: TypedListTraceItems<C>, pagination?: Pagination): Promise<{
|
|
244
|
+
items: {
|
|
245
|
+
id: string;
|
|
246
|
+
status: "success" | "warn" | "fail" | "skip";
|
|
247
|
+
tier: number;
|
|
248
|
+
target: {
|
|
249
|
+
app: string;
|
|
250
|
+
type: string;
|
|
251
|
+
id?: string | number | undefined;
|
|
252
|
+
};
|
|
253
|
+
operation: string;
|
|
254
|
+
tenantId?: string | undefined;
|
|
255
|
+
source?: {
|
|
256
|
+
app: string;
|
|
257
|
+
type: string;
|
|
258
|
+
id?: string | number | undefined;
|
|
259
|
+
} | undefined;
|
|
260
|
+
context?: Record<string, string | number | boolean | {
|
|
261
|
+
[key: string]: string | number | boolean | /*elided*/ any;
|
|
262
|
+
}> | undefined;
|
|
263
|
+
message?: string | undefined;
|
|
264
|
+
rerunable?: boolean | undefined;
|
|
265
|
+
trace?: string | undefined;
|
|
266
|
+
event?: {
|
|
267
|
+
detail: string | undefined;
|
|
268
|
+
source?: string | undefined;
|
|
269
|
+
"detail-type"?: string | undefined;
|
|
270
|
+
} | undefined;
|
|
271
|
+
error?: any;
|
|
272
|
+
attempts?: {
|
|
273
|
+
number: number;
|
|
274
|
+
status: "success" | "warn" | "fail" | "skip";
|
|
275
|
+
at: string;
|
|
276
|
+
error?: any;
|
|
277
|
+
}[] | undefined;
|
|
278
|
+
}[];
|
|
279
|
+
pagination?: {
|
|
280
|
+
pageSize?: string | number | null | undefined;
|
|
281
|
+
nextToken?: string | null | undefined;
|
|
282
|
+
} | undefined;
|
|
283
|
+
}>;
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { AuditEventBus } from "./events/bus.js";
|
|
2
|
+
import { AuditRepository } from "./repository.js";
|
|
3
|
+
import { createTypedUpsertAuditSchema, } from "./schema/service.js";
|
|
4
|
+
/**
|
|
5
|
+
* High-level service for managing audit records with typed App and ResourceType.
|
|
6
|
+
*
|
|
7
|
+
* Provides a business logic layer over the AuditRepository for CRUD operations
|
|
8
|
+
* and coordinates with EventBridge for event-driven notifications.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Type-safe app and resourceType parameters derived from config
|
|
12
|
+
* - Automatic creation of related resource audit entries
|
|
13
|
+
* - Schema validation on upsert operations
|
|
14
|
+
* - EventBridge integration for audit event notifications
|
|
15
|
+
* - Paginated list queries with filtering
|
|
16
|
+
*
|
|
17
|
+
* @typeParam C - The audit config type for type inference
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const config = defineAuditConfig({
|
|
22
|
+
* apps: ['Orders', 'Inventory'] as const,
|
|
23
|
+
* resourceTypes: ['Order', 'Product'] as const,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* const service = new AuditService(logger, config);
|
|
27
|
+
*
|
|
28
|
+
* // Upsert an audit with related resources
|
|
29
|
+
* await service.upsertItem({
|
|
30
|
+
* operation: 'createOrder',
|
|
31
|
+
* target: { app: 'Orders', type: 'Order', id: '123' },
|
|
32
|
+
* resources: [
|
|
33
|
+
* { app: 'Inventory', type: 'Product', id: '456' },
|
|
34
|
+
* ],
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* // Retrieve a specific audit - app and resourceType are typed!
|
|
38
|
+
* const audit = await service.getItem({
|
|
39
|
+
* id: 'audit-123',
|
|
40
|
+
* app: 'Orders', // TypeScript knows valid values
|
|
41
|
+
* resourceType: 'Order', // Autocomplete works
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export class AuditService {
|
|
46
|
+
logger;
|
|
47
|
+
events;
|
|
48
|
+
storage;
|
|
49
|
+
config;
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new AuditService instance.
|
|
52
|
+
*
|
|
53
|
+
* @param logger - Logger instance for error logging
|
|
54
|
+
* @param config - Audit configuration for type inference
|
|
55
|
+
* @param storage - Repository for DynamoDB operations (defaults to new AuditRepository)
|
|
56
|
+
* @param events - EventBridge bus for audit notifications (defaults to new AuditEventBus, can be null/undefined to disable)
|
|
57
|
+
*/
|
|
58
|
+
constructor(logger, config, storage, events = new AuditEventBus(logger)) {
|
|
59
|
+
this.logger = logger;
|
|
60
|
+
this.events = events;
|
|
61
|
+
this.config = config;
|
|
62
|
+
this.storage = storage ?? new AuditRepository(logger, config);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Retrieves a single audit record by its identifiers.
|
|
66
|
+
*
|
|
67
|
+
* @param identifiers - The audit identifiers (id, app, resourceType)
|
|
68
|
+
* @returns The audit record
|
|
69
|
+
* @throws Error if the audit record is not found
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const audit = await service.getItem({
|
|
74
|
+
* id: 'audit-123',
|
|
75
|
+
* app: 'MyApp',
|
|
76
|
+
* resourceType: 'User',
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
async getItem(identifiers) {
|
|
81
|
+
const result = await this.storage.getItem(identifiers);
|
|
82
|
+
if (result == null) {
|
|
83
|
+
throw new Error(`Unable to find Audit ${Array.from(Object.entries(identifiers))
|
|
84
|
+
.map(([key, value]) => `${key}:${value}`)
|
|
85
|
+
.join(" | ")}`);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Creates or updates an audit record and its related resource entries.
|
|
91
|
+
*
|
|
92
|
+
* For each resource in the `resources` array that has an ID:
|
|
93
|
+
* - Creates a separate audit entry with the resource as the target
|
|
94
|
+
* - Links it to the parent audit via a generated composite ID
|
|
95
|
+
* - Sets the original target as the source
|
|
96
|
+
* - Inherits rerunable status from the parent or event presence
|
|
97
|
+
*
|
|
98
|
+
* When an audit with the same ID already exists (retry scenario):
|
|
99
|
+
* - Appends the current execution to the `attempts` array
|
|
100
|
+
* - Preserves the original `createdAt` timestamp
|
|
101
|
+
* - Increments the attempt number automatically
|
|
102
|
+
*
|
|
103
|
+
* After storage, publishes an "Upserted" event to EventBridge (if events is configured).
|
|
104
|
+
*
|
|
105
|
+
* @param input - The audit data to upsert
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* await service.upsertItem({
|
|
110
|
+
* operation: 'processPayment',
|
|
111
|
+
* target: { app: 'Payments', type: 'Transaction', id: 'txn-123' },
|
|
112
|
+
* status: 'success',
|
|
113
|
+
* resources: [
|
|
114
|
+
* { app: 'Users', type: 'User', id: 'user-456' },
|
|
115
|
+
* { app: 'Orders', type: 'Order', id: 'order-789' },
|
|
116
|
+
* ],
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
async upsertItem(input) {
|
|
121
|
+
const schema = createTypedUpsertAuditSchema(this.config.schemas.resourceReference);
|
|
122
|
+
const item = schema.parse(input);
|
|
123
|
+
const now = new Date().toISOString();
|
|
124
|
+
// Build current attempt record
|
|
125
|
+
const currentAttempt = {
|
|
126
|
+
number: 1,
|
|
127
|
+
status: item.status,
|
|
128
|
+
error: item.error,
|
|
129
|
+
at: now,
|
|
130
|
+
};
|
|
131
|
+
// Check for existing audit to handle retry tracking
|
|
132
|
+
if (item.id) {
|
|
133
|
+
try {
|
|
134
|
+
const existing = await this.storage.getItem({
|
|
135
|
+
id: item.id,
|
|
136
|
+
app: item.target.app,
|
|
137
|
+
resourceType: item.target.type,
|
|
138
|
+
tenantId: item.tenantId,
|
|
139
|
+
});
|
|
140
|
+
if (existing) {
|
|
141
|
+
// Append to existing attempts array
|
|
142
|
+
const existingAttempts = existing.attempts ?? [];
|
|
143
|
+
currentAttempt.number = existingAttempts.length + 1;
|
|
144
|
+
item.attempts = [...existingAttempts, currentAttempt];
|
|
145
|
+
// Preserve original createdAt
|
|
146
|
+
item.createdAt = existing.createdAt.toISOString();
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
item.attempts = [currentAttempt];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Audit doesn't exist, this is the first attempt
|
|
154
|
+
item.attempts = [currentAttempt];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
item.attempts = [currentAttempt];
|
|
159
|
+
}
|
|
160
|
+
const batch = Array.from(item.resources || [])
|
|
161
|
+
.filter((resource) => !!resource.id)
|
|
162
|
+
.map((resource) => ({
|
|
163
|
+
...item,
|
|
164
|
+
...resource,
|
|
165
|
+
id: `${item.id}#${resource.app}.${resource.type}#${resource.id}`, // deterministic ID for retry correlation
|
|
166
|
+
// Source
|
|
167
|
+
source: item.target,
|
|
168
|
+
rerunable: item.rerunable !== undefined ? item.rerunable : !!item.event,
|
|
169
|
+
// Remove these (they are only needed on the parent)
|
|
170
|
+
event: undefined,
|
|
171
|
+
result: undefined,
|
|
172
|
+
error: undefined,
|
|
173
|
+
attempts: undefined,
|
|
174
|
+
}));
|
|
175
|
+
await this.storage.upsertBatch([item, ...batch]);
|
|
176
|
+
await this.events?.upserted([item]);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Lists audit records with filtering and pagination.
|
|
180
|
+
*
|
|
181
|
+
* Queries audits based on resource type, app, and optional resource ID.
|
|
182
|
+
* Results are paginated and can be filtered by various criteria.
|
|
183
|
+
*
|
|
184
|
+
* @param params - Query parameters for filtering
|
|
185
|
+
* @param pagination - Optional pagination settings
|
|
186
|
+
* @returns Paginated collection of audit records
|
|
187
|
+
* @throws Re-throws any storage errors after logging
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* const { items, pagination } = await service.listItems({
|
|
192
|
+
* resource: { type: 'Order', id: '123' },
|
|
193
|
+
* app: 'Orders',
|
|
194
|
+
* });
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
async listItems(params, pagination) {
|
|
198
|
+
try {
|
|
199
|
+
return await this.storage.listItems(params, pagination);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
this.logger.error("An error occurred while trying to list items", {
|
|
203
|
+
error,
|
|
204
|
+
});
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Lists audit records by trace ID with pagination.
|
|
210
|
+
*
|
|
211
|
+
* Retrieves all audits associated with a distributed trace,
|
|
212
|
+
* ordered by their stage in the processing pipeline.
|
|
213
|
+
*
|
|
214
|
+
* @param params - Query parameters including the trace ID
|
|
215
|
+
* @param pagination - Optional pagination settings
|
|
216
|
+
* @returns Paginated collection of trace-related audit records
|
|
217
|
+
* @throws Re-throws any storage errors after logging
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const { items } = await service.listTraceItems({
|
|
222
|
+
* trace: 'trace-abc-123',
|
|
223
|
+
* });
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
async listTraceItems(params, pagination) {
|
|
227
|
+
try {
|
|
228
|
+
return await this.storage.listTraceItems(params, pagination);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
this.logger.error("An error occurred while trying to list trace items", {
|
|
232
|
+
error,
|
|
233
|
+
});
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test configuration for SDK tests.
|
|
3
|
+
* Provides sample apps and resource types for testing.
|
|
4
|
+
*/
|
|
5
|
+
export declare const testConfig: {
|
|
6
|
+
service: string | undefined;
|
|
7
|
+
} & {
|
|
8
|
+
readonly apps: readonly ["App1", "TestApp"];
|
|
9
|
+
readonly resourceTypes: readonly ["Unknown", "User", "Order"];
|
|
10
|
+
} & {
|
|
11
|
+
schemas: {
|
|
12
|
+
app: import("zod").ZodEnum<{
|
|
13
|
+
[x: string]: string;
|
|
14
|
+
}>;
|
|
15
|
+
resourceType: import("zod").ZodEnum<{
|
|
16
|
+
[x: string]: string;
|
|
17
|
+
}>;
|
|
18
|
+
resourceReference: import("zod").ZodObject<{
|
|
19
|
+
app: import("zod").ZodEnum<{
|
|
20
|
+
[x: string]: string;
|
|
21
|
+
}>;
|
|
22
|
+
type: import("zod").ZodEnum<{
|
|
23
|
+
[x: string]: string;
|
|
24
|
+
}>;
|
|
25
|
+
id: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>>;
|
|
26
|
+
}, import("zod/v4/core").$strip>;
|
|
27
|
+
};
|
|
28
|
+
_types: {
|
|
29
|
+
App: "App1" | "TestApp";
|
|
30
|
+
ResourceType: "Unknown" | "User" | "Order";
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Type alias for the App union type from the test config.
|
|
35
|
+
*/
|
|
36
|
+
export type App = (typeof testConfig)["_types"]["App"];
|
|
37
|
+
/**
|
|
38
|
+
* Type alias for the ResourceType union type from the test config.
|
|
39
|
+
*/
|
|
40
|
+
export type ResourceType = (typeof testConfig)["_types"]["ResourceType"];
|
|
41
|
+
/**
|
|
42
|
+
* Enum-like object for App values in tests.
|
|
43
|
+
*/
|
|
44
|
+
export declare const App: {
|
|
45
|
+
readonly App1: "App1";
|
|
46
|
+
readonly TestApp: "TestApp";
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Enum-like object for ResourceType values in tests.
|
|
50
|
+
*/
|
|
51
|
+
export declare const ResourceType: {
|
|
52
|
+
readonly UNKNOWN: "Unknown";
|
|
53
|
+
readonly USER: "User";
|
|
54
|
+
readonly ORDER: "Order";
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=test-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-config.d.ts","sourceRoot":"","sources":["../src/test-config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGrB,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,cAAc,CAAC,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,GAAG;;;CAGN,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,YAAY;;;;CAIf,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineAuditConfig } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Test configuration for SDK tests.
|
|
4
|
+
* Provides sample apps and resource types for testing.
|
|
5
|
+
*/
|
|
6
|
+
export const testConfig = defineAuditConfig({
|
|
7
|
+
apps: ["App1", "TestApp"],
|
|
8
|
+
resourceTypes: ["Unknown", "User", "Order"],
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Enum-like object for App values in tests.
|
|
12
|
+
*/
|
|
13
|
+
export const App = {
|
|
14
|
+
App1: "App1",
|
|
15
|
+
TestApp: "TestApp",
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Enum-like object for ResourceType values in tests.
|
|
19
|
+
*/
|
|
20
|
+
export const ResourceType = {
|
|
21
|
+
UNKNOWN: "Unknown",
|
|
22
|
+
USER: "User",
|
|
23
|
+
ORDER: "Order",
|
|
24
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AuditConfig } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the App type from an AuditConfig.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const config = defineAuditConfig({
|
|
8
|
+
* apps: ['Orders', 'Inventory'] as const,
|
|
9
|
+
* resourceTypes: ['Order', 'Product'] as const,
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* type App = InferApp<typeof config>; // 'Orders' | 'Inventory'
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export type InferApp<C extends AuditConfig> = C["_types"]["App"];
|
|
16
|
+
/**
|
|
17
|
+
* Extracts the ResourceType type from an AuditConfig.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const config = defineAuditConfig({
|
|
22
|
+
* apps: ['Orders', 'Inventory'] as const,
|
|
23
|
+
* resourceTypes: ['Order', 'Product'] as const,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* type ResourceType = InferResourceType<typeof config>; // 'Order' | 'Product'
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export type InferResourceType<C extends AuditConfig> = C["_types"]["ResourceType"];
|
|
30
|
+
/**
|
|
31
|
+
* Generic App type - accepts any string.
|
|
32
|
+
* For strict typing, use InferApp<typeof config> with your config.
|
|
33
|
+
*/
|
|
34
|
+
export type AnyApp = string;
|
|
35
|
+
/**
|
|
36
|
+
* Generic ResourceType type - accepts any string.
|
|
37
|
+
* For strict typing, use InferResourceType<typeof config> with your config.
|
|
38
|
+
*/
|
|
39
|
+
export type AnyResourceType = string;
|
|
40
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC;AAEjE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,WAAW,IAClD,CAAC,CAAC,QAAQ,CAAC,CAAC,cAAc,CAAC,CAAC;AAE7B;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|