@h-ai/audit 0.1.0-alpha5

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/index.js ADDED
@@ -0,0 +1,594 @@
1
+ import { z } from 'zod';
2
+ import { core, err, ok } from '@h-ai/core';
3
+ import { validateIdentifiers, BaseReldbCrudRepository, reldb } from '@h-ai/reldb';
4
+
5
+ // src/audit-config.ts
6
+ var AuditInitConfigSchema = z.object({
7
+ /** 用户表名,用于 list 查询时 LEFT JOIN 获取用户名 */
8
+ userTable: z.string().default("hai_iam_users"),
9
+ /** 用户表主键列名,用于 JOIN 条件 */
10
+ userIdColumn: z.string().default("id"),
11
+ /** 用户表用户名列名,用于 SELECT 输出 */
12
+ userNameColumn: z.string().default("username")
13
+ });
14
+ function toVoid(result) {
15
+ if (!result.success) {
16
+ return result;
17
+ }
18
+ return ok(void 0);
19
+ }
20
+ function createHelper(logFn) {
21
+ return {
22
+ async login(userId, ip, ua) {
23
+ const result = await logFn({ userId, action: "login", resource: "auth", ipAddress: ip, userAgent: ua });
24
+ return toVoid(result);
25
+ },
26
+ async logout(userId, ip, ua) {
27
+ const result = await logFn({ userId, action: "logout", resource: "auth", ipAddress: ip, userAgent: ua });
28
+ return toVoid(result);
29
+ },
30
+ async register(userId, ip, ua) {
31
+ const result = await logFn({ userId, action: "register", resource: "auth", resourceId: userId, ipAddress: ip, userAgent: ua });
32
+ return toVoid(result);
33
+ },
34
+ async passwordResetRequest(email, ip, ua) {
35
+ const result = await logFn({ action: "password_reset_request", resource: "auth", details: { email }, ipAddress: ip, userAgent: ua });
36
+ return toVoid(result);
37
+ },
38
+ async passwordResetComplete(userId, ip, ua) {
39
+ const result = await logFn({ userId, action: "password_reset", resource: "auth", ipAddress: ip, userAgent: ua });
40
+ return toVoid(result);
41
+ },
42
+ async crud(input) {
43
+ const result = await logFn({
44
+ userId: input.userId,
45
+ action: input.action,
46
+ resource: input.resource,
47
+ resourceId: input.resourceId,
48
+ details: input.details,
49
+ ipAddress: input.ip,
50
+ userAgent: input.ua
51
+ });
52
+ return toVoid(result);
53
+ }
54
+ };
55
+ }
56
+
57
+ // messages/en-US.json
58
+ var en_US_default = {
59
+ $schema: "https://inlang.com/schema/inlang-message-format",
60
+ audit_notInitialized: "Audit module not initialized, call audit.init() first",
61
+ audit_initFailed: "Audit module initialization failed: {error}",
62
+ audit_logFailed: "Failed to record audit log: {error}",
63
+ audit_queryFailed: "Failed to query audit logs: {error}",
64
+ audit_cleanupFailed: "Failed to cleanup audit logs: {error}",
65
+ audit_statsFailed: "Failed to query audit statistics: {error}",
66
+ audit_configError: "Audit config validation failed: {error}",
67
+ audit_initInProgress: "Audit module initialization is already in progress",
68
+ audit_invalidInput: "Invalid audit input for {field}: {reason}"
69
+ };
70
+
71
+ // messages/zh-CN.json
72
+ var zh_CN_default = {
73
+ $schema: "https://inlang.com/schema/inlang-message-format",
74
+ audit_notInitialized: "\u5BA1\u8BA1\u6A21\u5757\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 audit.init()",
75
+ audit_initFailed: "\u5BA1\u8BA1\u6A21\u5757\u521D\u59CB\u5316\u5931\u8D25\uFF1A{error}",
76
+ audit_logFailed: "\u5BA1\u8BA1\u65E5\u5FD7\u8BB0\u5F55\u5931\u8D25\uFF1A{error}",
77
+ audit_queryFailed: "\u5BA1\u8BA1\u65E5\u5FD7\u67E5\u8BE2\u5931\u8D25\uFF1A{error}",
78
+ audit_cleanupFailed: "\u5BA1\u8BA1\u65E5\u5FD7\u6E05\u7406\u5931\u8D25\uFF1A{error}",
79
+ audit_statsFailed: "\u5BA1\u8BA1\u7EDF\u8BA1\u67E5\u8BE2\u5931\u8D25\uFF1A{error}",
80
+ audit_configError: "\u5BA1\u8BA1\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
81
+ audit_initInProgress: "\u5BA1\u8BA1\u6A21\u5757\u6B63\u5728\u521D\u59CB\u5316\u4E2D\uFF0C\u8BF7\u52FF\u5E76\u53D1\u8C03\u7528",
82
+ audit_invalidInput: "\u5BA1\u8BA1\u8F93\u5165\u53C2\u6570\u65E0\u6548\uFF08{field}\uFF09\uFF1A{reason}"
83
+ };
84
+
85
+ // src/audit-i18n.ts
86
+ var auditM = core.i18n.createMessageGetter({
87
+ "zh-CN": zh_CN_default,
88
+ "en-US": en_US_default
89
+ });
90
+ var AuditErrorInfo = {
91
+ LOG_FAILED: "001:500",
92
+ QUERY_FAILED: "002:500",
93
+ CLEANUP_FAILED: "003:500",
94
+ STATS_FAILED: "004:500",
95
+ INIT_IN_PROGRESS: "005:409",
96
+ NOT_INITIALIZED: "010:500",
97
+ CONFIG_ERROR: "012:500"
98
+ };
99
+ var HaiAuditError = core.error.buildHaiErrorsDef("audit", AuditErrorInfo);
100
+
101
+ // src/audit-repository-log.ts
102
+ var logger = core.logger.child({ module: "audit", scope: "repository" });
103
+ var AUDIT_TABLE = "hai_audit_logs";
104
+ var AuditLogRepository = class extends BaseReldbCrudRepository {
105
+ repoConfig;
106
+ isSqlite;
107
+ /**
108
+ * @param config - 仓库配置(用户表映射)
109
+ */
110
+ constructor(config) {
111
+ super(reldb, {
112
+ table: AUDIT_TABLE,
113
+ idColumn: "id",
114
+ generateId: () => core.id.withPrefix("audit_"),
115
+ fields: [
116
+ { fieldName: "id", columnName: "id", def: { type: "TEXT", primaryKey: true }, select: true, create: true, update: false },
117
+ { fieldName: "userId", columnName: "user_id", def: { type: "TEXT" }, select: true, create: true, update: false },
118
+ { fieldName: "action", columnName: "action", def: { type: "TEXT", notNull: true }, select: true, create: true, update: false },
119
+ { fieldName: "resource", columnName: "resource", def: { type: "TEXT", notNull: true }, select: true, create: true, update: false },
120
+ { fieldName: "resourceId", columnName: "resource_id", def: { type: "TEXT" }, select: true, create: true, update: false },
121
+ { fieldName: "details", columnName: "details", def: { type: "JSON" }, select: true, create: true, update: false },
122
+ { fieldName: "ipAddress", columnName: "ip_address", def: { type: "TEXT" }, select: true, create: true, update: false },
123
+ { fieldName: "userAgent", columnName: "user_agent", def: { type: "TEXT" }, select: true, create: true, update: false },
124
+ { fieldName: "createdAt", columnName: "created_at", def: { type: "TIMESTAMP" }, select: true, create: true, update: false }
125
+ ]
126
+ });
127
+ this.repoConfig = config;
128
+ this.isSqlite = this.db.config?.type === "sqlite";
129
+ }
130
+ /**
131
+ * 将 Date 转为适合当前数据库类型的 SQL 参数
132
+ *
133
+ * SQLite 存储 TIMESTAMP 为毫秒时间戳,其他数据库使用 ISO 字符串。
134
+ *
135
+ * @param date - 要转换的日期
136
+ * @returns SQLite 返回毫秒时间戳(number),其他返回 ISO 字符串
137
+ */
138
+ toDateParam(date) {
139
+ return this.isSqlite ? date.getTime() : date.toISOString();
140
+ }
141
+ /**
142
+ * 记录一条审计日志
143
+ *
144
+ * @param input - 日志内容
145
+ * @param input.userId - 操作用户 ID(系统操作可省略)
146
+ * @param input.action - 操作类型(如 login / create)
147
+ * @param input.resource - 资源类型(如 auth / users)
148
+ * @param input.resourceId - 资源 ID(可选)
149
+ * @param input.details - 操作详情对象(可选)
150
+ * @param input.ipAddress - 客户端 IP(可选)
151
+ * @param input.userAgent - 客户端 User-Agent(可选)
152
+ * @returns 成功时返回创建的 AuditLog;失败时返回 LOG_FAILED
153
+ */
154
+ async log(input) {
155
+ logger.debug("Recording audit log", { action: input.action, resource: input.resource });
156
+ const id = core.id.withPrefix("audit_");
157
+ const now = /* @__PURE__ */ new Date();
158
+ const data = {
159
+ id,
160
+ userId: input.userId ?? null,
161
+ action: input.action,
162
+ resource: input.resource,
163
+ resourceId: input.resourceId ?? null,
164
+ details: input.details ?? null,
165
+ ipAddress: input.ipAddress ?? null,
166
+ userAgent: input.userAgent ?? null,
167
+ createdAt: now
168
+ };
169
+ try {
170
+ const createResult = await this.create(data);
171
+ if (!createResult.success) {
172
+ logger.error("Failed to record audit log", { action: input.action, error: createResult.error.message });
173
+ return err(
174
+ HaiAuditError.LOG_FAILED,
175
+ auditM("audit_logFailed", { params: { error: createResult.error.message } }),
176
+ createResult.error
177
+ );
178
+ }
179
+ const auditLog = {
180
+ id,
181
+ userId: input.userId ?? null,
182
+ action: input.action,
183
+ resource: input.resource,
184
+ resourceId: input.resourceId ?? null,
185
+ details: input.details ? JSON.stringify(input.details) : null,
186
+ ipAddress: input.ipAddress ?? null,
187
+ userAgent: input.userAgent ?? null,
188
+ createdAt: now
189
+ };
190
+ logger.info("Audit log recorded", { id, action: input.action, resource: input.resource });
191
+ return ok(auditLog);
192
+ } catch (error) {
193
+ logger.error("Failed to record audit log", { action: input.action, error });
194
+ return err(
195
+ HaiAuditError.LOG_FAILED,
196
+ auditM("audit_logFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
197
+ error
198
+ );
199
+ }
200
+ }
201
+ /**
202
+ * 分页查询日志列表(含用户名 LEFT JOIN)
203
+ *
204
+ * @param options - 过滤条件与分页参数
205
+ * @returns 成功时返回 { items, total };失败时返回 QUERY_FAILED
206
+ */
207
+ async listWithUser(options = {}) {
208
+ logger.debug("Querying audit logs", { userId: options.userId, action: options.action, resource: options.resource });
209
+ const conditions = [];
210
+ const params = [];
211
+ const { userTable, userIdColumn, userNameColumn } = this.repoConfig;
212
+ if (options.userId) {
213
+ conditions.push("a.user_id = ?");
214
+ params.push(options.userId);
215
+ }
216
+ if (options.action) {
217
+ conditions.push("a.action = ?");
218
+ params.push(options.action);
219
+ }
220
+ if (options.resource) {
221
+ conditions.push("a.resource = ?");
222
+ params.push(options.resource);
223
+ }
224
+ if (options.startDate) {
225
+ conditions.push("a.created_at >= ?");
226
+ params.push(this.toDateParam(options.startDate));
227
+ }
228
+ if (options.endDate) {
229
+ conditions.push("a.created_at <= ?");
230
+ params.push(this.toDateParam(options.endDate));
231
+ }
232
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
233
+ try {
234
+ const result = await this.sql().queryPage({
235
+ sql: `SELECT a.id, a.user_id AS userId, a.action, a.resource, a.resource_id AS resourceId,
236
+ a.details, a.ip_address AS ipAddress, a.user_agent AS userAgent,
237
+ a.created_at AS createdAt, u.${userNameColumn} AS username
238
+ FROM ${AUDIT_TABLE} a
239
+ LEFT JOIN ${userTable} u ON a.user_id = u.${userIdColumn}
240
+ ${whereClause}
241
+ ORDER BY a.created_at DESC`,
242
+ params,
243
+ pagination: { page: options.page, pageSize: options.pageSize },
244
+ overrides: { defaultPageSize: 20 }
245
+ });
246
+ if (!result.success) {
247
+ logger.error("Failed to query audit logs", { error: result.error.message });
248
+ return err(
249
+ HaiAuditError.QUERY_FAILED,
250
+ auditM("audit_queryFailed", { params: { error: result.error.message } }),
251
+ result.error
252
+ );
253
+ }
254
+ return ok({ items: result.data.items, total: result.data.total });
255
+ } catch (error) {
256
+ logger.error("Failed to query audit logs", { error });
257
+ return err(
258
+ HaiAuditError.QUERY_FAILED,
259
+ auditM("audit_queryFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
260
+ error
261
+ );
262
+ }
263
+ }
264
+ /**
265
+ * 获取指定用户的最近活动
266
+ *
267
+ * @param userId - 用户 ID
268
+ * @param limit - 最大返回条数,默认 10
269
+ * @returns 成功时返回 AuditLog 数组(按时间倒序);失败时返回 QUERY_FAILED
270
+ */
271
+ async getUserRecent(userId, limit = 10) {
272
+ logger.debug("Getting user recent activity", { userId, limit });
273
+ try {
274
+ const result = await this.findAll({
275
+ where: "user_id = ?",
276
+ params: [userId],
277
+ orderBy: "created_at DESC",
278
+ limit
279
+ });
280
+ if (!result.success) {
281
+ return err(
282
+ HaiAuditError.QUERY_FAILED,
283
+ auditM("audit_queryFailed", { params: { error: result.error.message } }),
284
+ result.error
285
+ );
286
+ }
287
+ return ok(result.data);
288
+ } catch (error) {
289
+ return err(
290
+ HaiAuditError.QUERY_FAILED,
291
+ auditM("audit_queryFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
292
+ error
293
+ );
294
+ }
295
+ }
296
+ /**
297
+ * 清理指定天数之前的旧日志
298
+ *
299
+ * @param olderThanDays - 保留天数,默认 90;清理此天数之前的日志
300
+ * @returns 成功时返回删除的记录数;失败时返回 CLEANUP_FAILED
301
+ */
302
+ async cleanupOld(olderThanDays = 90) {
303
+ logger.debug("Cleaning up old audit logs", { olderThanDays });
304
+ const cutoff = /* @__PURE__ */ new Date();
305
+ cutoff.setDate(cutoff.getDate() - olderThanDays);
306
+ try {
307
+ const result = await this.sql().execute(
308
+ `DELETE FROM ${AUDIT_TABLE} WHERE created_at < ?`,
309
+ [this.toDateParam(cutoff)]
310
+ );
311
+ if (!result.success) {
312
+ logger.error("Failed to cleanup audit logs", { error: result.error.message });
313
+ return err(
314
+ HaiAuditError.CLEANUP_FAILED,
315
+ auditM("audit_cleanupFailed", { params: { error: result.error.message } }),
316
+ result.error
317
+ );
318
+ }
319
+ const deleted = result.data.changes;
320
+ logger.info("Audit logs cleaned up", { olderThanDays, deleted });
321
+ return ok(deleted);
322
+ } catch (error) {
323
+ logger.error("Failed to cleanup audit logs", { error });
324
+ return err(
325
+ HaiAuditError.CLEANUP_FAILED,
326
+ auditM("audit_cleanupFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
327
+ error
328
+ );
329
+ }
330
+ }
331
+ /**
332
+ * 获取指定天数内的操作统计(按 action 分组计数)
333
+ *
334
+ * @param days - 统计天数,默认 7
335
+ * @returns 成功时返回 AuditStatItem 数组(按 count 倒序);失败时返回 STATS_FAILED
336
+ */
337
+ async getStats(days = 7) {
338
+ logger.debug("Getting audit statistics", { days });
339
+ const cutoff = /* @__PURE__ */ new Date();
340
+ cutoff.setDate(cutoff.getDate() - days);
341
+ try {
342
+ const result = await this.sql().query(
343
+ `SELECT action, COUNT(*) as count
344
+ FROM ${AUDIT_TABLE}
345
+ WHERE created_at >= ?
346
+ GROUP BY action
347
+ ORDER BY count DESC`,
348
+ [this.toDateParam(cutoff)]
349
+ );
350
+ if (!result.success) {
351
+ logger.error("Failed to query audit statistics", { error: result.error.message });
352
+ return err(
353
+ HaiAuditError.STATS_FAILED,
354
+ auditM("audit_statsFailed", { params: { error: result.error.message } }),
355
+ result.error
356
+ );
357
+ }
358
+ return ok(result.data.map((item) => ({ action: item.action, count: Number(item.count) })));
359
+ } catch (error) {
360
+ logger.error("Failed to query audit statistics", { error });
361
+ return err(
362
+ HaiAuditError.STATS_FAILED,
363
+ auditM("audit_statsFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
364
+ error
365
+ );
366
+ }
367
+ }
368
+ };
369
+
370
+ // src/audit-main.ts
371
+ var logger2 = core.logger.child({ module: "audit", scope: "main" });
372
+ var MAX_TEXT_LENGTH = 256;
373
+ function isNonEmptyString(value) {
374
+ return typeof value === "string" && value.trim().length > 0;
375
+ }
376
+ function isValidTextLength(value, maxLength = MAX_TEXT_LENGTH) {
377
+ return value.length <= maxLength;
378
+ }
379
+ var currentRepo = null;
380
+ var currentHelper = null;
381
+ var initInProgress = false;
382
+ var notInitialized = core.module.createNotInitializedKit(
383
+ HaiAuditError.NOT_INITIALIZED,
384
+ () => auditM("audit_notInitialized")
385
+ );
386
+ var notInitializedHelper = notInitialized.proxy();
387
+ var audit = {
388
+ /**
389
+ * 初始化审计模块
390
+ *
391
+ * 会先关闭已有实例(如已初始化),再用新配置重新初始化。
392
+ * 内部创建 AuditLogRepository 实例(BaseReldbCrudRepository 自动建表)。
393
+ * 依赖 @h-ai/reldb 已初始化。
394
+ *
395
+ * @param config - 初始化配置(可选,所有字段均有默认值)
396
+ * @returns 成功时返回 ok(undefined);失败时返回 CONFIG_ERROR
397
+ *
398
+ * @example
399
+ * ```ts
400
+ * const result = await audit.init()
401
+ * if (!result.success) {
402
+ * logger.error('Audit init failed', { error: result.error.message })
403
+ * }
404
+ * ```
405
+ */
406
+ async init(config) {
407
+ if (initInProgress) {
408
+ logger2.warn("Audit init already in progress, skipping concurrent call");
409
+ return err(
410
+ HaiAuditError.INIT_IN_PROGRESS,
411
+ auditM("audit_initInProgress")
412
+ );
413
+ }
414
+ initInProgress = true;
415
+ try {
416
+ if (currentRepo) {
417
+ logger2.warn("Audit module is already initialized, reinitializing");
418
+ await audit.close();
419
+ }
420
+ logger2.info("Initializing audit module");
421
+ const parseResult = AuditInitConfigSchema.safeParse(config ?? {});
422
+ if (!parseResult.success) {
423
+ logger2.error("Audit config validation failed", { error: parseResult.error.message });
424
+ return err(
425
+ HaiAuditError.CONFIG_ERROR,
426
+ auditM("audit_configError", { params: { error: parseResult.error.message } }),
427
+ parseResult.error
428
+ );
429
+ }
430
+ const parsed = parseResult.data;
431
+ const identifierResult = validateIdentifiers([parsed.userTable, parsed.userIdColumn, parsed.userNameColumn]);
432
+ if (!identifierResult.success) {
433
+ logger2.error("Audit config contains invalid identifiers", { error: identifierResult.error.message });
434
+ return err(
435
+ HaiAuditError.CONFIG_ERROR,
436
+ auditM("audit_configError", { params: { error: identifierResult.error.message } }),
437
+ identifierResult.error
438
+ );
439
+ }
440
+ currentRepo = new AuditLogRepository({
441
+ userTable: parsed.userTable,
442
+ userIdColumn: parsed.userIdColumn,
443
+ userNameColumn: parsed.userNameColumn
444
+ });
445
+ currentHelper = createHelper((input) => currentRepo.log(input));
446
+ logger2.info("Audit module initialized");
447
+ return ok(void 0);
448
+ } catch (error) {
449
+ logger2.error("Audit module initialization failed", { error });
450
+ return err(
451
+ HaiAuditError.CONFIG_ERROR,
452
+ auditM("audit_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
453
+ error
454
+ );
455
+ } finally {
456
+ initInProgress = false;
457
+ }
458
+ },
459
+ /** 当前是否已初始化 */
460
+ get isInitialized() {
461
+ return currentRepo !== null;
462
+ },
463
+ /**
464
+ * 记录一条审计日志
465
+ *
466
+ * @param input - 日志内容(action 和 resource 为必填)
467
+ * @returns 成功时返回创建的 AuditLog;未初始化时返回 NOT_INITIALIZED
468
+ */
469
+ log(input) {
470
+ if (!currentRepo) {
471
+ return Promise.resolve(notInitialized.result());
472
+ }
473
+ if (!isNonEmptyString(input.action) || !isNonEmptyString(input.resource)) {
474
+ return Promise.resolve(err(
475
+ HaiAuditError.LOG_FAILED,
476
+ auditM("audit_invalidInput", { params: { field: "action/resource", reason: "must be non-empty string" } })
477
+ ));
478
+ }
479
+ if (!isValidTextLength(input.action) || !isValidTextLength(input.resource)) {
480
+ return Promise.resolve(err(
481
+ HaiAuditError.LOG_FAILED,
482
+ auditM("audit_invalidInput", { params: { field: "action/resource", reason: "exceeds max length" } })
483
+ ));
484
+ }
485
+ return currentRepo.log(input);
486
+ },
487
+ /**
488
+ * 分页查询审计日志列表(含用户名 LEFT JOIN)
489
+ *
490
+ * @param options - 过滤条件与分页参数
491
+ * @returns 成功时返回 { items, total };未初始化时返回 NOT_INITIALIZED
492
+ */
493
+ list(options) {
494
+ if (!currentRepo) {
495
+ return Promise.resolve(notInitialized.result());
496
+ }
497
+ if (options?.startDate && options?.endDate && options.startDate > options.endDate) {
498
+ return Promise.resolve(err(
499
+ HaiAuditError.QUERY_FAILED,
500
+ auditM("audit_invalidInput", { params: { field: "dateRange", reason: "startDate must be before endDate" } })
501
+ ));
502
+ }
503
+ return currentRepo.listWithUser(options);
504
+ },
505
+ /**
506
+ * 获取指定用户的最近活动记录
507
+ *
508
+ * @param userId - 用户 ID
509
+ * @param limit - 最大返回条数,默认 10
510
+ * @returns 成功时返回 AuditLog 数组;未初始化时返回 NOT_INITIALIZED
511
+ */
512
+ getUserRecent(userId, limit) {
513
+ if (!currentRepo) {
514
+ return Promise.resolve(notInitialized.result());
515
+ }
516
+ if (!isNonEmptyString(userId)) {
517
+ return Promise.resolve(err(
518
+ HaiAuditError.QUERY_FAILED,
519
+ auditM("audit_invalidInput", { params: { field: "userId", reason: "must be non-empty string" } })
520
+ ));
521
+ }
522
+ if (typeof limit === "number" && (!Number.isInteger(limit) || limit <= 0)) {
523
+ return Promise.resolve(err(
524
+ HaiAuditError.QUERY_FAILED,
525
+ auditM("audit_invalidInput", { params: { field: "limit", reason: "must be a positive integer" } })
526
+ ));
527
+ }
528
+ return currentRepo.getUserRecent(userId, limit);
529
+ },
530
+ /**
531
+ * 清理指定天数之前的旧日志
532
+ *
533
+ * @param olderThanDays - 保留天数,默认 90
534
+ * @returns 成功时返回删除的记录数;未初始化时返回 NOT_INITIALIZED
535
+ */
536
+ cleanup(olderThanDays) {
537
+ if (!currentRepo) {
538
+ return Promise.resolve(notInitialized.result());
539
+ }
540
+ if (typeof olderThanDays === "number" && (!Number.isInteger(olderThanDays) || olderThanDays < 0)) {
541
+ return Promise.resolve(err(
542
+ HaiAuditError.CLEANUP_FAILED,
543
+ auditM("audit_invalidInput", { params: { field: "olderThanDays", reason: "must be a non-negative integer" } })
544
+ ));
545
+ }
546
+ return currentRepo.cleanupOld(olderThanDays);
547
+ },
548
+ /**
549
+ * 获取指定天数内的操作统计(按 action 分组计数)
550
+ *
551
+ * @param days - 统计天数,默认 7
552
+ * @returns 成功时返回 AuditStatItem 数组;未初始化时返回 NOT_INITIALIZED
553
+ */
554
+ getStats(days) {
555
+ if (!currentRepo) {
556
+ return Promise.resolve(notInitialized.result());
557
+ }
558
+ if (typeof days === "number" && (!Number.isInteger(days) || days < 0)) {
559
+ return Promise.resolve(err(
560
+ HaiAuditError.STATS_FAILED,
561
+ auditM("audit_invalidInput", { params: { field: "days", reason: "must be a non-negative integer" } })
562
+ ));
563
+ }
564
+ return currentRepo.getStats(days);
565
+ },
566
+ /**
567
+ * 便捷记录器
568
+ *
569
+ * 未初始化时调用任意方法均返回 NOT_INITIALIZED 错误。
570
+ */
571
+ get helper() {
572
+ if (!currentHelper) {
573
+ return notInitializedHelper;
574
+ }
575
+ return currentHelper;
576
+ },
577
+ /**
578
+ * 关闭审计模块,释放内部状态
579
+ */
580
+ async close() {
581
+ if (!currentRepo) {
582
+ logger2.info("Audit module already closed, skipping");
583
+ return;
584
+ }
585
+ logger2.info("Closing audit module");
586
+ currentRepo = null;
587
+ currentHelper = null;
588
+ logger2.info("Audit module closed");
589
+ }
590
+ };
591
+
592
+ export { AuditInitConfigSchema, HaiAuditError, audit, createHelper };
593
+ //# sourceMappingURL=index.js.map
594
+ //# sourceMappingURL=index.js.map