@h-ai/audit 0.1.0-alpha.67 → 0.1.0-alpha.69

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/README.en.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  A unified audit logging module. The `audit` object records, queries, cleans up, and aggregates audit logs, with persistence provided by `@h-ai/reldb`.
10
10
 
11
+ SQLite, PostgreSQL and MySQL share the same read contract: `createdAt` is a `Date`, `details` is a JSON string or `null`, and absent nullable fields return `null`. Date filters use the same driver conversion as writes. Counts are safe integers; out-of-range counts return `STATS_FAILED`.
12
+
11
13
  ## Core features
12
14
 
13
15
  - Record user actions and security events; filter by user, date and action.
@@ -97,6 +99,8 @@ Audit logs are stored in the fixed `hai_audit_logs` table and require an initial
97
99
  - `audit.helper.login/logout/register/...`: convenient log helpers.
98
100
  - `audit.close()`: close the module.
99
101
 
102
+ Record common events through `audit.helper`, with the public `AuditHelper` type. The helper's internal assembly factory is outside the package API.
103
+
100
104
  #### Input constraints
101
105
 
102
106
  - `audit.log(input)`: `action` / `resource` must be nonempty strings of at most 256 characters.
@@ -122,12 +126,16 @@ All operations return `HaiResult<T>`. Common error codes:
122
126
 
123
127
  > Note: `audit.close()` also returns `HaiResult<void>`. `audit.helper.passwordResetRequest()` records the original email address without additional masking by default.
124
128
 
129
+ `audit.init()` awaits automatic table creation and repository readiness before success. DDL failures leave `isInitialized=false`; correct the database and retry. Custom joins, statistics and cleanup share the same barrier. Closing during initialization invalidates late publication. Check the init HaiResult before recording or querying. Audit does not own the shared RelDB connection; close audit before the database.
130
+
125
131
  ## Development and testing
126
132
 
127
133
  ```bash
128
134
  pnpm --filter @h-ai/audit test
129
135
  ```
130
136
 
137
+ Source examples initialize RelDB first, check audit results, and check cleanup in reverse dependency order; see the [JSDoc conventions (Chinese)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证).
138
+
131
139
  ## License
132
140
 
133
141
  Apache-2.0
package/README.ja.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  統一された監査ログモジュールです。`audit` オブジェクトで記録、検索、削除、集計を行い、`@h-ai/reldb` で永続化します。
10
10
 
11
+ SQLite、PostgreSQL、MySQL で読み取り契約は共通です。`createdAt` は `Date`、`details` は JSON 文字列または `null`、値のない nullable フィールドは `null` を返します。日時フィルターは書き込みと同じドライバー変換を使用します。件数は安全な整数とし、範囲外では `STATS_FAILED` を返します。
12
+
11
13
  ## 主な機能
12
14
 
13
15
  - ユーザー操作やセキュリティイベントを記録し、ユーザー・日時・操作で検索。
@@ -97,6 +99,8 @@ await audit.init({
97
99
  - `audit.helper.login/logout/register/...`:記録用ヘルパー。
98
100
  - `audit.close()`:モジュールを終了。
99
101
 
102
+ 一般的なイベントは `audit.helper` で記録し、対応する `AuditHelper` 型は公開を維持します。内部の組み立て用ファクトリーはパッケージ API に含めません。
103
+
100
104
  #### 入力制約
101
105
 
102
106
  - `audit.log(input)`:`action` / `resource` は空でない文字列で、長さは 256 文字以内。
@@ -122,12 +126,16 @@ await audit.init({
122
126
 
123
127
  > 注:`audit.close()` も `HaiResult<void>` を返します。`audit.helper.passwordResetRequest()` は既定でメールアドレスを追加マスクせず、そのまま記録します。
124
128
 
129
+ `audit.init()` は自動建表とリポジトリーの準備完了を待って成功します。DDL 失敗時は `isInitialized=false` のままで、DB を修正後に再試行できます。独自 JOIN・統計・削除も同じ準備完了を待ちます。初期化中の close は遅れた公開を無効化します。init の HaiResult を確認後に記録・検索し、共有 DB の接続は所有しないため audit の終了後に DB を終了します。
130
+
125
131
  ## 開発とテスト
126
132
 
127
133
  ```bash
128
134
  pnpm --filter @h-ai/audit test
129
135
  ```
130
136
 
137
+ ソース例は RelDB を先に初期化し、監査結果と依存関係の逆順による終了結果を確認します。[JSDoc 規約(中国語)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证) を参照してください。
138
+
131
139
  ## ライセンス
132
140
 
133
141
  Apache-2.0
package/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  统一审计日志模块,通过 `audit` 对象提供审计日志的记录、查询、清理与统计功能,基于 `@h-ai/reldb` 实现持久化。
10
10
 
11
+ SQLite、PostgreSQL、MySQL 的读取结果保持同一契约:`createdAt` 为 `Date`,`details` 为 JSON 字符串或 `null`,缺失的可空字段返回 `null`。日期筛选与写入使用相同的驱动日期转换;统计数量为安全整数,超出范围返回 `STATS_FAILED`。
12
+
11
13
  ## 核心功能清单
12
14
 
13
15
  - 记录用户操作与安全事件,支持按用户、时间和操作筛选。
@@ -97,6 +99,8 @@ await audit.init({
97
99
  - `audit.helper.login/logout/register/...` - 便捷记录器
98
100
  - `audit.close()` - 关闭模块
99
101
 
102
+ 常见事件通过 `audit.helper` 记录,对应的 `AuditHelper` 类型保持公开;便捷记录器的内部装配工厂不属于包 API。
103
+
100
104
  #### 输入约束
101
105
 
102
106
  - `audit.log(input)`:`action` / `resource` 必须为非空字符串,且长度不超过 256。
@@ -122,12 +126,16 @@ await audit.init({
122
126
 
123
127
  > 说明:`audit.close()` 也返回 `HaiResult<void>`;`audit.helper.passwordResetRequest()` 会记录原始邮箱,默认不会额外掩码。
124
128
 
129
+ `audit.init()` 会等待自动建表和仓库就绪才返回成功;DDL 失败保持 `isInitialized=false`,修正数据库后可重试。自定义 JOIN、统计和清理也使用同一就绪屏障;初始化期间关闭会使迟到结果失效。先检查 init 的 HaiResult,再立即记录或查询;audit 不拥有共享 RelDB 的连接,关闭 audit 后再关闭数据库。
130
+
125
131
  ## 开发测试
126
132
 
127
133
  ```bash
128
134
  pnpm --filter @h-ai/audit test
129
135
  ```
130
136
 
137
+ 审计源码示例明确先初始化 RelDB、检查审计写入结果,再按依赖反序检查关闭结果;参见[模块 JSDoc 约定](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)。
138
+
131
139
  ## 许可证
132
140
 
133
141
  Apache-2.0
package/dist/index.d.ts CHANGED
@@ -13,20 +13,11 @@ import { HaiResult, PaginationOptionsInput, PaginatedResult } from '@h-ai/core';
13
13
  * 审计模块初始化配置 Schema(Zod)
14
14
  *
15
15
  * 所有字段均有默认值,可直接调用 `audit.init()` 无需传参。
16
- *
17
16
  * @example
18
17
  * ```ts
19
- * import { audit } from '@h-ai/audit'
20
- * import { reldb } from '@h-ai/reldb'
21
- *
22
- * await audit.init()
23
- *
24
- * // 自定义用户表映射
25
- * await audit.init({
26
- * userTable: 'sys_users',
27
- * userIdColumn: 'user_id',
28
- * userNameColumn: 'name',
29
- * })
18
+ * import { AuditInitConfigSchema } from '@h-ai/audit'
19
+ * const config = AuditInitConfigSchema.parse({ userTable: 'app_users' })
20
+ * // 这里只校验字段映射;实际初始化前必须已有可用的 RelDB。
30
21
  * ```
31
22
  */
32
23
  declare const AuditInitConfigSchema: z.ZodObject<{
@@ -34,11 +25,35 @@ declare const AuditInitConfigSchema: z.ZodObject<{
34
25
  userIdColumn: z.ZodDefault<z.ZodString>;
35
26
  userNameColumn: z.ZodDefault<z.ZodString>;
36
27
  }, z.core.$strip>;
37
- /** 审计模块初始化配置(解析后) */
28
+ /**
29
+ * 审计模块初始化配置(解析后)
30
+ * @example
31
+ * ```ts
32
+ * import { AuditInitConfigSchema, type AuditInitConfig } from '@h-ai/audit'
33
+ * const config: AuditInitConfig = AuditInitConfigSchema.parse({ userTable: 'app_users' })
34
+ * ```
35
+ */
38
36
  type AuditInitConfig = z.output<typeof AuditInitConfigSchema>;
39
- /** 审计模块初始化配置(传入参数) */
37
+ /**
38
+ * 审计模块初始化配置(传入参数)
39
+ * @example
40
+ * ```ts
41
+ * import type { AuditInitConfigInput } from '@h-ai/audit'
42
+ * const record: AuditInitConfigInput = { userTable: 'app_users', userIdColumn: 'id', userNameColumn: 'username' }
43
+ * ```
44
+ */
40
45
  type AuditInitConfigInput = z.input<typeof AuditInitConfigSchema>;
41
46
 
47
+ /**
48
+ * 审计初始化、写入、查询和清理失败的稳定错误定义。
49
+ * @example
50
+ * ```ts
51
+ * import { HaiAuditError } from '@h-ai/audit'
52
+ * import { err } from '@h-ai/core'
53
+ * const failed = err(HaiAuditError.NOT_INITIALIZED, 'Audit is not initialized')
54
+ * if (!failed.success) failed.error.code
55
+ * ```
56
+ */
42
57
  declare const HaiAuditError: {
43
58
  readonly LOG_FAILED: _h_ai_core.HaiErrorDef;
44
59
  readonly QUERY_FAILED: _h_ai_core.HaiErrorDef;
@@ -52,6 +67,11 @@ declare const HaiAuditError: {
52
67
  * 审计日志记录
53
68
  *
54
69
  * 表示一条已持久化的审计日志条目。
70
+ * @example
71
+ * ```ts
72
+ * import type { AuditLog } from '@h-ai/audit'
73
+ * const record: AuditLog = { id: 'audit_example', userId: 'user_1', action: 'create', resource: 'orders', resourceId: 'order_1', details: null, ipAddress: null, userAgent: null, createdAt: new Date() }
74
+ * ```
55
75
  */
56
76
  interface AuditLog {
57
77
  /** 审计日志 ID,格式为 `audit_` 前缀 + 随机串 */
@@ -77,6 +97,11 @@ interface AuditLog {
77
97
  * 审计日志记录(含用户名)
78
98
  *
79
99
  * 通过 LEFT JOIN 用户表获取 username 字段,用于列表展示。
100
+ * @example
101
+ * ```ts
102
+ * import type { AuditLogWithUser } from '@h-ai/audit'
103
+ * const record: AuditLogWithUser = { id: 'audit_example', userId: 'user_1', action: 'create', resource: 'orders', resourceId: 'order_1', details: null, ipAddress: null, userAgent: null, createdAt: new Date() , username: 'alice' }
104
+ * ```
80
105
  */
81
106
  interface AuditLogWithUser extends AuditLog {
82
107
  /** 操作用户的用户名;用户不存在或系统操作时为 null */
@@ -87,6 +112,11 @@ interface AuditLogWithUser extends AuditLog {
87
112
  *
88
113
  * 调用 {@link AuditFunctions.log} 时传入的参数。
89
114
  * 仅 `action` 和 `resource` 为必填。
115
+ * @example
116
+ * ```ts
117
+ * import type { CreateAuditLogInput } from '@h-ai/audit'
118
+ * const record: CreateAuditLogInput = { userId: 'user_1', action: 'create', resource: 'orders', resourceId: 'order_1', details: { status: 'pending' } }
119
+ * ```
90
120
  */
91
121
  interface CreateAuditLogInput {
92
122
  /** 操作用户 ID;系统操作可省略 */
@@ -108,6 +138,11 @@ interface CreateAuditLogInput {
108
138
  * 审计日志列表查询选项
109
139
  *
110
140
  * 所有字段均为可选,不传则不过滤。
141
+ * @example
142
+ * ```ts
143
+ * import type { ListAuditLogsOptions } from '@h-ai/audit'
144
+ * const record: ListAuditLogsOptions = { action: 'create', resource: 'orders', page: 1, pageSize: 20 }
145
+ * ```
111
146
  */
112
147
  interface ListAuditLogsOptions extends PaginationOptionsInput {
113
148
  /** 按用户 ID 过滤 */
@@ -125,6 +160,11 @@ interface ListAuditLogsOptions extends PaginationOptionsInput {
125
160
  * 审计统计结果项
126
161
  *
127
162
  * 按操作类型(action)分组的统计条目。
163
+ * @example
164
+ * ```ts
165
+ * import type { AuditStatItem } from '@h-ai/audit'
166
+ * const record: AuditStatItem = { action: 'create', count: 12 }
167
+ * ```
128
168
  */
129
169
  interface AuditStatItem {
130
170
  /** 操作类型 */
@@ -136,6 +176,11 @@ interface AuditStatItem {
136
176
  * CRUD 审计操作输入
137
177
  *
138
178
  * 调用 {@link AuditHelper.crud} 时传入的参数。
179
+ * @example
180
+ * ```ts
181
+ * import type { CrudAuditInput } from '@h-ai/audit'
182
+ * const record: CrudAuditInput = { userId: 'user_1', action: 'update', resource: 'orders', resourceId: 'order_1', details: { status: 'paid' } }
183
+ * ```
139
184
  */
140
185
  interface CrudAuditInput {
141
186
  /** 操作用户 ID(系统操作传 null) */
@@ -158,11 +203,14 @@ interface CrudAuditInput {
158
203
  *
159
204
  * 封装常见审计场景(认证、CRUD),简化调用方代码。
160
205
  * 通过 {@link AuditFunctions.helper} 访问。
161
- *
162
206
  * @example
163
207
  * ```ts
164
- * await audit.helper.login('user_1', '127.0.0.1', 'Mozilla/5.0')
165
- * await audit.helper.crud({ userId: 'user_1', action: 'create', resource: 'users', resourceId: 'user_2' })
208
+ * import type { AuditHelper } from '@h-ai/audit'
209
+ * // helper 来自已初始化的 audit;数据库生命周期归调用方。
210
+ * async function recordLogin(helper: AuditHelper) {
211
+ * const result = await helper.login('user_1', '127.0.0.1')
212
+ * return result.success ? 'recorded' : result.error.message
213
+ * }
166
214
  * ```
167
215
  */
168
216
  interface AuditHelper {
@@ -222,18 +270,27 @@ interface AuditHelper {
222
270
  * 审计模块函数接口
223
271
  *
224
272
  * 统一的审计日志访问入口。
225
- *
226
273
  * @example
227
274
  * ```ts
228
275
  * import { audit } from '@h-ai/audit'
229
- * import { reldb } from '@h-ai/reldb'
230
- *
231
- * await reldb.init({ type: 'sqlite', database: './data.db' })
232
- * await audit.init()
233
- *
234
- * await audit.log({ userId: 'user_1', action: 'login', resource: 'auth' })
235
- * const logs = await audit.list({ pageSize: 10 })
236
- * await audit.close()
276
+ * import { core } from '@h-ai/core'
277
+ * import { reldb, type ReldbConfigInput } from '@h-ai/reldb'
278
+ * async function recordCreation(config: ReldbConfigInput) {
279
+ * const database = await reldb.init(config)
280
+ * if (!database.success) return database
281
+ * try {
282
+ * const initialized = await audit.init()
283
+ * if (!initialized.success) return initialized
284
+ * const logged = await audit.log({ action: 'create', resource: 'orders', resourceId: 'order_1' })
285
+ * if (logged.success) core.logger.info('Audit recorded', { id: logged.data.id })
286
+ * return logged
287
+ * } finally {
288
+ * const auditClosed = await audit.close()
289
+ * if (!auditClosed.success) return auditClosed // 保留依赖连接,修复后重试关闭。
290
+ * const databaseClosed = await reldb.close()
291
+ * if (!databaseClosed.success) return databaseClosed
292
+ * }
293
+ * }
237
294
  * ```
238
295
  */
239
296
  interface AuditFunctions {
@@ -294,24 +351,6 @@ interface AuditFunctions {
294
351
  readonly helper: AuditHelper;
295
352
  }
296
353
 
297
- /**
298
- * @h-ai/audit — 审计便捷记录器
299
- *
300
- * 封装常见审计操作(登录、登出、注册、密码重置、CRUD),简化调用方代码。
301
- * @module audit-helper
302
- */
303
-
304
- /**
305
- * 创建便捷记录器
306
- *
307
- * 封装常见审计操作(登录、登出、注册、密码重置、CRUD),
308
- * 简化调用方代码。每个方法内部调用 logFn 并将 AuditLog 结果映射为 void。
309
- *
310
- * @param logFn - 底层日志记录函数(通常为 currentRepo.log)
311
- * @returns 便捷记录器接口
312
- */
313
- declare function createHelper(logFn: (input: CreateAuditLogInput) => Promise<HaiResult<AuditLog>>): AuditHelper;
314
-
315
354
  /**
316
355
  * @h-ai/audit — 审计日志服务主入口
317
356
  *
@@ -332,23 +371,29 @@ declare function createHelper(logFn: (input: CreateAuditLogInput) => Promise<Hai
332
371
  * - `audit.getStats()` — 获取统计数据
333
372
  * - `audit.helper` — 便捷记录器
334
373
  * - `audit.isInitialized` — 初始化状态
335
- *
336
374
  * @example
337
375
  * ```ts
338
376
  * import { audit } from '@h-ai/audit'
339
- * import { reldb } from '@h-ai/reldb'
340
- *
341
- * await reldb.init({ type: 'sqlite', database: ':memory:' })
342
- * const result = await audit.init()
343
- * if (!result.success) {
344
- * // 处理初始化错误
377
+ * import { core } from '@h-ai/core'
378
+ * import { reldb, type ReldbConfigInput } from '@h-ai/reldb'
379
+ * async function recordCreation(config: ReldbConfigInput) {
380
+ * const database = await reldb.init(config)
381
+ * if (!database.success) return database
382
+ * try {
383
+ * const initialized = await audit.init()
384
+ * if (!initialized.success) return initialized
385
+ * const logged = await audit.log({ action: 'create', resource: 'orders', resourceId: 'order_1' })
386
+ * if (logged.success) core.logger.info('Audit recorded', { id: logged.data.id })
387
+ * return logged
388
+ * } finally {
389
+ * const auditClosed = await audit.close()
390
+ * if (!auditClosed.success) return auditClosed // 保留依赖连接,修复后重试关闭。
391
+ * const databaseClosed = await reldb.close()
392
+ * if (!databaseClosed.success) return databaseClosed
393
+ * }
345
394
  * }
346
- *
347
- * await audit.helper.login('user_1', '127.0.0.1')
348
- * const logs = await audit.list({ pageSize: 10 })
349
- * await audit.close()
350
395
  * ```
351
396
  */
352
397
  declare const audit: AuditFunctions;
353
398
 
354
- export { type AuditFunctions, type AuditHelper, type AuditInitConfig, type AuditInitConfigInput, AuditInitConfigSchema, type AuditLog, type AuditLogWithUser, type AuditStatItem, type CreateAuditLogInput, type CrudAuditInput, HaiAuditError, type ListAuditLogsOptions, audit, createHelper };
399
+ export { type AuditFunctions, type AuditHelper, type AuditInitConfig, type AuditInitConfigInput, AuditInitConfigSchema, type AuditLog, type AuditLogWithUser, type AuditStatItem, type CreateAuditLogInput, type CrudAuditInput, HaiAuditError, type ListAuditLogsOptions, audit };
package/dist/index.js CHANGED
@@ -69,11 +69,27 @@ var en_US_default = {
69
69
  audit_queryFailed: "Failed to query audit logs: {error}",
70
70
  audit_cleanupFailed: "Failed to cleanup audit logs: {error}",
71
71
  audit_statsFailed: "Failed to query audit statistics: {error}",
72
+ audit_invalidStatCount: "Audit count is outside the valid range and cannot be displayed safely",
72
73
  audit_configError: "Audit config validation failed: {error}",
73
74
  audit_initInProgress: "Audit module initialization is already in progress",
74
75
  audit_invalidInput: "Invalid audit input for {field}: {reason}"
75
76
  };
76
77
 
78
+ // messages/ja-JP.json
79
+ var ja_JP_default = {
80
+ $schema: "https://inlang.com/schema/inlang-message-format",
81
+ audit_notInitialized: "\u76E3\u67FB\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Baudit.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
82
+ audit_initFailed: "\u76E3\u67FB\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
83
+ audit_logFailed: "\u76E3\u67FB\u30ED\u30B0\u306E\u8A18\u9332\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
84
+ audit_queryFailed: "\u76E3\u67FB\u30ED\u30B0\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
85
+ audit_cleanupFailed: "\u76E3\u67FB\u30ED\u30B0\u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
86
+ audit_statsFailed: "\u76E3\u67FB\u7D71\u8A08\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
87
+ audit_invalidStatCount: "\u76E3\u67FB\u4EF6\u6570\u304C\u6709\u52B9\u7BC4\u56F2\u3092\u8D85\u3048\u3066\u3044\u308B\u305F\u3081\u3001\u5B89\u5168\u306B\u8868\u793A\u3067\u304D\u307E\u305B\u3093",
88
+ audit_configError: "\u76E3\u67FB\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
89
+ audit_initInProgress: "\u76E3\u67FB\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u521D\u671F\u5316\u306F\u65E2\u306B\u5B9F\u884C\u4E2D\u3067\u3059",
90
+ audit_invalidInput: "{field}\u306E\u76E3\u67FB\u5165\u529B\u304C\u7121\u52B9\u3067\u3059: {reason}"
91
+ };
92
+
77
93
  // messages/zh-CN.json
78
94
  var zh_CN_default = {
79
95
  $schema: "https://inlang.com/schema/inlang-message-format",
@@ -83,6 +99,7 @@ var zh_CN_default = {
83
99
  audit_queryFailed: "\u5BA1\u8BA1\u65E5\u5FD7\u67E5\u8BE2\u5931\u8D25\uFF1A{error}",
84
100
  audit_cleanupFailed: "\u5BA1\u8BA1\u65E5\u5FD7\u6E05\u7406\u5931\u8D25\uFF1A{error}",
85
101
  audit_statsFailed: "\u5BA1\u8BA1\u7EDF\u8BA1\u67E5\u8BE2\u5931\u8D25\uFF1A{error}",
102
+ audit_invalidStatCount: "\u5BA1\u8BA1\u7EDF\u8BA1\u6570\u91CF\u8D85\u51FA\u6709\u6548\u8303\u56F4\uFF0C\u65E0\u6CD5\u5B89\u5168\u663E\u793A",
86
103
  audit_configError: "\u5BA1\u8BA1\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
87
104
  audit_initInProgress: "\u5BA1\u8BA1\u6A21\u5757\u6B63\u5728\u521D\u59CB\u5316\u4E2D\uFF0C\u8BF7\u52FF\u5E76\u53D1\u8C03\u7528",
88
105
  audit_invalidInput: "\u5BA1\u8BA1\u8F93\u5165\u53C2\u6570\u65E0\u6548\uFF08{field}\uFF09\uFF1A{reason}"
@@ -91,7 +108,8 @@ var zh_CN_default = {
91
108
  // src/audit-i18n.ts
92
109
  var auditM = core.i18n.createMessageGetter({
93
110
  "zh-CN": zh_CN_default,
94
- "en-US": en_US_default
111
+ "en-US": en_US_default,
112
+ "ja-JP": ja_JP_default
95
113
  });
96
114
  var AuditErrorInfo = {
97
115
  LOG_FAILED: "001:500",
@@ -107,6 +125,17 @@ var HaiAuditError = core.error.buildHaiErrorsDef("audit", AuditErrorInfo);
107
125
  // src/audit-repository-log.ts
108
126
  var logger = core.logger.child({ module: "audit", scope: "repository" });
109
127
  var AUDIT_TABLE = "hai_audit_logs";
128
+ function normalizeAuditLog(row) {
129
+ return {
130
+ ...row,
131
+ userId: row.userId ?? null,
132
+ resourceId: row.resourceId ?? null,
133
+ details: row.details == null ? null : typeof row.details === "string" ? row.details : JSON.stringify(row.details) ?? null,
134
+ ipAddress: row.ipAddress ?? null,
135
+ userAgent: row.userAgent ?? null,
136
+ createdAt: row.createdAt instanceof Date ? row.createdAt : new Date(row.createdAt)
137
+ };
138
+ }
110
139
  var AuditLogRepository = class extends BaseReldbCrudRepository {
111
140
  repoConfig;
112
141
  isSqlite;
@@ -133,16 +162,24 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
133
162
  this.repoConfig = config;
134
163
  this.isSqlite = this.db.config?.type === "sqlite";
135
164
  }
165
+ /**
166
+ * 等待基类完成建表及字段校验;init和自定义SQL共享此屏障,失败不发布仓库。
167
+ * @returns 建表结果,失败由调用方映射到对应审计错误
168
+ * @example const ready = await repository.ready(); if (!ready.success) return ready
169
+ */
170
+ ready() {
171
+ return this.ensureReady();
172
+ }
136
173
  /**
137
174
  * 将 Date 转为适合当前数据库类型的 SQL 参数
138
175
  *
139
- * SQLite 存储 TIMESTAMP 为毫秒时间戳,其他数据库使用 ISO 字符串。
176
+ * 与基类写入保持一致:SQLite 使用毫秒,其他数据库由驱动转换 Date。
140
177
  *
141
178
  * @param date - 要转换的日期
142
- * @returns SQLite 返回毫秒时间戳(number),其他返回 ISO 字符串
179
+ * @returns SQLite 返回毫秒时间戳,其他返回 Date
143
180
  */
144
181
  toDateParam(date) {
145
- return this.isSqlite ? date.getTime() : date.toISOString();
182
+ return this.isSqlite ? date.getTime() : date;
146
183
  }
147
184
  /**
148
185
  * 记录一条审计日志
@@ -211,10 +248,15 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
211
248
  * @returns 成功时返回 PaginatedResult(items/total/page/pageSize);失败时返回 QUERY_FAILED
212
249
  */
213
250
  async listWithUser(options = {}) {
251
+ const ready = await this.ready();
252
+ if (!ready.success)
253
+ return ready;
214
254
  logger.debug("Querying audit logs", { userId: options.userId, action: options.action, resource: options.resource });
215
255
  const conditions = [];
216
256
  const params = [];
217
257
  const { userTable, userIdColumn, userNameColumn } = this.repoConfig;
258
+ const quote = this.db.config?.type === "mysql" ? "`" : '"';
259
+ const identifier = (name) => `${quote}${name.replaceAll(quote, quote + quote)}${quote}`;
218
260
  if (options.userId) {
219
261
  conditions.push("a.user_id = ?");
220
262
  params.push(options.userId);
@@ -238,11 +280,11 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
238
280
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
239
281
  try {
240
282
  const result = await this.sql().queryPage({
241
- sql: `SELECT a.id, a.user_id AS userId, a.action, a.resource, a.resource_id AS resourceId,
242
- a.details, a.ip_address AS ipAddress, a.user_agent AS userAgent,
243
- a.created_at AS createdAt, u.${userNameColumn} AS username
283
+ sql: `SELECT a.id, a.user_id AS ${identifier("userId")}, a.action, a.resource, a.resource_id AS ${identifier("resourceId")},
284
+ a.details, a.ip_address AS ${identifier("ipAddress")}, a.user_agent AS ${identifier("userAgent")},
285
+ a.created_at AS ${identifier("createdAt")}, u.${identifier(userNameColumn)} AS username
244
286
  FROM ${AUDIT_TABLE} a
245
- LEFT JOIN ${userTable} u ON a.user_id = u.${userIdColumn}
287
+ LEFT JOIN ${identifier(userTable)} u ON a.user_id = u.${identifier(userIdColumn)}
246
288
  ${whereClause}
247
289
  ORDER BY a.created_at DESC`,
248
290
  params,
@@ -257,7 +299,7 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
257
299
  result.error
258
300
  );
259
301
  }
260
- return ok(result.data);
302
+ return ok({ ...result.data, items: result.data.items.map((row) => ({ ...normalizeAuditLog(row), username: row.username ?? null })) });
261
303
  } catch (error) {
262
304
  logger.error("Failed to query audit logs", { error });
263
305
  return err(
@@ -290,7 +332,7 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
290
332
  result.error
291
333
  );
292
334
  }
293
- return ok(result.data);
335
+ return ok(result.data.map(normalizeAuditLog));
294
336
  } catch (error) {
295
337
  return err(
296
338
  HaiAuditError.QUERY_FAILED,
@@ -306,6 +348,9 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
306
348
  * @returns 成功时返回删除的记录数;失败时返回 CLEANUP_FAILED
307
349
  */
308
350
  async cleanupOld(olderThanDays = 90) {
351
+ const ready = await this.ready();
352
+ if (!ready.success)
353
+ return ready;
309
354
  logger.debug("Cleaning up old audit logs", { olderThanDays });
310
355
  const cutoff = /* @__PURE__ */ new Date();
311
356
  cutoff.setDate(cutoff.getDate() - olderThanDays);
@@ -341,6 +386,9 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
341
386
  * @returns 成功时返回 AuditStatItem 数组(按 count 倒序);失败时返回 STATS_FAILED
342
387
  */
343
388
  async getStats(days = 7) {
389
+ const ready = await this.ready();
390
+ if (!ready.success)
391
+ return ready;
344
392
  logger.debug("Getting audit statistics", { days });
345
393
  const cutoff = /* @__PURE__ */ new Date();
346
394
  cutoff.setDate(cutoff.getDate() - days);
@@ -361,7 +409,10 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
361
409
  result.error
362
410
  );
363
411
  }
364
- return ok(result.data.map((item) => ({ action: item.action, count: Number(item.count) })));
412
+ const statistics = result.data.map((item) => ({ action: item.action, count: Number(item.count) }));
413
+ if (statistics.some((item) => !Number.isSafeInteger(item.count) || item.count < 0))
414
+ return err(HaiAuditError.STATS_FAILED, auditM("audit_invalidStatCount"));
415
+ return ok(statistics);
365
416
  } catch (error) {
366
417
  logger.error("Failed to query audit statistics", { error });
367
418
  return err(
@@ -395,6 +446,7 @@ function sanitizeAuditDetails(details) {
395
446
  var currentRepo = null;
396
447
  var currentHelper = null;
397
448
  var initInProgress = false;
449
+ var lifecycleGeneration = 0;
398
450
  var notInitialized = core.module.createNotInitializedKit(
399
451
  HaiAuditError.NOT_INITIALIZED,
400
452
  () => auditM("audit_notInitialized")
@@ -405,7 +457,7 @@ var audit = {
405
457
  * 初始化审计模块
406
458
  *
407
459
  * 会先关闭已有实例(如已初始化),再用新配置重新初始化。
408
- * 内部创建 AuditLogRepository 实例(BaseReldbCrudRepository 自动建表)。
460
+ * 等待 AuditLogRepository / BaseReldbCrudRepository 完成建表后才发布实例。
409
461
  * 依赖 @h-ai/reldb 已初始化。
410
462
  *
411
463
  * @param config - 初始化配置(可选,所有字段均有默认值)
@@ -428,13 +480,12 @@ var audit = {
428
480
  );
429
481
  }
430
482
  initInProgress = true;
483
+ const generation = ++lifecycleGeneration;
431
484
  try {
432
485
  if (currentRepo) {
433
486
  logger2.warn("Audit module is already initialized, reinitializing");
434
- const closeResult = await audit.close();
435
- if (!closeResult.success) {
436
- return closeResult;
437
- }
487
+ currentRepo = null;
488
+ currentHelper = null;
438
489
  }
439
490
  logger2.info("Initializing audit module");
440
491
  const parseResult = AuditInitConfigSchema.safeParse(config ?? {});
@@ -456,11 +507,18 @@ var audit = {
456
507
  identifierResult.error
457
508
  );
458
509
  }
459
- currentRepo = new AuditLogRepository({
510
+ const candidate = new AuditLogRepository({
460
511
  userTable: parsed.userTable,
461
512
  userIdColumn: parsed.userIdColumn,
462
513
  userNameColumn: parsed.userNameColumn
463
514
  });
515
+ const ready = await candidate.ready();
516
+ if (!ready.success) {
517
+ return err(HaiAuditError.CONFIG_ERROR, auditM("audit_initFailed", { params: { error: ready.error.message } }), ready.error);
518
+ }
519
+ if (generation !== lifecycleGeneration)
520
+ return err(HaiAuditError.NOT_INITIALIZED, auditM("audit_notInitialized"));
521
+ currentRepo = candidate;
464
522
  currentHelper = createHelper((input) => audit.log(input));
465
523
  logger2.info("Audit module initialized");
466
524
  return ok(void 0);
@@ -601,6 +659,7 @@ var audit = {
601
659
  * 关闭审计模块,释放内部状态
602
660
  */
603
661
  async close() {
662
+ lifecycleGeneration++;
604
663
  if (!currentRepo) {
605
664
  logger2.info("Audit module already closed, skipping");
606
665
  return ok(void 0);
@@ -613,6 +672,6 @@ var audit = {
613
672
  }
614
673
  };
615
674
 
616
- export { AuditInitConfigSchema, HaiAuditError, audit, createHelper };
675
+ export { AuditInitConfigSchema, HaiAuditError, audit };
617
676
  //# sourceMappingURL=index.js.map
618
677
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/audit-config.ts","../src/audit-helper.ts","../messages/en-US.json","../messages/zh-CN.json","../src/audit-i18n.ts","../src/audit-types.ts","../src/audit-repository-log.ts","../src/audit-main.ts"],"names":["core","ok","logger","err"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @h-ai/audit — 错误码与配置\n *\n * 定义审计模块的错误码常量与初始化配置类型。\n * @module audit-config\n */\n\nimport { z } from 'zod'\n\n// ─── 初始化配置 ───\n\n/**\n * 审计模块初始化配置 Schema(Zod)\n *\n * 所有字段均有默认值,可直接调用 `audit.init()` 无需传参。\n *\n * @example\n * ```ts\n * import { audit } from '@h-ai/audit'\n * import { reldb } from '@h-ai/reldb'\n *\n * await audit.init()\n *\n * // 自定义用户表映射\n * await audit.init({\n * userTable: 'sys_users',\n * userIdColumn: 'user_id',\n * userNameColumn: 'name',\n * })\n * ```\n */\nexport const AuditInitConfigSchema = z.object({\n /** 用户表名,用于 list 查询时 LEFT JOIN 获取用户名 */\n userTable: z.string().default('hai_iam_users'),\n /** 用户表主键列名,用于 JOIN 条件 */\n userIdColumn: z.string().default('id'),\n /** 用户表用户名列名,用于 SELECT 输出 */\n userNameColumn: z.string().default('username'),\n})\n\n/** 审计模块初始化配置(解析后) */\nexport type AuditInitConfig = z.output<typeof AuditInitConfigSchema>\n\n/** 审计模块初始化配置(传入参数) */\nexport type AuditInitConfigInput = z.input<typeof AuditInitConfigSchema>\n","/**\n * @h-ai/audit — 审计便捷记录器\n *\n * 封装常见审计操作(登录、登出、注册、密码重置、CRUD),简化调用方代码。\n * @module audit-helper\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type {\n AuditHelper,\n AuditLog,\n CreateAuditLogInput,\n CrudAuditInput,\n} from './audit-types.js'\n\nimport { ok } from '@h-ai/core'\n\n/**\n * 将 log 结果映射为 void 返回\n *\n * @param result - log 操作结果\n * @returns 成功返回 ok(undefined);失败透传原始错误\n */\nfunction toVoid(result: HaiResult<AuditLog>): HaiResult<void> {\n if (!result.success) {\n return result\n }\n return ok(undefined)\n}\n\n/**\n * 创建便捷记录器\n *\n * 封装常见审计操作(登录、登出、注册、密码重置、CRUD),\n * 简化调用方代码。每个方法内部调用 logFn 并将 AuditLog 结果映射为 void。\n *\n * @param logFn - 底层日志记录函数(通常为 currentRepo.log)\n * @returns 便捷记录器接口\n */\nexport function createHelper(logFn: (input: CreateAuditLogInput) => Promise<HaiResult<AuditLog>>): AuditHelper {\n return {\n async login(userId: string, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({ userId, action: 'login', resource: 'auth', ipAddress: ip, userAgent: ua })\n return toVoid(result)\n },\n\n async logout(userId: string, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({ userId, action: 'logout', resource: 'auth', ipAddress: ip, userAgent: ua })\n return toVoid(result)\n },\n\n async register(userId: string, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({ userId, action: 'register', resource: 'auth', resourceId: userId, ipAddress: ip, userAgent: ua })\n return toVoid(result)\n },\n\n async passwordResetRequest(email: string, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({\n action: 'password_reset_request',\n resource: 'auth',\n details: { email },\n ipAddress: ip,\n userAgent: ua,\n })\n return toVoid(result)\n },\n\n async passwordResetComplete(userId: string | null, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({ userId, action: 'password_reset', resource: 'auth', ipAddress: ip, userAgent: ua })\n return toVoid(result)\n },\n\n async crud(\n input: CrudAuditInput,\n ): Promise<HaiResult<void>> {\n const result = await logFn({\n userId: input.userId,\n action: input.action,\n resource: input.resource,\n resourceId: input.resourceId,\n details: input.details,\n ipAddress: input.ip,\n userAgent: input.ua,\n })\n return toVoid(result)\n },\n }\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"audit_notInitialized\": \"Audit module not initialized, call audit.init() first\",\n \"audit_initFailed\": \"Audit module initialization failed: {error}\",\n \"audit_logFailed\": \"Failed to record audit log: {error}\",\n \"audit_queryFailed\": \"Failed to query audit logs: {error}\",\n \"audit_cleanupFailed\": \"Failed to cleanup audit logs: {error}\",\n \"audit_statsFailed\": \"Failed to query audit statistics: {error}\",\n \"audit_configError\": \"Audit config validation failed: {error}\",\n \"audit_initInProgress\": \"Audit module initialization is already in progress\",\n \"audit_invalidInput\": \"Invalid audit input for {field}: {reason}\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"audit_notInitialized\": \"审计模块尚未初始化,请先调用 audit.init()\",\n \"audit_initFailed\": \"审计模块初始化失败:{error}\",\n \"audit_logFailed\": \"审计日志记录失败:{error}\",\n \"audit_queryFailed\": \"审计日志查询失败:{error}\",\n \"audit_cleanupFailed\": \"审计日志清理失败:{error}\",\n \"audit_statsFailed\": \"审计统计查询失败:{error}\",\n \"audit_configError\": \"审计配置校验失败:{error}\",\n \"audit_initInProgress\": \"审计模块正在初始化中,请勿并发调用\",\n \"audit_invalidInput\": \"审计输入参数无效({field}):{reason}\"\n}\n","/**\n * @h-ai/audit — i18n\n *\n * 本文件提供审计模块的 i18n 文案访问入口。\n * @module audit-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n// ─── i18n ───\n\n/**\n * 审计模块文案 Key 类型\n */\ntype AuditMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取审计模块文案。\n *\n * @param key - 文案 Key(以 `audit_` 为前缀)\n * @param options - 插值参数与格式化选项\n * @returns 本地化后的文案字符串\n * @remarks 具体参数结构与 core.i18n.createMessageGetter 保持一致。\n *\n * @example\n * ```ts\n * const text = auditM('audit_notInitialized')\n * const withError = auditM('audit_logFailed', { params: { error: 'DB connection lost' } })\n * ```\n */\nexport const auditM = core.i18n.createMessageGetter<AuditMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n})\n","/**\n * @h-ai/audit — 公共类型定义\n *\n * 审计模块对外类型:错误接口、实体类型、操作接口、函数接口。\n * @module audit-types\n */\n\nimport type { ErrorInfo, HaiResult, PaginatedResult, PaginationOptionsInput } from '@h-ai/core'\nimport type { AuditInitConfigInput } from './audit-config.js'\nimport { core } from '@h-ai/core'\n\n// ─── 错误定义(照 @h-ai/core 范式) ───\n\nconst AuditErrorInfo = {\n LOG_FAILED: '001:500',\n QUERY_FAILED: '002:500',\n CLEANUP_FAILED: '003:500',\n STATS_FAILED: '004:500',\n INIT_IN_PROGRESS: '005:409',\n NOT_INITIALIZED: '010:500',\n CONFIG_ERROR: '012:500',\n} as const satisfies ErrorInfo\n\nexport const HaiAuditError = core.error.buildHaiErrorsDef('audit', AuditErrorInfo)\n\n// ─── 实体类型 ───\n\n/**\n * 审计日志记录\n *\n * 表示一条已持久化的审计日志条目。\n */\nexport interface AuditLog {\n /** 审计日志 ID,格式为 `audit_` 前缀 + 随机串 */\n id: string\n /** 操作用户 ID;系统操作时为 null */\n userId: string | null\n /** 操作类型(如 login / logout / create / update / delete) */\n action: string\n /** 资源类型(如 auth / users / roles) */\n resource: string\n /** 资源 ID;不涉及特定资源时为 null */\n resourceId: string | null\n /** 操作详情(JSON 字符串);无详情时为 null */\n details: string | null\n /** 客户端 IP 地址;未知时为 null */\n ipAddress: string | null\n /** 客户端 User-Agent;未知时为 null */\n userAgent: string | null\n /** 创建时间 */\n createdAt: Date\n}\n\n/**\n * 审计日志记录(含用户名)\n *\n * 通过 LEFT JOIN 用户表获取 username 字段,用于列表展示。\n */\nexport interface AuditLogWithUser extends AuditLog {\n /** 操作用户的用户名;用户不存在或系统操作时为 null */\n username: string | null\n}\n\n/**\n * 创建审计日志输入\n *\n * 调用 {@link AuditFunctions.log} 时传入的参数。\n * 仅 `action` 和 `resource` 为必填。\n */\nexport interface CreateAuditLogInput {\n /** 操作用户 ID;系统操作可省略 */\n userId?: string | null\n /** 操作类型(如 login / create / update / delete) */\n action: string\n /** 资源类型(如 auth / users / roles) */\n resource: string\n /** 资源 ID;不涉及特定资源时可省略 */\n resourceId?: string | null\n /** 操作详情对象;密码、token、secret 等凭证类字段会先自动脱敏,再 JSON 序列化存储 */\n details?: Record<string, unknown> | null\n /** 客户端 IP 地址 */\n ipAddress?: string | null\n /** 客户端 User-Agent */\n userAgent?: string | null\n}\n\n/**\n * 审计日志列表查询选项\n *\n * 所有字段均为可选,不传则不过滤。\n */\nexport interface ListAuditLogsOptions extends PaginationOptionsInput {\n /** 按用户 ID 过滤 */\n userId?: string\n /** 按操作类型过滤(精确匹配) */\n action?: string\n /** 按资源类型过滤(精确匹配) */\n resource?: string\n /** 起始时间(含),只返回此时间之后的日志 */\n startDate?: Date\n /** 结束时间(含),只返回此时间之前的日志 */\n endDate?: Date\n}\n\n/**\n * 审计统计结果项\n *\n * 按操作类型(action)分组的统计条目。\n */\nexport interface AuditStatItem {\n /** 操作类型 */\n action: string\n /** 该操作在统计时间范围内的次数 */\n count: number\n}\n\n/**\n * CRUD 审计操作输入\n *\n * 调用 {@link AuditHelper.crud} 时传入的参数。\n */\nexport interface CrudAuditInput {\n /** 操作用户 ID(系统操作传 null) */\n userId?: string | null\n /** 操作类型 */\n action: 'create' | 'read' | 'update' | 'delete'\n /** 资源类型(如 users / roles) */\n resource: string\n /** 资源 ID(可选) */\n resourceId?: string\n /** 操作详情对象(可选) */\n details?: Record<string, unknown>\n /** 客户端 IP(可选) */\n ip?: string\n /** 客户端 User-Agent(可选) */\n ua?: string\n}\n\n// ─── 便捷操作接口 ───\n\n/**\n * 审计便捷记录器\n *\n * 封装常见审计场景(认证、CRUD),简化调用方代码。\n * 通过 {@link AuditFunctions.helper} 访问。\n *\n * @example\n * ```ts\n * await audit.helper.login('user_1', '127.0.0.1', 'Mozilla/5.0')\n * await audit.helper.crud({ userId: 'user_1', action: 'create', resource: 'users', resourceId: 'user_2' })\n * ```\n */\nexport interface AuditHelper {\n /**\n * 记录用户登录\n *\n * @param userId - 登录用户 ID\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n login: (userId: string, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录用户登出\n *\n * @param userId - 登出用户 ID\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n logout: (userId: string, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录用户注册\n *\n * @param userId - 注册用户 ID\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n register: (userId: string, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录密码重置请求\n *\n * @param email - 请求重置的邮箱地址\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n passwordResetRequest: (email: string, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录密码重置完成\n *\n * @param userId - 重置密码的用户 ID(匿名重置场景传 null)\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n passwordResetComplete: (userId: string | null, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录 CRUD 操作\n *\n * @param input - CRUD 审计输入(action 和 resource 为必填)\n *\n * @example\n * ```ts\n * await audit.helper.crud({ userId: 'user_1', action: 'create', resource: 'users', resourceId: 'user_2' })\n * ```\n */\n crud: (input: CrudAuditInput) => Promise<HaiResult<void>>\n}\n\n// ─── 函数接口 ───\n\n/**\n * 审计模块函数接口\n *\n * 统一的审计日志访问入口。\n *\n * @example\n * ```ts\n * import { audit } from '@h-ai/audit'\n * import { reldb } from '@h-ai/reldb'\n *\n * await reldb.init({ type: 'sqlite', database: './data.db' })\n * await audit.init()\n *\n * await audit.log({ userId: 'user_1', action: 'login', resource: 'auth' })\n * const logs = await audit.list({ pageSize: 10 })\n * await audit.close()\n * ```\n */\nexport interface AuditFunctions {\n /**\n * 初始化审计模块\n *\n * @param config - 初始化配置(可选,所有字段均有默认值)\n * @returns 成功时返回 ok(undefined);失败时返回 CONFIG_ERROR\n */\n init: (config?: AuditInitConfigInput) => Promise<HaiResult<void>>\n /**\n * 关闭审计模块,释放内部状态\n */\n close: () => Promise<HaiResult<void>>\n /** 当前是否已初始化 */\n readonly isInitialized: boolean\n /**\n * 记录一条审计日志\n *\n * @param input - 日志内容(action 和 resource 为必填)\n * @returns 成功时返回创建的 AuditLog;失败时返回 LOG_FAILED\n */\n log: (input: CreateAuditLogInput) => Promise<HaiResult<AuditLog>>\n /**\n * 分页查询审计日志列表(含用户名 LEFT JOIN)\n *\n * @param options - 查询过滤与分页选项\n * @returns 成功时返回 PaginatedResult(items/total/page/pageSize);失败时返回 QUERY_FAILED\n */\n list: (options?: ListAuditLogsOptions) => Promise<HaiResult<PaginatedResult<AuditLogWithUser>>>\n /**\n * 获取指定用户的最近活动记录\n *\n * @param userId - 用户 ID\n * @param limit - 最大返回条数,默认 10\n * @returns 成功时返回 AuditLog 数组;失败时返回 QUERY_FAILED\n */\n getUserRecent: (userId: string, limit?: number) => Promise<HaiResult<AuditLog[]>>\n /**\n * 清理指定天数之前的旧日志\n *\n * @param olderThanDays - 保留天数,默认 90;清理此天数之前的日志\n * @returns 成功时返回删除的记录数;失败时返回 CLEANUP_FAILED\n */\n cleanup: (olderThanDays?: number) => Promise<HaiResult<number>>\n /**\n * 获取指定天数内的操作统计(按 action 分组计数)\n *\n * @param days - 统计天数,默认 7\n * @returns 成功时返回 AuditStatItem 数组;失败时返回 STATS_FAILED\n */\n getStats: (days?: number) => Promise<HaiResult<AuditStatItem[]>>\n /**\n * 便捷记录器,封装常见审计场景\n *\n * 未初始化时调用任意方法均返回 NOT_INITIALIZED 错误。\n */\n readonly helper: AuditHelper\n}\n","/**\n * @h-ai/audit — 审计日志仓库\n *\n * 基于 @h-ai/reldb BaseReldbCrudRepository 实现审计日志的持久化与查询。 仅在需要 JOIN / 聚合时使用自定义 SQL,其余 CRUD 操作由基类处理。\n * @module audit-repository\n */\n\nimport type { HaiResult, PaginatedResult } from '@h-ai/core'\nimport type { AuditLog, AuditLogWithUser, AuditStatItem, ListAuditLogsOptions } from './audit-types.js'\nimport { core, err, ok } from '@h-ai/core'\nimport { BaseReldbCrudRepository, reldb } from '@h-ai/reldb'\nimport { auditM } from './audit-i18n.js'\nimport { HaiAuditError } from './audit-types.js'\n\nconst logger = core.logger.child({ module: 'audit', scope: 'repository' })\n\n// ─── 仓库配置 ───\n\n/**\n * 审计日志仓库配置(内部使用)\n *\n * 由 audit-main.ts 在 init() 时构造并传入。\n */\n/** 审计日志表名(固定值) */\nconst AUDIT_TABLE = 'hai_audit_logs'\n\ninterface AuditRepositoryConfig {\n /** 用户表名 */\n userTable: string\n /** 用户表主键列名 */\n userIdColumn: string\n /** 用户表用户名列名 */\n userNameColumn: string\n}\n\n// ─── 仓库 ───\n\n/**\n * 审计日志仓库\n *\n * 通过 BaseReldbCrudRepository 自动建表、字段映射与类型转换,\n * 仅在需要 JOIN / 聚合时使用自定义 SQL。\n *\n * @remarks 此类仅供 audit-main.ts 内部使用,不通过 index.ts 对外导出。\n */\nexport class AuditLogRepository extends BaseReldbCrudRepository<AuditLog> {\n private readonly repoConfig: AuditRepositoryConfig\n private readonly isSqlite: boolean\n\n /**\n * @param config - 仓库配置(用户表映射)\n */\n constructor(config: AuditRepositoryConfig) {\n super(reldb, {\n table: AUDIT_TABLE,\n idColumn: 'id',\n generateId: () => core.id.withPrefix('audit_'),\n fields: [\n { fieldName: 'id', columnName: 'id', def: { type: 'TEXT', primaryKey: true }, select: true, create: true, update: false },\n { fieldName: 'userId', columnName: 'user_id', def: { type: 'TEXT' }, select: true, create: true, update: false },\n { fieldName: 'action', columnName: 'action', def: { type: 'TEXT', notNull: true }, select: true, create: true, update: false },\n { fieldName: 'resource', columnName: 'resource', def: { type: 'TEXT', notNull: true }, select: true, create: true, update: false },\n { fieldName: 'resourceId', columnName: 'resource_id', def: { type: 'TEXT' }, select: true, create: true, update: false },\n { fieldName: 'details', columnName: 'details', def: { type: 'JSON' }, select: true, create: true, update: false },\n { fieldName: 'ipAddress', columnName: 'ip_address', def: { type: 'TEXT' }, select: true, create: true, update: false },\n { fieldName: 'userAgent', columnName: 'user_agent', def: { type: 'TEXT' }, select: true, create: true, update: false },\n { fieldName: 'createdAt', columnName: 'created_at', def: { type: 'TIMESTAMP' }, select: true, create: true, update: false },\n ],\n })\n this.repoConfig = config\n this.isSqlite = this.db.config?.type === 'sqlite'\n }\n\n /**\n * 将 Date 转为适合当前数据库类型的 SQL 参数\n *\n * SQLite 存储 TIMESTAMP 为毫秒时间戳,其他数据库使用 ISO 字符串。\n *\n * @param date - 要转换的日期\n * @returns SQLite 返回毫秒时间戳(number),其他返回 ISO 字符串\n */\n private toDateParam(date: Date): number | string {\n return this.isSqlite ? date.getTime() : date.toISOString()\n }\n\n /**\n * 记录一条审计日志\n *\n * @param input - 日志内容\n * @param input.userId - 操作用户 ID(系统操作可省略)\n * @param input.action - 操作类型(如 login / create)\n * @param input.resource - 资源类型(如 auth / users)\n * @param input.resourceId - 资源 ID(可选)\n * @param input.details - 操作详情对象(可选)\n * @param input.ipAddress - 客户端 IP(可选)\n * @param input.userAgent - 客户端 User-Agent(可选)\n * @returns 成功时返回创建的 AuditLog;失败时返回 LOG_FAILED\n */\n async log(input: {\n userId?: string | null\n action: string\n resource: string\n resourceId?: string | null\n details?: Record<string, unknown> | null\n ipAddress?: string | null\n userAgent?: string | null\n }): Promise<HaiResult<AuditLog>> {\n logger.debug('Recording audit log', { action: input.action, resource: input.resource })\n\n const id = core.id.withPrefix('audit_')\n const now = new Date()\n const data: Record<string, unknown> = {\n id,\n userId: input.userId ?? null,\n action: input.action,\n resource: input.resource,\n resourceId: input.resourceId ?? null,\n details: input.details ?? null,\n ipAddress: input.ipAddress ?? null,\n userAgent: input.userAgent ?? null,\n createdAt: now,\n }\n\n try {\n const createResult = await this.create(data)\n if (!createResult.success) {\n logger.error('Failed to record audit log', { action: input.action, error: createResult.error.message })\n return err(\n HaiAuditError.LOG_FAILED,\n auditM('audit_logFailed', { params: { error: createResult.error.message } }),\n createResult.error,\n )\n }\n\n const auditLog: AuditLog = {\n id,\n userId: input.userId ?? null,\n action: input.action,\n resource: input.resource,\n resourceId: input.resourceId ?? null,\n details: input.details ? JSON.stringify(input.details) : null,\n ipAddress: input.ipAddress ?? null,\n userAgent: input.userAgent ?? null,\n createdAt: now,\n }\n\n logger.info('Audit log recorded', { id, action: input.action, resource: input.resource })\n return ok(auditLog)\n }\n catch (error) {\n logger.error('Failed to record audit log', { action: input.action, error })\n return err(\n HaiAuditError.LOG_FAILED,\n auditM('audit_logFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n\n /**\n * 分页查询日志列表(含用户名 LEFT JOIN)\n *\n * @param options - 过滤条件与分页参数\n * @returns 成功时返回 PaginatedResult(items/total/page/pageSize);失败时返回 QUERY_FAILED\n */\n async listWithUser(options: ListAuditLogsOptions = {}): Promise<HaiResult<PaginatedResult<AuditLogWithUser>>> {\n logger.debug('Querying audit logs', { userId: options.userId, action: options.action, resource: options.resource })\n\n const conditions: string[] = []\n const params: unknown[] = []\n const { userTable, userIdColumn, userNameColumn } = this.repoConfig\n\n if (options.userId) {\n conditions.push('a.user_id = ?')\n params.push(options.userId)\n }\n if (options.action) {\n conditions.push('a.action = ?')\n params.push(options.action)\n }\n if (options.resource) {\n conditions.push('a.resource = ?')\n params.push(options.resource)\n }\n if (options.startDate) {\n conditions.push('a.created_at >= ?')\n params.push(this.toDateParam(options.startDate))\n }\n if (options.endDate) {\n conditions.push('a.created_at <= ?')\n params.push(this.toDateParam(options.endDate))\n }\n\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n\n try {\n const result = await this.sql().queryPage<AuditLogWithUser>({\n sql: `SELECT a.id, a.user_id AS userId, a.action, a.resource, a.resource_id AS resourceId,\n a.details, a.ip_address AS ipAddress, a.user_agent AS userAgent,\n a.created_at AS createdAt, u.${userNameColumn} AS username\n FROM ${AUDIT_TABLE} a\n LEFT JOIN ${userTable} u ON a.user_id = u.${userIdColumn}\n ${whereClause}\n ORDER BY a.created_at DESC`,\n params,\n pagination: { page: options.page, pageSize: options.pageSize },\n overrides: { defaultPageSize: 20 },\n })\n\n if (!result.success) {\n logger.error('Failed to query audit logs', { error: result.error.message })\n return err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_queryFailed', { params: { error: result.error.message } }),\n result.error,\n )\n }\n\n return ok(result.data)\n }\n catch (error) {\n logger.error('Failed to query audit logs', { error })\n return err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_queryFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n\n /**\n * 获取指定用户的最近活动\n *\n * @param userId - 用户 ID\n * @param limit - 最大返回条数,默认 10\n * @returns 成功时返回 AuditLog 数组(按时间倒序);失败时返回 QUERY_FAILED\n */\n async getUserRecent(userId: string, limit = 10): Promise<HaiResult<AuditLog[]>> {\n logger.debug('Getting user recent activity', { userId, limit })\n\n try {\n const result = await this.findAll({\n where: 'user_id = ?',\n params: [userId],\n orderBy: 'created_at DESC',\n limit,\n })\n\n if (!result.success) {\n return err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_queryFailed', { params: { error: result.error.message } }),\n result.error,\n )\n }\n\n return ok(result.data)\n }\n catch (error) {\n return err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_queryFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n\n /**\n * 清理指定天数之前的旧日志\n *\n * @param olderThanDays - 保留天数,默认 90;清理此天数之前的日志\n * @returns 成功时返回删除的记录数;失败时返回 CLEANUP_FAILED\n */\n async cleanupOld(olderThanDays = 90): Promise<HaiResult<number>> {\n logger.debug('Cleaning up old audit logs', { olderThanDays })\n\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - olderThanDays)\n\n try {\n const result = await this.sql().execute(\n `DELETE FROM ${AUDIT_TABLE} WHERE created_at < ?`,\n [this.toDateParam(cutoff)],\n )\n\n if (!result.success) {\n logger.error('Failed to cleanup audit logs', { error: result.error.message })\n return err(\n HaiAuditError.CLEANUP_FAILED,\n auditM('audit_cleanupFailed', { params: { error: result.error.message } }),\n result.error,\n )\n }\n\n const deleted = result.data.changes\n logger.info('Audit logs cleaned up', { olderThanDays, deleted })\n return ok(deleted)\n }\n catch (error) {\n logger.error('Failed to cleanup audit logs', { error })\n return err(\n HaiAuditError.CLEANUP_FAILED,\n auditM('audit_cleanupFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n\n /**\n * 获取指定天数内的操作统计(按 action 分组计数)\n *\n * @param days - 统计天数,默认 7\n * @returns 成功时返回 AuditStatItem 数组(按 count 倒序);失败时返回 STATS_FAILED\n */\n async getStats(days = 7): Promise<HaiResult<AuditStatItem[]>> {\n logger.debug('Getting audit statistics', { days })\n\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - days)\n\n try {\n const result = await this.sql().query<AuditStatItem>(\n `SELECT action, COUNT(*) as count\n FROM ${AUDIT_TABLE}\n WHERE created_at >= ?\n GROUP BY action\n ORDER BY count DESC`,\n [this.toDateParam(cutoff)],\n )\n\n if (!result.success) {\n logger.error('Failed to query audit statistics', { error: result.error.message })\n return err(\n HaiAuditError.STATS_FAILED,\n auditM('audit_statsFailed', { params: { error: result.error.message } }),\n result.error,\n )\n }\n\n return ok(result.data.map(item => ({ action: item.action, count: Number(item.count) })))\n }\n catch (error) {\n logger.error('Failed to query audit statistics', { error })\n return err(\n HaiAuditError.STATS_FAILED,\n auditM('audit_statsFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n}\n","/**\n * @h-ai/audit — 审计日志服务主入口\n *\n * 本文件提供统一的 `audit` 对象,聚合所有审计日志操作功能。\n * @module audit-main\n */\n\nimport type { HaiResult, PaginatedResult } from '@h-ai/core'\nimport type { AuditInitConfigInput } from './audit-config.js'\nimport type {\n AuditFunctions,\n AuditHelper,\n AuditLog,\n AuditLogWithUser,\n AuditStatItem,\n CreateAuditLogInput,\n ListAuditLogsOptions,\n} from './audit-types.js'\nimport { core, err, ok } from '@h-ai/core'\nimport { validateIdentifiers } from '@h-ai/reldb'\nimport { AuditInitConfigSchema } from './audit-config.js'\nimport { createHelper } from './audit-helper.js'\n\nimport { auditM } from './audit-i18n.js'\nimport { AuditLogRepository } from './audit-repository-log.js'\nimport {\n HaiAuditError,\n} from './audit-types.js'\n\nconst logger = core.logger.child({ module: 'audit', scope: 'main' })\n\n// ─── 输入校验 ───\n\n/** 字符串字段最大长度(防止异常大输入) */\nconst MAX_TEXT_LENGTH = 256\n\n/** 审计详情中的敏感字段统一脱敏为此占位值 */\nconst REDACTED_AUDIT_DETAIL_VALUE = '[REDACTED]'\n\n/**\n * 判断值是否为非空字符串\n *\n * @param value - 待校验值\n */\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\n/**\n * 校验字符串长度是否在限制内\n *\n * @param value - 待校验字符串\n * @param maxLength - 允许最大长度\n */\nfunction isValidTextLength(value: string, maxLength = MAX_TEXT_LENGTH): boolean {\n return value.length <= maxLength\n}\n\n/**\n * 对审计详情执行统一脱敏\n *\n * @param details - 原始详情对象\n */\nfunction sanitizeAuditDetails(details?: Record<string, unknown> | null): Record<string, unknown> | null {\n if (!details) {\n return details ?? null\n }\n\n return core.sanitize.sanitizeSensitiveFields(details, {\n matcher: core.sanitize.defaultSensitiveKeyRegex,\n replacement: REDACTED_AUDIT_DETAIL_VALUE,\n }) as Record<string, unknown>\n}\n\n// ─── 内部状态 ───\n\n/** 当前审计日志仓库实例;init 后赋值,close 后置 null */\nlet currentRepo: AuditLogRepository | null = null\n\n/** 缓存的便捷记录器实例;init 时创建,close 时置 null */\nlet currentHelper: AuditHelper | null = null\n\n/** 并发初始化防护标志 */\nlet initInProgress = false\n\n// ─── 未初始化占位 ───\n\n/**\n * 未初始化时的错误工具集\n *\n * 调用 audit.log / audit.list 等方法时,\n * 若模块未初始化则返回 { success: false, error: { code: NOT_INITIALIZED } }\n */\nconst notInitialized = core.module.createNotInitializedKit(\n HaiAuditError.NOT_INITIALIZED,\n () => auditM('audit_notInitialized'),\n)\n\n// ─── 便捷记录器占位 ───\n\n/** 未初始化时的便捷记录器占位 */\nconst notInitializedHelper = notInitialized.proxy<AuditHelper>()\n\n// ─── 审计服务对象 ───\n\n/**\n * 审计日志服务对象\n *\n * 统一的审计日志访问入口,提供以下功能:\n * - `audit.init()` — 初始化审计模块\n * - `audit.close()` — 关闭模块\n * - `audit.log()` — 记录审计日志\n * - `audit.list()` — 查询审计日志列表\n * - `audit.getUserRecent()` — 获取用户最近活动\n * - `audit.cleanup()` — 清理旧日志\n * - `audit.getStats()` — 获取统计数据\n * - `audit.helper` — 便捷记录器\n * - `audit.isInitialized` — 初始化状态\n *\n * @example\n * ```ts\n * import { audit } from '@h-ai/audit'\n * import { reldb } from '@h-ai/reldb'\n *\n * await reldb.init({ type: 'sqlite', database: ':memory:' })\n * const result = await audit.init()\n * if (!result.success) {\n * // 处理初始化错误\n * }\n *\n * await audit.helper.login('user_1', '127.0.0.1')\n * const logs = await audit.list({ pageSize: 10 })\n * await audit.close()\n * ```\n */\nexport const audit: AuditFunctions = {\n /**\n * 初始化审计模块\n *\n * 会先关闭已有实例(如已初始化),再用新配置重新初始化。\n * 内部创建 AuditLogRepository 实例(BaseReldbCrudRepository 自动建表)。\n * 依赖 @h-ai/reldb 已初始化。\n *\n * @param config - 初始化配置(可选,所有字段均有默认值)\n * @returns 成功时返回 ok(undefined);失败时返回 CONFIG_ERROR\n *\n * @example\n * ```ts\n * const result = await audit.init()\n * if (!result.success) {\n * logger.error('Audit init failed', { error: result.error.message })\n * }\n * ```\n */\n async init(config?: AuditInitConfigInput): Promise<HaiResult<void>> {\n // 并发初始化防护:避免多次 init 同时执行导致资源泄漏\n if (initInProgress) {\n logger.warn('Audit init already in progress, skipping concurrent call')\n return err(\n HaiAuditError.INIT_IN_PROGRESS,\n auditM('audit_initInProgress'),\n )\n }\n initInProgress = true\n\n try {\n if (currentRepo) {\n logger.warn('Audit module is already initialized, reinitializing')\n const closeResult = await audit.close()\n if (!closeResult.success) {\n return closeResult\n }\n }\n\n logger.info('Initializing audit module')\n\n const parseResult = AuditInitConfigSchema.safeParse(config ?? {})\n if (!parseResult.success) {\n logger.error('Audit config validation failed', { error: parseResult.error.message })\n return err(\n HaiAuditError.CONFIG_ERROR,\n auditM('audit_configError', { params: { error: parseResult.error.message } }),\n parseResult.error,\n )\n }\n const parsed = parseResult.data\n\n const identifierResult = validateIdentifiers([parsed.userTable, parsed.userIdColumn, parsed.userNameColumn])\n if (!identifierResult.success) {\n logger.error('Audit config contains invalid identifiers', { error: identifierResult.error.message })\n return err(\n HaiAuditError.CONFIG_ERROR,\n auditM('audit_configError', { params: { error: identifierResult.error.message } }),\n identifierResult.error,\n )\n }\n\n currentRepo = new AuditLogRepository({\n userTable: parsed.userTable,\n userIdColumn: parsed.userIdColumn,\n userNameColumn: parsed.userNameColumn,\n })\n currentHelper = createHelper(input => audit.log(input))\n\n logger.info('Audit module initialized')\n return ok(undefined)\n }\n catch (error) {\n logger.error('Audit module initialization failed', { error })\n return err(\n HaiAuditError.CONFIG_ERROR,\n auditM('audit_initFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n finally {\n initInProgress = false\n }\n },\n\n /** 当前是否已初始化 */\n get isInitialized(): boolean {\n return currentRepo !== null\n },\n\n /**\n * 记录一条审计日志\n *\n * @param input - 日志内容(action 和 resource 为必填)\n * @returns 成功时返回创建的 AuditLog;未初始化时返回 NOT_INITIALIZED\n */\n log(input: CreateAuditLogInput): Promise<HaiResult<AuditLog>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n const sanitizedInput: CreateAuditLogInput = {\n ...input,\n details: sanitizeAuditDetails(input.details),\n }\n\n if (!isNonEmptyString(sanitizedInput.action) || !isNonEmptyString(sanitizedInput.resource)) {\n return Promise.resolve(err(\n HaiAuditError.LOG_FAILED,\n auditM('audit_invalidInput', { params: { field: 'action/resource', reason: 'must be non-empty string' } }),\n ))\n }\n\n if (!isValidTextLength(sanitizedInput.action) || !isValidTextLength(sanitizedInput.resource)) {\n return Promise.resolve(err(\n HaiAuditError.LOG_FAILED,\n auditM('audit_invalidInput', { params: { field: 'action/resource', reason: 'exceeds max length' } }),\n ))\n }\n\n return currentRepo.log(sanitizedInput)\n },\n\n /**\n * 分页查询审计日志列表(含用户名 LEFT JOIN)\n *\n * @param options - 过滤条件与分页参数\n * @returns 成功时返回 PaginatedResult(items/total/page/pageSize);未初始化时返回 NOT_INITIALIZED\n */\n list(options?: ListAuditLogsOptions): Promise<HaiResult<PaginatedResult<AuditLogWithUser>>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n if (options?.startDate && options?.endDate && options.startDate > options.endDate) {\n return Promise.resolve(err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_invalidInput', { params: { field: 'dateRange', reason: 'startDate must be before endDate' } }),\n ))\n }\n\n return currentRepo.listWithUser(options)\n },\n\n /**\n * 获取指定用户的最近活动记录\n *\n * @param userId - 用户 ID\n * @param limit - 最大返回条数,默认 10\n * @returns 成功时返回 AuditLog 数组;未初始化时返回 NOT_INITIALIZED\n */\n getUserRecent(userId: string, limit?: number): Promise<HaiResult<AuditLog[]>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n if (!isNonEmptyString(userId)) {\n return Promise.resolve(err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_invalidInput', { params: { field: 'userId', reason: 'must be non-empty string' } }),\n ))\n }\n\n if (typeof limit === 'number' && (!Number.isInteger(limit) || limit <= 0)) {\n return Promise.resolve(err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_invalidInput', { params: { field: 'limit', reason: 'must be a positive integer' } }),\n ))\n }\n\n return currentRepo.getUserRecent(userId, limit)\n },\n\n /**\n * 清理指定天数之前的旧日志\n *\n * @param olderThanDays - 保留天数,默认 90\n * @returns 成功时返回删除的记录数;未初始化时返回 NOT_INITIALIZED\n */\n cleanup(olderThanDays?: number): Promise<HaiResult<number>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n if (typeof olderThanDays === 'number' && (!Number.isInteger(olderThanDays) || olderThanDays < 0)) {\n return Promise.resolve(err(\n HaiAuditError.CLEANUP_FAILED,\n auditM('audit_invalidInput', { params: { field: 'olderThanDays', reason: 'must be a non-negative integer' } }),\n ))\n }\n\n return currentRepo.cleanupOld(olderThanDays)\n },\n\n /**\n * 获取指定天数内的操作统计(按 action 分组计数)\n *\n * @param days - 统计天数,默认 7\n * @returns 成功时返回 AuditStatItem 数组;未初始化时返回 NOT_INITIALIZED\n */\n getStats(days?: number): Promise<HaiResult<AuditStatItem[]>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n if (typeof days === 'number' && (!Number.isInteger(days) || days < 0)) {\n return Promise.resolve(err(\n HaiAuditError.STATS_FAILED,\n auditM('audit_invalidInput', { params: { field: 'days', reason: 'must be a non-negative integer' } }),\n ))\n }\n\n return currentRepo.getStats(days)\n },\n\n /**\n * 便捷记录器\n *\n * 未初始化时调用任意方法均返回 NOT_INITIALIZED 错误。\n */\n get helper(): AuditHelper {\n if (!currentHelper) {\n return notInitializedHelper\n }\n return currentHelper\n },\n\n /**\n * 关闭审计模块,释放内部状态\n */\n async close(): Promise<HaiResult<void>> {\n if (!currentRepo) {\n logger.info('Audit module already closed, skipping')\n return ok(undefined)\n }\n\n logger.info('Closing audit module')\n currentRepo = null\n currentHelper = null\n logger.info('Audit module closed')\n return ok(undefined)\n },\n}\n"]}
1
+ {"version":3,"sources":["../src/audit-config.ts","../src/audit-helper.ts","../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/audit-i18n.ts","../src/audit-types.ts","../src/audit-repository-log.ts","../src/audit-main.ts"],"names":["core","ok","logger","err"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @h-ai/audit — 错误码与配置\n *\n * 定义审计模块的错误码常量与初始化配置类型。\n * @module audit-config\n */\n\nimport { z } from 'zod'\n\n// ─── 初始化配置 ───\n\n/**\n * 审计模块初始化配置 Schema(Zod)\n *\n * 所有字段均有默认值,可直接调用 `audit.init()` 无需传参。\n * @example\n * ```ts\n * import { AuditInitConfigSchema } from '@h-ai/audit'\n * const config = AuditInitConfigSchema.parse({ userTable: 'app_users' })\n * // 这里只校验字段映射;实际初始化前必须已有可用的 RelDB。\n * ```\n */\nexport const AuditInitConfigSchema = z.object({\n /** 用户表名,用于 list 查询时 LEFT JOIN 获取用户名 */\n userTable: z.string().default('hai_iam_users'),\n /** 用户表主键列名,用于 JOIN 条件 */\n userIdColumn: z.string().default('id'),\n /** 用户表用户名列名,用于 SELECT 输出 */\n userNameColumn: z.string().default('username'),\n})\n\n/**\n * 审计模块初始化配置(解析后)\n * @example\n * ```ts\n * import { AuditInitConfigSchema, type AuditInitConfig } from '@h-ai/audit'\n * const config: AuditInitConfig = AuditInitConfigSchema.parse({ userTable: 'app_users' })\n * ```\n */\nexport type AuditInitConfig = z.output<typeof AuditInitConfigSchema>\n\n/**\n * 审计模块初始化配置(传入参数)\n * @example\n * ```ts\n * import type { AuditInitConfigInput } from '@h-ai/audit'\n * const record: AuditInitConfigInput = { userTable: 'app_users', userIdColumn: 'id', userNameColumn: 'username' }\n * ```\n */\nexport type AuditInitConfigInput = z.input<typeof AuditInitConfigSchema>\n","/**\n * @h-ai/audit — 审计便捷记录器\n *\n * 封装常见审计操作(登录、登出、注册、密码重置、CRUD),简化调用方代码。\n * @module audit-helper\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type {\n AuditHelper,\n AuditLog,\n CreateAuditLogInput,\n CrudAuditInput,\n} from './audit-types.js'\n\nimport { ok } from '@h-ai/core'\n\n/**\n * 将 log 结果映射为 void 返回\n *\n * @param result - log 操作结果\n * @returns 成功返回 ok(undefined);失败透传原始错误\n */\nfunction toVoid(result: HaiResult<AuditLog>): HaiResult<void> {\n if (!result.success) {\n return result\n }\n return ok(undefined)\n}\n\n/**\n * 创建便捷记录器\n *\n * 封装常见审计操作(登录、登出、注册、密码重置、CRUD),\n * 简化调用方代码。每个方法内部调用 logFn 并将 AuditLog 结果映射为 void。\n *\n * @param logFn - 底层日志记录函数(通常为 currentRepo.log)\n * @returns 便捷记录器接口\n * @example 模块内部上下文\n * ```ts\n * function buildHelper(logFn: (input: CreateAuditLogInput) => Promise<HaiResult<AuditLog>>): AuditHelper {\n * return createHelper(logFn)\n * }\n * // logFn 由实际仓储/模块提供;helper 将失败原样传递,不接管数据库生命周期。\n * ```\n */\nexport function createHelper(logFn: (input: CreateAuditLogInput) => Promise<HaiResult<AuditLog>>): AuditHelper {\n return {\n async login(userId: string, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({ userId, action: 'login', resource: 'auth', ipAddress: ip, userAgent: ua })\n return toVoid(result)\n },\n\n async logout(userId: string, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({ userId, action: 'logout', resource: 'auth', ipAddress: ip, userAgent: ua })\n return toVoid(result)\n },\n\n async register(userId: string, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({ userId, action: 'register', resource: 'auth', resourceId: userId, ipAddress: ip, userAgent: ua })\n return toVoid(result)\n },\n\n async passwordResetRequest(email: string, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({\n action: 'password_reset_request',\n resource: 'auth',\n details: { email },\n ipAddress: ip,\n userAgent: ua,\n })\n return toVoid(result)\n },\n\n async passwordResetComplete(userId: string | null, ip?: string, ua?: string): Promise<HaiResult<void>> {\n const result = await logFn({ userId, action: 'password_reset', resource: 'auth', ipAddress: ip, userAgent: ua })\n return toVoid(result)\n },\n\n async crud(\n input: CrudAuditInput,\n ): Promise<HaiResult<void>> {\n const result = await logFn({\n userId: input.userId,\n action: input.action,\n resource: input.resource,\n resourceId: input.resourceId,\n details: input.details,\n ipAddress: input.ip,\n userAgent: input.ua,\n })\n return toVoid(result)\n },\n }\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"audit_notInitialized\": \"Audit module not initialized, call audit.init() first\",\n \"audit_initFailed\": \"Audit module initialization failed: {error}\",\n \"audit_logFailed\": \"Failed to record audit log: {error}\",\n \"audit_queryFailed\": \"Failed to query audit logs: {error}\",\n \"audit_cleanupFailed\": \"Failed to cleanup audit logs: {error}\",\n \"audit_statsFailed\": \"Failed to query audit statistics: {error}\",\n \"audit_invalidStatCount\": \"Audit count is outside the valid range and cannot be displayed safely\",\n \"audit_configError\": \"Audit config validation failed: {error}\",\n \"audit_initInProgress\": \"Audit module initialization is already in progress\",\n \"audit_invalidInput\": \"Invalid audit input for {field}: {reason}\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"audit_notInitialized\": \"監査モジュールが初期化されていません。先にaudit.init()を呼び出してください\",\n \"audit_initFailed\": \"監査モジュールの初期化に失敗しました: {error}\",\n \"audit_logFailed\": \"監査ログの記録に失敗しました: {error}\",\n \"audit_queryFailed\": \"監査ログの取得に失敗しました: {error}\",\n \"audit_cleanupFailed\": \"監査ログのクリーンアップに失敗しました: {error}\",\n \"audit_statsFailed\": \"監査統計の取得に失敗しました: {error}\",\n \"audit_invalidStatCount\": \"監査件数が有効範囲を超えているため、安全に表示できません\",\n \"audit_configError\": \"監査設定の検証に失敗しました: {error}\",\n \"audit_initInProgress\": \"監査モジュールの初期化は既に実行中です\",\n \"audit_invalidInput\": \"{field}の監査入力が無効です: {reason}\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"audit_notInitialized\": \"审计模块尚未初始化,请先调用 audit.init()\",\n \"audit_initFailed\": \"审计模块初始化失败:{error}\",\n \"audit_logFailed\": \"审计日志记录失败:{error}\",\n \"audit_queryFailed\": \"审计日志查询失败:{error}\",\n \"audit_cleanupFailed\": \"审计日志清理失败:{error}\",\n \"audit_statsFailed\": \"审计统计查询失败:{error}\",\n \"audit_invalidStatCount\": \"审计统计数量超出有效范围,无法安全显示\",\n \"audit_configError\": \"审计配置校验失败:{error}\",\n \"audit_initInProgress\": \"审计模块正在初始化中,请勿并发调用\",\n \"audit_invalidInput\": \"审计输入参数无效({field}):{reason}\"\n}\n","/**\n * @h-ai/audit — i18n\n *\n * 本文件提供审计模块的 i18n 文案访问入口。\n * @module audit-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesJaJP from '../messages/ja-JP.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n// ─── i18n ───\n\n/**\n * 审计模块文案 Key 类型\n * @example 模块内部上下文\n * ```ts\n * const key: AuditMessageKey = 'audit_notInitialized'\n * const message = auditM(key)\n * ```\n */\ntype AuditMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取审计模块文案。\n *\n * @param key - 文案 Key(以 `audit_` 为前缀)\n * @param options - 插值参数与格式化选项\n * @returns 本地化后的文案字符串\n * @remarks 具体参数结构与 core.i18n.createMessageGetter 保持一致。\n * @example 模块内部上下文\n * ```ts\n * const message = auditM('audit_logFailed', { locale: 'en-US', params: { error: 'database unavailable' } })\n * ```\n */\nexport const auditM = core.i18n.createMessageGetter<AuditMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n 'ja-JP': messagesJaJP,\n})\n","/**\n * @h-ai/audit — 公共类型定义\n *\n * 审计模块对外类型:错误接口、实体类型、操作接口、函数接口。\n * @module audit-types\n */\n\nimport type { ErrorInfo, HaiResult, PaginatedResult, PaginationOptionsInput } from '@h-ai/core'\nimport type { AuditInitConfigInput } from './audit-config.js'\nimport { core } from '@h-ai/core'\n\n// ─── 错误定义(照 @h-ai/core 范式) ───\n\nconst AuditErrorInfo = {\n LOG_FAILED: '001:500',\n QUERY_FAILED: '002:500',\n CLEANUP_FAILED: '003:500',\n STATS_FAILED: '004:500',\n INIT_IN_PROGRESS: '005:409',\n NOT_INITIALIZED: '010:500',\n CONFIG_ERROR: '012:500',\n} as const satisfies ErrorInfo\n\n/**\n * 审计初始化、写入、查询和清理失败的稳定错误定义。\n * @example\n * ```ts\n * import { HaiAuditError } from '@h-ai/audit'\n * import { err } from '@h-ai/core'\n * const failed = err(HaiAuditError.NOT_INITIALIZED, 'Audit is not initialized')\n * if (!failed.success) failed.error.code\n * ```\n */\nexport const HaiAuditError = core.error.buildHaiErrorsDef('audit', AuditErrorInfo)\n\n// ─── 实体类型 ───\n\n/**\n * 审计日志记录\n *\n * 表示一条已持久化的审计日志条目。\n * @example\n * ```ts\n * import type { AuditLog } from '@h-ai/audit'\n * const record: AuditLog = { id: 'audit_example', userId: 'user_1', action: 'create', resource: 'orders', resourceId: 'order_1', details: null, ipAddress: null, userAgent: null, createdAt: new Date() }\n * ```\n */\nexport interface AuditLog {\n /** 审计日志 ID,格式为 `audit_` 前缀 + 随机串 */\n id: string\n /** 操作用户 ID;系统操作时为 null */\n userId: string | null\n /** 操作类型(如 login / logout / create / update / delete) */\n action: string\n /** 资源类型(如 auth / users / roles) */\n resource: string\n /** 资源 ID;不涉及特定资源时为 null */\n resourceId: string | null\n /** 操作详情(JSON 字符串);无详情时为 null */\n details: string | null\n /** 客户端 IP 地址;未知时为 null */\n ipAddress: string | null\n /** 客户端 User-Agent;未知时为 null */\n userAgent: string | null\n /** 创建时间 */\n createdAt: Date\n}\n\n/**\n * 审计日志记录(含用户名)\n *\n * 通过 LEFT JOIN 用户表获取 username 字段,用于列表展示。\n * @example\n * ```ts\n * import type { AuditLogWithUser } from '@h-ai/audit'\n * const record: AuditLogWithUser = { id: 'audit_example', userId: 'user_1', action: 'create', resource: 'orders', resourceId: 'order_1', details: null, ipAddress: null, userAgent: null, createdAt: new Date() , username: 'alice' }\n * ```\n */\nexport interface AuditLogWithUser extends AuditLog {\n /** 操作用户的用户名;用户不存在或系统操作时为 null */\n username: string | null\n}\n\n/**\n * 创建审计日志输入\n *\n * 调用 {@link AuditFunctions.log} 时传入的参数。\n * 仅 `action` 和 `resource` 为必填。\n * @example\n * ```ts\n * import type { CreateAuditLogInput } from '@h-ai/audit'\n * const record: CreateAuditLogInput = { userId: 'user_1', action: 'create', resource: 'orders', resourceId: 'order_1', details: { status: 'pending' } }\n * ```\n */\nexport interface CreateAuditLogInput {\n /** 操作用户 ID;系统操作可省略 */\n userId?: string | null\n /** 操作类型(如 login / create / update / delete) */\n action: string\n /** 资源类型(如 auth / users / roles) */\n resource: string\n /** 资源 ID;不涉及特定资源时可省略 */\n resourceId?: string | null\n /** 操作详情对象;密码、token、secret 等凭证类字段会先自动脱敏,再 JSON 序列化存储 */\n details?: Record<string, unknown> | null\n /** 客户端 IP 地址 */\n ipAddress?: string | null\n /** 客户端 User-Agent */\n userAgent?: string | null\n}\n\n/**\n * 审计日志列表查询选项\n *\n * 所有字段均为可选,不传则不过滤。\n * @example\n * ```ts\n * import type { ListAuditLogsOptions } from '@h-ai/audit'\n * const record: ListAuditLogsOptions = { action: 'create', resource: 'orders', page: 1, pageSize: 20 }\n * ```\n */\nexport interface ListAuditLogsOptions extends PaginationOptionsInput {\n /** 按用户 ID 过滤 */\n userId?: string\n /** 按操作类型过滤(精确匹配) */\n action?: string\n /** 按资源类型过滤(精确匹配) */\n resource?: string\n /** 起始时间(含),只返回此时间之后的日志 */\n startDate?: Date\n /** 结束时间(含),只返回此时间之前的日志 */\n endDate?: Date\n}\n\n/**\n * 审计统计结果项\n *\n * 按操作类型(action)分组的统计条目。\n * @example\n * ```ts\n * import type { AuditStatItem } from '@h-ai/audit'\n * const record: AuditStatItem = { action: 'create', count: 12 }\n * ```\n */\nexport interface AuditStatItem {\n /** 操作类型 */\n action: string\n /** 该操作在统计时间范围内的次数 */\n count: number\n}\n\n/**\n * CRUD 审计操作输入\n *\n * 调用 {@link AuditHelper.crud} 时传入的参数。\n * @example\n * ```ts\n * import type { CrudAuditInput } from '@h-ai/audit'\n * const record: CrudAuditInput = { userId: 'user_1', action: 'update', resource: 'orders', resourceId: 'order_1', details: { status: 'paid' } }\n * ```\n */\nexport interface CrudAuditInput {\n /** 操作用户 ID(系统操作传 null) */\n userId?: string | null\n /** 操作类型 */\n action: 'create' | 'read' | 'update' | 'delete'\n /** 资源类型(如 users / roles) */\n resource: string\n /** 资源 ID(可选) */\n resourceId?: string\n /** 操作详情对象(可选) */\n details?: Record<string, unknown>\n /** 客户端 IP(可选) */\n ip?: string\n /** 客户端 User-Agent(可选) */\n ua?: string\n}\n\n// ─── 便捷操作接口 ───\n\n/**\n * 审计便捷记录器\n *\n * 封装常见审计场景(认证、CRUD),简化调用方代码。\n * 通过 {@link AuditFunctions.helper} 访问。\n * @example\n * ```ts\n * import type { AuditHelper } from '@h-ai/audit'\n * // helper 来自已初始化的 audit;数据库生命周期归调用方。\n * async function recordLogin(helper: AuditHelper) {\n * const result = await helper.login('user_1', '127.0.0.1')\n * return result.success ? 'recorded' : result.error.message\n * }\n * ```\n */\nexport interface AuditHelper {\n /**\n * 记录用户登录\n *\n * @param userId - 登录用户 ID\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n login: (userId: string, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录用户登出\n *\n * @param userId - 登出用户 ID\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n logout: (userId: string, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录用户注册\n *\n * @param userId - 注册用户 ID\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n register: (userId: string, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录密码重置请求\n *\n * @param email - 请求重置的邮箱地址\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n passwordResetRequest: (email: string, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录密码重置完成\n *\n * @param userId - 重置密码的用户 ID(匿名重置场景传 null)\n * @param ip - 客户端 IP(可选)\n * @param ua - 客户端 User-Agent(可选)\n */\n passwordResetComplete: (userId: string | null, ip?: string, ua?: string) => Promise<HaiResult<void>>\n /**\n * 记录 CRUD 操作\n *\n * @param input - CRUD 审计输入(action 和 resource 为必填)\n *\n * @example\n * ```ts\n * await audit.helper.crud({ userId: 'user_1', action: 'create', resource: 'users', resourceId: 'user_2' })\n * ```\n */\n crud: (input: CrudAuditInput) => Promise<HaiResult<void>>\n}\n\n// ─── 函数接口 ───\n\n/**\n * 审计模块函数接口\n *\n * 统一的审计日志访问入口。\n * @example\n * ```ts\n * import { audit } from '@h-ai/audit'\n * import { core } from '@h-ai/core'\n * import { reldb, type ReldbConfigInput } from '@h-ai/reldb'\n * async function recordCreation(config: ReldbConfigInput) {\n * const database = await reldb.init(config)\n * if (!database.success) return database\n * try {\n * const initialized = await audit.init()\n * if (!initialized.success) return initialized\n * const logged = await audit.log({ action: 'create', resource: 'orders', resourceId: 'order_1' })\n * if (logged.success) core.logger.info('Audit recorded', { id: logged.data.id })\n * return logged\n * } finally {\n * const auditClosed = await audit.close()\n * if (!auditClosed.success) return auditClosed // 保留依赖连接,修复后重试关闭。\n * const databaseClosed = await reldb.close()\n * if (!databaseClosed.success) return databaseClosed\n * }\n * }\n * ```\n */\nexport interface AuditFunctions {\n /**\n * 初始化审计模块\n *\n * @param config - 初始化配置(可选,所有字段均有默认值)\n * @returns 成功时返回 ok(undefined);失败时返回 CONFIG_ERROR\n */\n init: (config?: AuditInitConfigInput) => Promise<HaiResult<void>>\n /**\n * 关闭审计模块,释放内部状态\n */\n close: () => Promise<HaiResult<void>>\n /** 当前是否已初始化 */\n readonly isInitialized: boolean\n /**\n * 记录一条审计日志\n *\n * @param input - 日志内容(action 和 resource 为必填)\n * @returns 成功时返回创建的 AuditLog;失败时返回 LOG_FAILED\n */\n log: (input: CreateAuditLogInput) => Promise<HaiResult<AuditLog>>\n /**\n * 分页查询审计日志列表(含用户名 LEFT JOIN)\n *\n * @param options - 查询过滤与分页选项\n * @returns 成功时返回 PaginatedResult(items/total/page/pageSize);失败时返回 QUERY_FAILED\n */\n list: (options?: ListAuditLogsOptions) => Promise<HaiResult<PaginatedResult<AuditLogWithUser>>>\n /**\n * 获取指定用户的最近活动记录\n *\n * @param userId - 用户 ID\n * @param limit - 最大返回条数,默认 10\n * @returns 成功时返回 AuditLog 数组;失败时返回 QUERY_FAILED\n */\n getUserRecent: (userId: string, limit?: number) => Promise<HaiResult<AuditLog[]>>\n /**\n * 清理指定天数之前的旧日志\n *\n * @param olderThanDays - 保留天数,默认 90;清理此天数之前的日志\n * @returns 成功时返回删除的记录数;失败时返回 CLEANUP_FAILED\n */\n cleanup: (olderThanDays?: number) => Promise<HaiResult<number>>\n /**\n * 获取指定天数内的操作统计(按 action 分组计数)\n *\n * @param days - 统计天数,默认 7\n * @returns 成功时返回 AuditStatItem 数组;失败时返回 STATS_FAILED\n */\n getStats: (days?: number) => Promise<HaiResult<AuditStatItem[]>>\n /**\n * 便捷记录器,封装常见审计场景\n *\n * 未初始化时调用任意方法均返回 NOT_INITIALIZED 错误。\n */\n readonly helper: AuditHelper\n}\n","/**\n * @h-ai/audit — 审计日志仓库\n *\n * 基于 @h-ai/reldb BaseReldbCrudRepository 实现审计日志的持久化与查询。 仅在需要 JOIN / 聚合时使用自定义 SQL,其余 CRUD 操作由基类处理。\n * @module audit-repository\n */\n\nimport type { HaiResult, PaginatedResult } from '@h-ai/core'\nimport type { AuditLog, AuditLogWithUser, AuditStatItem, ListAuditLogsOptions } from './audit-types.js'\nimport { core, err, ok } from '@h-ai/core'\nimport { BaseReldbCrudRepository, reldb } from '@h-ai/reldb'\nimport { auditM } from './audit-i18n.js'\nimport { HaiAuditError } from './audit-types.js'\n\nconst logger = core.logger.child({ module: 'audit', scope: 'repository' })\n\n// ─── 仓库配置 ───\n\n/** 审计日志表名(固定值) */\nconst AUDIT_TABLE = 'hai_audit_logs'\n\n/**\n * 原始 SQL 与基类映射的日期、JSON 返回值可能不同,在审计边界统一。\n * @example 模块内部上下文\n * ```ts\n * const row: AuditReadRow = { id: 'audit_example', userId: null, action: 'login', resource: 'auth', resourceId: null, details: { source: 'web' }, ipAddress: null, userAgent: null, createdAt: Date.now() }\n * const record = normalizeAuditLog(row)\n * ```\n */\ntype AuditReadRow = Omit<AuditLog, 'details' | 'createdAt'> & {\n details: unknown\n createdAt: Date | string | number\n}\n\n/** 保持公开审计契约:Date、JSON 字符串和显式 null。 */\nfunction normalizeAuditLog(row: AuditReadRow): AuditLog {\n return {\n ...row,\n userId: row.userId ?? null,\n resourceId: row.resourceId ?? null,\n details: row.details == null ? null : typeof row.details === 'string' ? row.details : JSON.stringify(row.details) ?? null,\n ipAddress: row.ipAddress ?? null,\n userAgent: row.userAgent ?? null,\n createdAt: row.createdAt instanceof Date ? row.createdAt : new Date(row.createdAt),\n }\n}\n\n/**\n * 审计仓储内部的用户表关联映射;由已校验的初始化配置构造。\n * @example 模块内部上下文\n * ```ts\n * const config: AuditRepositoryConfig = { userTable: 'app_users', userIdColumn: 'id', userNameColumn: 'username' }\n * const userTable = config.userTable\n * ```\n */\ninterface AuditRepositoryConfig {\n /** 用户表名 */\n userTable: string\n /** 用户表主键列名 */\n userIdColumn: string\n /** 用户表用户名列名 */\n userNameColumn: string\n}\n\n// ─── 仓库 ───\n\n/**\n * 审计日志仓库\n *\n * 通过 BaseReldbCrudRepository 自动建表、字段映射与类型转换,\n * 仅在需要 JOIN / 聚合时使用自定义 SQL。\n *\n * @remarks 此类仅供 audit-main.ts 内部使用,不通过 index.ts 对外导出。\n * @example 模块内部上下文\n * ```ts\n * // 共享 reldb 已初始化,连接生命周期由创建者管理。\n * const repository = new AuditLogRepository({ userTable: 'app_users', userIdColumn: 'id', userNameColumn: 'username' })\n * const ready = await repository.ready()\n * if (ready.success) {\n * const stats = await repository.getStats(7)\n * if (stats.success) stats.data.map(item => item.count)\n * }\n * ```\n */\nexport class AuditLogRepository extends BaseReldbCrudRepository<AuditLog> {\n private readonly repoConfig: AuditRepositoryConfig\n private readonly isSqlite: boolean\n\n /**\n * @param config - 仓库配置(用户表映射)\n */\n constructor(config: AuditRepositoryConfig) {\n super(reldb, {\n table: AUDIT_TABLE,\n idColumn: 'id',\n generateId: () => core.id.withPrefix('audit_'),\n fields: [\n { fieldName: 'id', columnName: 'id', def: { type: 'TEXT', primaryKey: true }, select: true, create: true, update: false },\n { fieldName: 'userId', columnName: 'user_id', def: { type: 'TEXT' }, select: true, create: true, update: false },\n { fieldName: 'action', columnName: 'action', def: { type: 'TEXT', notNull: true }, select: true, create: true, update: false },\n { fieldName: 'resource', columnName: 'resource', def: { type: 'TEXT', notNull: true }, select: true, create: true, update: false },\n { fieldName: 'resourceId', columnName: 'resource_id', def: { type: 'TEXT' }, select: true, create: true, update: false },\n { fieldName: 'details', columnName: 'details', def: { type: 'JSON' }, select: true, create: true, update: false },\n { fieldName: 'ipAddress', columnName: 'ip_address', def: { type: 'TEXT' }, select: true, create: true, update: false },\n { fieldName: 'userAgent', columnName: 'user_agent', def: { type: 'TEXT' }, select: true, create: true, update: false },\n { fieldName: 'createdAt', columnName: 'created_at', def: { type: 'TIMESTAMP' }, select: true, create: true, update: false },\n ],\n })\n this.repoConfig = config\n this.isSqlite = this.db.config?.type === 'sqlite'\n }\n\n /**\n * 等待基类完成建表及字段校验;init和自定义SQL共享此屏障,失败不发布仓库。\n * @returns 建表结果,失败由调用方映射到对应审计错误\n * @example const ready = await repository.ready(); if (!ready.success) return ready\n */\n ready(): Promise<HaiResult<void>> {\n return this.ensureReady()\n }\n\n /**\n * 将 Date 转为适合当前数据库类型的 SQL 参数\n *\n * 与基类写入保持一致:SQLite 使用毫秒,其他数据库由驱动转换 Date。\n *\n * @param date - 要转换的日期\n * @returns SQLite 返回毫秒时间戳,其他返回 Date\n */\n private toDateParam(date: Date): number | Date {\n return this.isSqlite ? date.getTime() : date\n }\n\n /**\n * 记录一条审计日志\n *\n * @param input - 日志内容\n * @param input.userId - 操作用户 ID(系统操作可省略)\n * @param input.action - 操作类型(如 login / create)\n * @param input.resource - 资源类型(如 auth / users)\n * @param input.resourceId - 资源 ID(可选)\n * @param input.details - 操作详情对象(可选)\n * @param input.ipAddress - 客户端 IP(可选)\n * @param input.userAgent - 客户端 User-Agent(可选)\n * @returns 成功时返回创建的 AuditLog;失败时返回 LOG_FAILED\n */\n async log(input: {\n userId?: string | null\n action: string\n resource: string\n resourceId?: string | null\n details?: Record<string, unknown> | null\n ipAddress?: string | null\n userAgent?: string | null\n }): Promise<HaiResult<AuditLog>> {\n logger.debug('Recording audit log', { action: input.action, resource: input.resource })\n\n const id = core.id.withPrefix('audit_')\n const now = new Date()\n const data: Record<string, unknown> = {\n id,\n userId: input.userId ?? null,\n action: input.action,\n resource: input.resource,\n resourceId: input.resourceId ?? null,\n details: input.details ?? null,\n ipAddress: input.ipAddress ?? null,\n userAgent: input.userAgent ?? null,\n createdAt: now,\n }\n\n try {\n const createResult = await this.create(data)\n if (!createResult.success) {\n logger.error('Failed to record audit log', { action: input.action, error: createResult.error.message })\n return err(\n HaiAuditError.LOG_FAILED,\n auditM('audit_logFailed', { params: { error: createResult.error.message } }),\n createResult.error,\n )\n }\n\n const auditLog: AuditLog = {\n id,\n userId: input.userId ?? null,\n action: input.action,\n resource: input.resource,\n resourceId: input.resourceId ?? null,\n details: input.details ? JSON.stringify(input.details) : null,\n ipAddress: input.ipAddress ?? null,\n userAgent: input.userAgent ?? null,\n createdAt: now,\n }\n\n logger.info('Audit log recorded', { id, action: input.action, resource: input.resource })\n return ok(auditLog)\n }\n catch (error) {\n logger.error('Failed to record audit log', { action: input.action, error })\n return err(\n HaiAuditError.LOG_FAILED,\n auditM('audit_logFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n\n /**\n * 分页查询日志列表(含用户名 LEFT JOIN)\n *\n * @param options - 过滤条件与分页参数\n * @returns 成功时返回 PaginatedResult(items/total/page/pageSize);失败时返回 QUERY_FAILED\n */\n async listWithUser(options: ListAuditLogsOptions = {}): Promise<HaiResult<PaginatedResult<AuditLogWithUser>>> {\n const ready = await this.ready()\n if (!ready.success)\n return ready\n logger.debug('Querying audit logs', { userId: options.userId, action: options.action, resource: options.resource })\n\n const conditions: string[] = []\n const params: unknown[] = []\n const { userTable, userIdColumn, userNameColumn } = this.repoConfig\n // 配置标识符已由 init 校验;别名加引号防止 PostgreSQL 折叠大小写。\n const quote = this.db.config?.type === 'mysql' ? '`' : '\"'\n const identifier = (name: string) => `${quote}${name.replaceAll(quote, quote + quote)}${quote}`\n\n if (options.userId) {\n conditions.push('a.user_id = ?')\n params.push(options.userId)\n }\n if (options.action) {\n conditions.push('a.action = ?')\n params.push(options.action)\n }\n if (options.resource) {\n conditions.push('a.resource = ?')\n params.push(options.resource)\n }\n if (options.startDate) {\n conditions.push('a.created_at >= ?')\n params.push(this.toDateParam(options.startDate))\n }\n if (options.endDate) {\n conditions.push('a.created_at <= ?')\n params.push(this.toDateParam(options.endDate))\n }\n\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n\n try {\n const result = await this.sql().queryPage<AuditReadRow & { username: string | null }>({\n sql: `SELECT a.id, a.user_id AS ${identifier('userId')}, a.action, a.resource, a.resource_id AS ${identifier('resourceId')},\n a.details, a.ip_address AS ${identifier('ipAddress')}, a.user_agent AS ${identifier('userAgent')},\n a.created_at AS ${identifier('createdAt')}, u.${identifier(userNameColumn)} AS username\n FROM ${AUDIT_TABLE} a\n LEFT JOIN ${identifier(userTable)} u ON a.user_id = u.${identifier(userIdColumn)}\n ${whereClause}\n ORDER BY a.created_at DESC`,\n params,\n pagination: { page: options.page, pageSize: options.pageSize },\n overrides: { defaultPageSize: 20 },\n })\n\n if (!result.success) {\n logger.error('Failed to query audit logs', { error: result.error.message })\n return err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_queryFailed', { params: { error: result.error.message } }),\n result.error,\n )\n }\n\n return ok({ ...result.data, items: result.data.items.map(row => ({ ...normalizeAuditLog(row), username: row.username ?? null })) })\n }\n catch (error) {\n logger.error('Failed to query audit logs', { error })\n return err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_queryFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n\n /**\n * 获取指定用户的最近活动\n *\n * @param userId - 用户 ID\n * @param limit - 最大返回条数,默认 10\n * @returns 成功时返回 AuditLog 数组(按时间倒序);失败时返回 QUERY_FAILED\n */\n async getUserRecent(userId: string, limit = 10): Promise<HaiResult<AuditLog[]>> {\n logger.debug('Getting user recent activity', { userId, limit })\n\n try {\n const result = await this.findAll({\n where: 'user_id = ?',\n params: [userId],\n orderBy: 'created_at DESC',\n limit,\n })\n\n if (!result.success) {\n return err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_queryFailed', { params: { error: result.error.message } }),\n result.error,\n )\n }\n\n return ok(result.data.map(normalizeAuditLog))\n }\n catch (error) {\n return err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_queryFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n\n /**\n * 清理指定天数之前的旧日志\n *\n * @param olderThanDays - 保留天数,默认 90;清理此天数之前的日志\n * @returns 成功时返回删除的记录数;失败时返回 CLEANUP_FAILED\n */\n async cleanupOld(olderThanDays = 90): Promise<HaiResult<number>> {\n const ready = await this.ready()\n if (!ready.success)\n return ready\n logger.debug('Cleaning up old audit logs', { olderThanDays })\n\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - olderThanDays)\n\n try {\n const result = await this.sql().execute(\n `DELETE FROM ${AUDIT_TABLE} WHERE created_at < ?`,\n [this.toDateParam(cutoff)],\n )\n\n if (!result.success) {\n logger.error('Failed to cleanup audit logs', { error: result.error.message })\n return err(\n HaiAuditError.CLEANUP_FAILED,\n auditM('audit_cleanupFailed', { params: { error: result.error.message } }),\n result.error,\n )\n }\n\n const deleted = result.data.changes\n logger.info('Audit logs cleaned up', { olderThanDays, deleted })\n return ok(deleted)\n }\n catch (error) {\n logger.error('Failed to cleanup audit logs', { error })\n return err(\n HaiAuditError.CLEANUP_FAILED,\n auditM('audit_cleanupFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n\n /**\n * 获取指定天数内的操作统计(按 action 分组计数)\n *\n * @param days - 统计天数,默认 7\n * @returns 成功时返回 AuditStatItem 数组(按 count 倒序);失败时返回 STATS_FAILED\n */\n async getStats(days = 7): Promise<HaiResult<AuditStatItem[]>> {\n const ready = await this.ready()\n if (!ready.success)\n return ready\n logger.debug('Getting audit statistics', { days })\n\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - days)\n\n try {\n const result = await this.sql().query<{ action: string, count: number | string | bigint }>(\n `SELECT action, COUNT(*) as count\n FROM ${AUDIT_TABLE}\n WHERE created_at >= ?\n GROUP BY action\n ORDER BY count DESC`,\n [this.toDateParam(cutoff)],\n )\n\n if (!result.success) {\n logger.error('Failed to query audit statistics', { error: result.error.message })\n return err(\n HaiAuditError.STATS_FAILED,\n auditM('audit_statsFailed', { params: { error: result.error.message } }),\n result.error,\n )\n }\n\n const statistics = result.data.map(item => ({ action: item.action, count: Number(item.count) }))\n if (statistics.some(item => !Number.isSafeInteger(item.count) || item.count < 0))\n return err(HaiAuditError.STATS_FAILED, auditM('audit_invalidStatCount'))\n return ok(statistics)\n }\n catch (error) {\n logger.error('Failed to query audit statistics', { error })\n return err(\n HaiAuditError.STATS_FAILED,\n auditM('audit_statsFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n }\n}\n","/**\n * @h-ai/audit — 审计日志服务主入口\n *\n * 本文件提供统一的 `audit` 对象,聚合所有审计日志操作功能。\n * @module audit-main\n */\n\nimport type { HaiResult, PaginatedResult } from '@h-ai/core'\nimport type { AuditInitConfigInput } from './audit-config.js'\nimport type {\n AuditFunctions,\n AuditHelper,\n AuditLog,\n AuditLogWithUser,\n AuditStatItem,\n CreateAuditLogInput,\n ListAuditLogsOptions,\n} from './audit-types.js'\nimport { core, err, ok } from '@h-ai/core'\nimport { validateIdentifiers } from '@h-ai/reldb'\nimport { AuditInitConfigSchema } from './audit-config.js'\nimport { createHelper } from './audit-helper.js'\n\nimport { auditM } from './audit-i18n.js'\nimport { AuditLogRepository } from './audit-repository-log.js'\nimport {\n HaiAuditError,\n} from './audit-types.js'\n\nconst logger = core.logger.child({ module: 'audit', scope: 'main' })\n\n// ─── 输入校验 ───\n\n/** 字符串字段最大长度(防止异常大输入) */\nconst MAX_TEXT_LENGTH = 256\n\n/** 审计详情中的敏感字段统一脱敏为此占位值 */\nconst REDACTED_AUDIT_DETAIL_VALUE = '[REDACTED]'\n\n/**\n * 判断值是否为非空字符串\n *\n * @param value - 待校验值\n */\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\n/**\n * 校验字符串长度是否在限制内\n *\n * @param value - 待校验字符串\n * @param maxLength - 允许最大长度\n */\nfunction isValidTextLength(value: string, maxLength = MAX_TEXT_LENGTH): boolean {\n return value.length <= maxLength\n}\n\n/**\n * 对审计详情执行统一脱敏\n *\n * @param details - 原始详情对象\n */\nfunction sanitizeAuditDetails(details?: Record<string, unknown> | null): Record<string, unknown> | null {\n if (!details) {\n return details ?? null\n }\n\n return core.sanitize.sanitizeSensitiveFields(details, {\n matcher: core.sanitize.defaultSensitiveKeyRegex,\n replacement: REDACTED_AUDIT_DETAIL_VALUE,\n }) as Record<string, unknown>\n}\n\n// ─── 内部状态 ───\n\n/** 当前审计日志仓库实例;init 后赋值,close 后置 null */\nlet currentRepo: AuditLogRepository | null = null\n\n/** 缓存的便捷记录器实例;init 时创建,close 时置 null */\nlet currentHelper: AuditHelper | null = null\n\n/** 并发初始化防护标志 */\nlet initInProgress = false\n\n/** close使尚未完成DDL的初始化失效,防止迟到发布。 */\nlet lifecycleGeneration = 0\n\n// ─── 未初始化占位 ───\n\n/**\n * 未初始化时的错误工具集\n *\n * 调用 audit.log / audit.list 等方法时,\n * 若模块未初始化则返回 { success: false, error: { code: NOT_INITIALIZED } }\n */\nconst notInitialized = core.module.createNotInitializedKit(\n HaiAuditError.NOT_INITIALIZED,\n () => auditM('audit_notInitialized'),\n)\n\n// ─── 便捷记录器占位 ───\n\n/** 未初始化时的便捷记录器占位 */\nconst notInitializedHelper = notInitialized.proxy<AuditHelper>()\n\n// ─── 审计服务对象 ───\n\n/**\n * 审计日志服务对象\n *\n * 统一的审计日志访问入口,提供以下功能:\n * - `audit.init()` — 初始化审计模块\n * - `audit.close()` — 关闭模块\n * - `audit.log()` — 记录审计日志\n * - `audit.list()` — 查询审计日志列表\n * - `audit.getUserRecent()` — 获取用户最近活动\n * - `audit.cleanup()` — 清理旧日志\n * - `audit.getStats()` — 获取统计数据\n * - `audit.helper` — 便捷记录器\n * - `audit.isInitialized` — 初始化状态\n * @example\n * ```ts\n * import { audit } from '@h-ai/audit'\n * import { core } from '@h-ai/core'\n * import { reldb, type ReldbConfigInput } from '@h-ai/reldb'\n * async function recordCreation(config: ReldbConfigInput) {\n * const database = await reldb.init(config)\n * if (!database.success) return database\n * try {\n * const initialized = await audit.init()\n * if (!initialized.success) return initialized\n * const logged = await audit.log({ action: 'create', resource: 'orders', resourceId: 'order_1' })\n * if (logged.success) core.logger.info('Audit recorded', { id: logged.data.id })\n * return logged\n * } finally {\n * const auditClosed = await audit.close()\n * if (!auditClosed.success) return auditClosed // 保留依赖连接,修复后重试关闭。\n * const databaseClosed = await reldb.close()\n * if (!databaseClosed.success) return databaseClosed\n * }\n * }\n * ```\n */\nexport const audit: AuditFunctions = {\n /**\n * 初始化审计模块\n *\n * 会先关闭已有实例(如已初始化),再用新配置重新初始化。\n * 等待 AuditLogRepository / BaseReldbCrudRepository 完成建表后才发布实例。\n * 依赖 @h-ai/reldb 已初始化。\n *\n * @param config - 初始化配置(可选,所有字段均有默认值)\n * @returns 成功时返回 ok(undefined);失败时返回 CONFIG_ERROR\n *\n * @example\n * ```ts\n * const result = await audit.init()\n * if (!result.success) {\n * logger.error('Audit init failed', { error: result.error.message })\n * }\n * ```\n */\n async init(config?: AuditInitConfigInput): Promise<HaiResult<void>> {\n // 并发初始化防护:避免多次 init 同时执行导致资源泄漏\n if (initInProgress) {\n logger.warn('Audit init already in progress, skipping concurrent call')\n return err(\n HaiAuditError.INIT_IN_PROGRESS,\n auditM('audit_initInProgress'),\n )\n }\n initInProgress = true\n const generation = ++lifecycleGeneration\n\n try {\n if (currentRepo) {\n logger.warn('Audit module is already initialized, reinitializing')\n currentRepo = null\n currentHelper = null\n }\n\n logger.info('Initializing audit module')\n\n const parseResult = AuditInitConfigSchema.safeParse(config ?? {})\n if (!parseResult.success) {\n logger.error('Audit config validation failed', { error: parseResult.error.message })\n return err(\n HaiAuditError.CONFIG_ERROR,\n auditM('audit_configError', { params: { error: parseResult.error.message } }),\n parseResult.error,\n )\n }\n const parsed = parseResult.data\n\n const identifierResult = validateIdentifiers([parsed.userTable, parsed.userIdColumn, parsed.userNameColumn])\n if (!identifierResult.success) {\n logger.error('Audit config contains invalid identifiers', { error: identifierResult.error.message })\n return err(\n HaiAuditError.CONFIG_ERROR,\n auditM('audit_configError', { params: { error: identifierResult.error.message } }),\n identifierResult.error,\n )\n }\n\n const candidate = new AuditLogRepository({\n userTable: parsed.userTable,\n userIdColumn: parsed.userIdColumn,\n userNameColumn: parsed.userNameColumn,\n })\n const ready = await candidate.ready()\n if (!ready.success) {\n return err(HaiAuditError.CONFIG_ERROR, auditM('audit_initFailed', { params: { error: ready.error.message } }), ready.error)\n }\n if (generation !== lifecycleGeneration)\n return err(HaiAuditError.NOT_INITIALIZED, auditM('audit_notInitialized'))\n // 候选仓库只拥有schema就绪Promise,不拥有共享reldb连接;成功屏障后才发布。\n currentRepo = candidate\n currentHelper = createHelper(input => audit.log(input))\n\n logger.info('Audit module initialized')\n return ok(undefined)\n }\n catch (error) {\n logger.error('Audit module initialization failed', { error })\n return err(\n HaiAuditError.CONFIG_ERROR,\n auditM('audit_initFailed', { params: { error: error instanceof Error ? error.message : String(error) } }),\n error,\n )\n }\n finally {\n initInProgress = false\n }\n },\n\n /** 当前是否已初始化 */\n get isInitialized(): boolean {\n return currentRepo !== null\n },\n\n /**\n * 记录一条审计日志\n *\n * @param input - 日志内容(action 和 resource 为必填)\n * @returns 成功时返回创建的 AuditLog;未初始化时返回 NOT_INITIALIZED\n */\n log(input: CreateAuditLogInput): Promise<HaiResult<AuditLog>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n const sanitizedInput: CreateAuditLogInput = {\n ...input,\n details: sanitizeAuditDetails(input.details),\n }\n\n if (!isNonEmptyString(sanitizedInput.action) || !isNonEmptyString(sanitizedInput.resource)) {\n return Promise.resolve(err(\n HaiAuditError.LOG_FAILED,\n auditM('audit_invalidInput', { params: { field: 'action/resource', reason: 'must be non-empty string' } }),\n ))\n }\n\n if (!isValidTextLength(sanitizedInput.action) || !isValidTextLength(sanitizedInput.resource)) {\n return Promise.resolve(err(\n HaiAuditError.LOG_FAILED,\n auditM('audit_invalidInput', { params: { field: 'action/resource', reason: 'exceeds max length' } }),\n ))\n }\n\n return currentRepo.log(sanitizedInput)\n },\n\n /**\n * 分页查询审计日志列表(含用户名 LEFT JOIN)\n *\n * @param options - 过滤条件与分页参数\n * @returns 成功时返回 PaginatedResult(items/total/page/pageSize);未初始化时返回 NOT_INITIALIZED\n */\n list(options?: ListAuditLogsOptions): Promise<HaiResult<PaginatedResult<AuditLogWithUser>>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n if (options?.startDate && options?.endDate && options.startDate > options.endDate) {\n return Promise.resolve(err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_invalidInput', { params: { field: 'dateRange', reason: 'startDate must be before endDate' } }),\n ))\n }\n\n return currentRepo.listWithUser(options)\n },\n\n /**\n * 获取指定用户的最近活动记录\n *\n * @param userId - 用户 ID\n * @param limit - 最大返回条数,默认 10\n * @returns 成功时返回 AuditLog 数组;未初始化时返回 NOT_INITIALIZED\n */\n getUserRecent(userId: string, limit?: number): Promise<HaiResult<AuditLog[]>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n if (!isNonEmptyString(userId)) {\n return Promise.resolve(err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_invalidInput', { params: { field: 'userId', reason: 'must be non-empty string' } }),\n ))\n }\n\n if (typeof limit === 'number' && (!Number.isInteger(limit) || limit <= 0)) {\n return Promise.resolve(err(\n HaiAuditError.QUERY_FAILED,\n auditM('audit_invalidInput', { params: { field: 'limit', reason: 'must be a positive integer' } }),\n ))\n }\n\n return currentRepo.getUserRecent(userId, limit)\n },\n\n /**\n * 清理指定天数之前的旧日志\n *\n * @param olderThanDays - 保留天数,默认 90\n * @returns 成功时返回删除的记录数;未初始化时返回 NOT_INITIALIZED\n */\n cleanup(olderThanDays?: number): Promise<HaiResult<number>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n if (typeof olderThanDays === 'number' && (!Number.isInteger(olderThanDays) || olderThanDays < 0)) {\n return Promise.resolve(err(\n HaiAuditError.CLEANUP_FAILED,\n auditM('audit_invalidInput', { params: { field: 'olderThanDays', reason: 'must be a non-negative integer' } }),\n ))\n }\n\n return currentRepo.cleanupOld(olderThanDays)\n },\n\n /**\n * 获取指定天数内的操作统计(按 action 分组计数)\n *\n * @param days - 统计天数,默认 7\n * @returns 成功时返回 AuditStatItem 数组;未初始化时返回 NOT_INITIALIZED\n */\n getStats(days?: number): Promise<HaiResult<AuditStatItem[]>> {\n if (!currentRepo) {\n return Promise.resolve(notInitialized.result())\n }\n\n if (typeof days === 'number' && (!Number.isInteger(days) || days < 0)) {\n return Promise.resolve(err(\n HaiAuditError.STATS_FAILED,\n auditM('audit_invalidInput', { params: { field: 'days', reason: 'must be a non-negative integer' } }),\n ))\n }\n\n return currentRepo.getStats(days)\n },\n\n /**\n * 便捷记录器\n *\n * 未初始化时调用任意方法均返回 NOT_INITIALIZED 错误。\n */\n get helper(): AuditHelper {\n if (!currentHelper) {\n return notInitializedHelper\n }\n return currentHelper\n },\n\n /**\n * 关闭审计模块,释放内部状态\n */\n async close(): Promise<HaiResult<void>> {\n lifecycleGeneration++\n if (!currentRepo) {\n logger.info('Audit module already closed, skipping')\n return ok(undefined)\n }\n\n logger.info('Closing audit module')\n currentRepo = null\n currentHelper = null\n logger.info('Audit module closed')\n return ok(undefined)\n },\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/audit",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.67",
4
+ "version": "0.1.0-alpha.69",
5
5
  "description": "Hai Framework audit logging module.",
6
6
  "license": "Apache-2.0",
7
7
  "exports": {
@@ -20,15 +20,16 @@
20
20
  "access": "public"
21
21
  },
22
22
  "engines": {
23
- "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
23
+ "node": "^22.23.4 || ^24.22.1 || ^26.12.1"
24
24
  },
25
25
  "dependencies": {
26
- "@h-ai/core": "0.1.0-alpha.67",
27
- "@h-ai/reldb": "0.1.0-alpha.67"
26
+ "@h-ai/core": "0.1.0-alpha.69",
27
+ "@h-ai/reldb": "0.1.0-alpha.69"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.6.5",
31
31
  "rimraf": "^6.1.3",
32
+ "testcontainers": "^12.2.0",
32
33
  "tsup": "^8.5.1",
33
34
  "typescript": "^6.0.3",
34
35
  "vitest": "^4.1.11"