@h-ai/core 1.0.0-beta.17 → 1.0.0-beta.19

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
@@ -93,7 +93,9 @@ The startup boundary stops when required configuration is unavailable. `core.ini
93
93
  import { core } from '@h-ai/core'
94
94
 
95
95
  // 浏览器不读取 YAML;此处只设置日志,输出 warn 及以上级别。
96
- core.init({ logging: { level: 'warn' } }) // 返回 undefined;之后 info 日志被过滤。
96
+ core.init({
97
+ logging: { level: 'warn' }
98
+ }) // 返回 undefined;之后 info 日志被过滤。
97
99
 
98
100
  // 设置当前页面使用的默认消息语言。
99
101
  core.i18n.setGlobalLocale('zh-CN') // 返回 undefined;页面默认消息使用中文。
@@ -131,7 +133,9 @@ import { core } from '@h-ai/core'
131
133
  import { z } from 'zod'
132
134
 
133
135
  // 读取上方 config/app.yml;Schema 同时描述类型和校验规则。
134
- const AppSchema = z.object({ port: z.number().int().min(1).max(65535) })
136
+ const AppSchema = z.object({
137
+ port: z.number().int().min(1).max(65535)
138
+ })
135
139
  const loaded = core.config.load('app', './config/app.yml', AppSchema)
136
140
  if (!loaded.success)
137
141
  throw new Error(loaded.error.message, { cause: loaded.error })
@@ -230,11 +234,25 @@ import { core } from '@h-ai/core'
230
234
  import { z } from 'zod'
231
235
 
232
236
  // 用显式环境数据演示覆盖顺序、数值/布尔转换与字符串保留。
233
- const AppSchema = z.object({ port: z.number(), enabled: z.boolean(), label: z.string(), code: z.string() })
237
+ const AppSchema = z.object({
238
+ port: z.number(),
239
+ enabled: z.boolean(),
240
+ label: z.string(),
241
+ code: z.string()
242
+ })
234
243
  const resolved = core.config.resolve({
235
244
  name: 'app',
236
- // eslint-disable-next-line no-template-curly-in-string -- 保留 YAML 中等待框架解析的字面表达式。
237
- value: { port: '${PORT:3000}', enabled: '${ENABLED:false}', label: 'server-${PORT:3000}', code: '${APP_CODE:00123}' },
245
+
246
+ value: {
247
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
248
+ port: '${PORT:3000}',
249
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
250
+ enabled: '${ENABLED:false}',
251
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
252
+ label: 'server-${PORT:3000}',
253
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
254
+ code: '${APP_CODE:00123}'
255
+ },
238
256
  environment: { PORT: '4000', HAI_APP_PORT: '5000' },
239
257
  schema: AppSchema,
240
258
  })
@@ -265,9 +283,18 @@ resolve() also lets build/deploy tools calculate settings in memory. toEnv() con
265
283
  import { core } from '@h-ai/core'
266
284
 
267
285
  // 配置生成器把普通对象转成 HAI_APP_PORT 等变量;数组保留为 JSON。
268
- const values = core.config.toEnv({ prefix: 'HAI_APP', value: { port: 3000, tags: ['local'] } })
286
+ const values = core.config.toEnv({
287
+ prefix: 'HAI_APP',
288
+ value: {
289
+ port: 3000,
290
+ tags: ['local']
291
+ }
292
+ })
269
293
  // values:{ HAI_APP_PORT: '3000', HAI_APP_TAGS: '["local"]' }。
270
- const merged = core.config.mergeEnv([values, { HAI_APP_PORT: '4000' }])
294
+ const merged = core.config.mergeEnv([
295
+ values,
296
+ { HAI_APP_PORT: '4000' }
297
+ ])
271
298
  // merged:{ HAI_APP_PORT: '4000', HAI_APP_TAGS: '["local"]' }。
272
299
  const text = core.config.stringifyEnv(merged) // 编码为可写入 .env 的文本。
273
300
  // text 含以下两行,每行以换行符结尾:
@@ -295,7 +322,9 @@ core.logger.trace('Processing detail', { step: 1 })
295
322
  core.logger.debug('Read started')
296
323
  core.logger.info('Order created', { orderId: 'order-1' })
297
324
  core.logger.warn('Retry scheduled')
298
- core.logger.error('Operation failed', { error: new Error('Example failure') })
325
+ core.logger.error('Operation failed', {
326
+ error: new Error('Example failure')
327
+ })
299
328
  core.logger.fatal('Service cannot continue')
300
329
  // 六次日志调用均返回 undefined;分别输出对应级别、上面的消息及所传字段。
301
330
  // 例如 info 日志的消息为 'Order created',字段 orderId 为 'order-1'。
@@ -307,10 +336,16 @@ create() makes a named logger; child() attaches fixed context. configure() sets
307
336
  import { core } from '@h-ai/core'
308
337
 
309
338
  // 全局配置可作用于已经创建的 Logger;redact 补充业务隐私字段。
310
- core.logger.configure({ level: 'info', format: 'json', redact: ['customer.email'] }) // 返回 undefined;后续日志按此配置输出。
339
+ core.logger.configure({
340
+ level: 'info',
341
+ format: 'json',
342
+ redact: ['customer.email']
343
+ }) // 返回 undefined;后续日志按此配置输出。
311
344
  const orders = core.logger.create({ name: 'orders' }) // 返回 Logger 对象,可调用 info/warn 等方法。
312
345
  const request = core.logger.child({ requestId: 'request-1' }) // 返回带固定 requestId 的 Logger。
313
- orders.info('Order created', { customer: { email: 'user@example.com' } }) // 返回 undefined;输出中 customer.email 为 '[REDACTED]'。
346
+ orders.info('Order created', {
347
+ customer: { email: 'user@example.com' }
348
+ }) // 返回 undefined;输出中 customer.email 为 '[REDACTED]'。
314
349
  request.warn('Request needs attention') // 返回 undefined;输出中 requestId 为 'request-1'。
315
350
  core.logger.getLevel() // 'info'。
316
351
  ```
@@ -370,7 +405,10 @@ core.sanitize.sanitizeSensitiveValue('example-only', 'password') // '[REDACTED]'
370
405
  const safe = core.sanitize.sanitizeSensitiveFields({ password: 'example-only', email: 'user@example.com' })
371
406
  // safe:{ password: '[REDACTED]', email: 'user@example.com' },原对象不变。
372
407
  // 默认仅遮蔽凭据;额外业务隐私可在已处理凭据的副本上继续清理。
373
- core.sanitize.sanitizeSensitiveFields(safe, { matcher: ['email'], replacement: '***' })
408
+ core.sanitize.sanitizeSensitiveFields(safe, {
409
+ matcher: ['email'],
410
+ replacement: '***'
411
+ })
374
412
  // => { password: '[REDACTED]', email: '***' }
375
413
  core.sanitize.sanitizeUrl('https://user:example-only@example.com/?token=example-only')
376
414
  // => 'https://[REDACTED]:[REDACTED]@example.com/?token=[REDACTED]'
@@ -449,6 +487,10 @@ Internal `cause` / `ext` are private by default. Field errors for 400/422 can re
449
487
 
450
488
  These constants belong to `HaiConfigError`; cross-module failures use `HaiCommonError`.
451
489
 
490
+ `HaiCommonError.RATE_LIMIT_EXCEEDED` maps to `hai:common:203` / HTTP 429. Kit shortcut responses and guards use the same common error definitions.
491
+
492
+ HTTP/UI boundaries expose business messages only for explicit valid 4xx statuses. Missing or invalid `httpStatus` is treated as 500 with a safe message. Custom business errors must carry a complete error definition or an explicit status.
493
+
452
494
  ### Messages and locales: `core.i18n`
453
495
 
454
496
  Create message getters with parameter interpolation, read built-in Core messages, and manage locale selection. Constants list framework locales and do not register application translations.
@@ -478,7 +520,9 @@ const message = core.i18n.createMessageGetter({
478
520
  // createMessageGetter 返回消息函数;如 message('welcome', { locale: 'zh-CN', params: { name: 'Alice' } }) 得到 '欢迎 Alice'。
479
521
  const greeting = await core.i18n.runWithLocale('en-US', async () => {
480
522
  await Promise.resolve()
481
- return message('welcome', { params: { name: 'Alice' } })
523
+ return message('welcome', {
524
+ params: { name: 'Alice' }
525
+ })
482
526
  })
483
527
  // greeting:'Welcome Alice';runWithLocale 返回回调的结果,此处等待 Promise 得到字符串。
484
528
  core.logger.info('Greeting prepared', { greeting })
@@ -500,7 +544,9 @@ const message = core.zodValidation.createPrefixedZodMessageGetter('core', key =>
500
544
  ? core.i18n.coreM('core_validationEmail', { locale: 'zh-CN' })
501
545
  : core.i18n.coreM('core_errorValidation', { locale: 'zh-CN' }))
502
546
  // 返回消息函数;message('validationEmail') 得到 '请输入有效的邮箱地址'。
503
- const parsed = z.object({ email: z.email() }).safeParse({ email: 'invalid' })
547
+ const parsed = z.object({
548
+ email: z.email()
549
+ }).safeParse({ email: 'invalid' })
504
550
  if (!parsed.success) {
505
551
  const errors = core.zodValidation.mapZodErrorToFormErrors(parsed.error, message)
506
552
  // errors:[{ field: 'email', message: '请输入有效的邮箱地址' }]。
@@ -617,10 +663,14 @@ import { AccessConditionSchema, core } from '@h-ai/core'
617
663
 
618
664
  core.access.normalizeClientIp('::ffff:198.51.100.23') // 198.51.100.23。
619
665
  const resolve = core.access.createClientIpResolver(['192.0.2.10']) // 返回 IP 解析函数,只信任这个代理。
620
- const request = new Request('https://example.test/api/orders', { headers: { 'x-forwarded-for': '198.51.100.23' } })
666
+ const request = new Request('https://example.test/api/orders', {
667
+ headers: { 'x-forwarded-for': '198.51.100.23' }
668
+ })
621
669
  // 第二参数必须来自真实 socket/可信平台;这里用测试地址模拟代理对端。
622
670
  const clientIp = resolve(request, '192.0.2.10') // '198.51.100.23',读取受信代理的转发头。
623
- const check = core.access.createConditionCheck(AccessConditionSchema.parse({ allowIps: ['198.51.100.0/24'] }))
671
+ const check = core.access.createConditionCheck(AccessConditionSchema.parse({
672
+ allowIps: ['198.51.100.0/24']
673
+ }))
624
674
  // createConditionCheck 返回检查函数;check(clientIp) 得到 { allowed: true }。
625
675
  core.logger.info('Address checked', { allowed: check(clientIp).allowed })
626
676
  ```
@@ -632,7 +682,15 @@ import { core } from '@h-ai/core'
632
682
 
633
683
  // 可改为 loadPolicy({ file: './config/security/request-access.yml' }) 在启动时读独立文件。
634
684
  const policy = core.access.loadPolicy({
635
- rules: [{ id: 'external-api', paths: ['/api/external/**'], methods: ['POST'], allowIps: ['192.0.2.0/24'], encryption: 'disabled' }],
685
+ rules: [
686
+ {
687
+ id: 'external-api',
688
+ paths: ['/api/external/**'],
689
+ methods: ['POST'],
690
+ allowIps: ['192.0.2.0/24'],
691
+ encryption: 'disabled'
692
+ }
693
+ ],
636
694
  })
637
695
  // policy.rules[0] 保留上方规则,并补 denyIps: []、windows: []、timezone: 'UTC'。
638
696
  const check = core.access.createRequestCheck(policy) // 返回请求检查函数,另带 encryption 方法和 requiresEncryption 属性。
@@ -702,7 +760,9 @@ core.object.omit(settings, ['port']) // { name: 'orders' }。
702
760
  core.object.keys(settings) // ['name', 'port'],元素类型为 'name' | 'port'。
703
761
  core.object.values(settings) // ['orders', 4000]。
704
762
  core.object.entries(settings) // [['name', 'orders'], ['port', 4000]]。
705
- core.object.fromEntries([['mode', 'local']]) // { mode: 'local' }。
763
+ core.object.fromEntries([
764
+ ['mode', 'local']
765
+ ]) // { mode: 'local' }。
706
766
  ```
707
767
 
708
768
  deepClone uses structuredClone for cloneable Date/Map/Set/cyclic values and throws for unsupported values such as functions. deepMerge recursively merges plain objects only; later arrays, dates, collections, and class instances replace earlier values without modifying inputs. It does not clone instances.
@@ -739,11 +799,17 @@ import { core } from '@h-ai/core'
739
799
 
740
800
  core.array.unique([1, 1, 2]) // [1, 2]。
741
801
  core.array.chunk([1, 2, 3], 2) // [[1, 2], [3]]。
742
- core.array.groupBy([{ role: 'admin' }, { role: 'user' }], item => item.role)
802
+ core.array.groupBy([
803
+ { role: 'admin' },
804
+ { role: 'user' }
805
+ ], item => item.role)
743
806
  // => { admin: [{ role: 'admin' }], user: [{ role: 'user' }] }
744
807
  core.array.first([1, 2, 3]) // 1;空数组为 undefined。
745
808
  core.array.last([1, 2, 3]) // 3;空数组为 undefined。
746
- core.array.flatten([[1], [2, 3]]) // [1, 2, 3],只展开一层。
809
+ core.array.flatten([
810
+ [1],
811
+ [2, 3]
812
+ ]) // [1, 2, 3],只展开一层。
747
813
  core.array.compact([0, null, 1, undefined]) // [0, 1],保留 0/false/空字符串。
748
814
  core.array.shuffle([1, 2, 3]) // 如 [2, 3, 1],次序随机;新数组不修改输入。
749
815
  core.array.intersection([1, 2], [2, 3]) // [2]。
package/README.ja.md CHANGED
@@ -93,7 +93,9 @@ core.logger.info('Application ready', { name: config.data.name }) // 返回 unde
93
93
  import { core } from '@h-ai/core'
94
94
 
95
95
  // 浏览器不读取 YAML;此处只设置日志,输出 warn 及以上级别。
96
- core.init({ logging: { level: 'warn' } }) // 返回 undefined;之后 info 日志被过滤。
96
+ core.init({
97
+ logging: { level: 'warn' }
98
+ }) // 返回 undefined;之后 info 日志被过滤。
97
99
 
98
100
  // 设置当前页面使用的默认消息语言。
99
101
  core.i18n.setGlobalLocale('zh-CN') // 返回 undefined;页面默认消息使用中文。
@@ -131,7 +133,9 @@ import { core } from '@h-ai/core'
131
133
  import { z } from 'zod'
132
134
 
133
135
  // 读取上方 config/app.yml;Schema 同时描述类型和校验规则。
134
- const AppSchema = z.object({ port: z.number().int().min(1).max(65535) })
136
+ const AppSchema = z.object({
137
+ port: z.number().int().min(1).max(65535)
138
+ })
135
139
  const loaded = core.config.load('app', './config/app.yml', AppSchema)
136
140
  if (!loaded.success)
137
141
  throw new Error(loaded.error.message, { cause: loaded.error })
@@ -230,11 +234,25 @@ import { core } from '@h-ai/core'
230
234
  import { z } from 'zod'
231
235
 
232
236
  // 用显式环境数据演示覆盖顺序、数值/布尔转换与字符串保留。
233
- const AppSchema = z.object({ port: z.number(), enabled: z.boolean(), label: z.string(), code: z.string() })
237
+ const AppSchema = z.object({
238
+ port: z.number(),
239
+ enabled: z.boolean(),
240
+ label: z.string(),
241
+ code: z.string()
242
+ })
234
243
  const resolved = core.config.resolve({
235
244
  name: 'app',
236
- // eslint-disable-next-line no-template-curly-in-string -- 保留 YAML 中等待框架解析的字面表达式。
237
- value: { port: '${PORT:3000}', enabled: '${ENABLED:false}', label: 'server-${PORT:3000}', code: '${APP_CODE:00123}' },
245
+
246
+ value: {
247
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
248
+ port: '${PORT:3000}',
249
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
250
+ enabled: '${ENABLED:false}',
251
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
252
+ label: 'server-${PORT:3000}',
253
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
254
+ code: '${APP_CODE:00123}'
255
+ },
238
256
  environment: { PORT: '4000', HAI_APP_PORT: '5000' },
239
257
  schema: AppSchema,
240
258
  })
@@ -265,9 +283,18 @@ resolve() はビルド・デプロイ時のメモリ上の設定計算にも使
265
283
  import { core } from '@h-ai/core'
266
284
 
267
285
  // 配置生成器把普通对象转成 HAI_APP_PORT 等变量;数组保留为 JSON。
268
- const values = core.config.toEnv({ prefix: 'HAI_APP', value: { port: 3000, tags: ['local'] } })
286
+ const values = core.config.toEnv({
287
+ prefix: 'HAI_APP',
288
+ value: {
289
+ port: 3000,
290
+ tags: ['local']
291
+ }
292
+ })
269
293
  // values:{ HAI_APP_PORT: '3000', HAI_APP_TAGS: '["local"]' }。
270
- const merged = core.config.mergeEnv([values, { HAI_APP_PORT: '4000' }])
294
+ const merged = core.config.mergeEnv([
295
+ values,
296
+ { HAI_APP_PORT: '4000' }
297
+ ])
271
298
  // merged:{ HAI_APP_PORT: '4000', HAI_APP_TAGS: '["local"]' }。
272
299
  const text = core.config.stringifyEnv(merged) // 编码为可写入 .env 的文本。
273
300
  // text 含以下两行,每行以换行符结尾:
@@ -295,7 +322,9 @@ core.logger.trace('Processing detail', { step: 1 })
295
322
  core.logger.debug('Read started')
296
323
  core.logger.info('Order created', { orderId: 'order-1' })
297
324
  core.logger.warn('Retry scheduled')
298
- core.logger.error('Operation failed', { error: new Error('Example failure') })
325
+ core.logger.error('Operation failed', {
326
+ error: new Error('Example failure')
327
+ })
299
328
  core.logger.fatal('Service cannot continue')
300
329
  // 六次日志调用均返回 undefined;分别输出对应级别、上面的消息及所传字段。
301
330
  // 例如 info 日志的消息为 'Order created',字段 orderId 为 'order-1'。
@@ -307,10 +336,16 @@ create() は名前付き Logger、child() は固定項目付き Logger を作り
307
336
  import { core } from '@h-ai/core'
308
337
 
309
338
  // 全局配置可作用于已经创建的 Logger;redact 补充业务隐私字段。
310
- core.logger.configure({ level: 'info', format: 'json', redact: ['customer.email'] }) // 返回 undefined;后续日志按此配置输出。
339
+ core.logger.configure({
340
+ level: 'info',
341
+ format: 'json',
342
+ redact: ['customer.email']
343
+ }) // 返回 undefined;后续日志按此配置输出。
311
344
  const orders = core.logger.create({ name: 'orders' }) // 返回 Logger 对象,可调用 info/warn 等方法。
312
345
  const request = core.logger.child({ requestId: 'request-1' }) // 返回带固定 requestId 的 Logger。
313
- orders.info('Order created', { customer: { email: 'user@example.com' } }) // 返回 undefined;输出中 customer.email 为 '[REDACTED]'。
346
+ orders.info('Order created', {
347
+ customer: { email: 'user@example.com' }
348
+ }) // 返回 undefined;输出中 customer.email 为 '[REDACTED]'。
314
349
  request.warn('Request needs attention') // 返回 undefined;输出中 requestId 为 'request-1'。
315
350
  core.logger.getLevel() // 'info'。
316
351
  ```
@@ -370,7 +405,10 @@ core.sanitize.sanitizeSensitiveValue('example-only', 'password') // '[REDACTED]'
370
405
  const safe = core.sanitize.sanitizeSensitiveFields({ password: 'example-only', email: 'user@example.com' })
371
406
  // safe:{ password: '[REDACTED]', email: 'user@example.com' },原对象不变。
372
407
  // 默认仅遮蔽凭据;额外业务隐私可在已处理凭据的副本上继续清理。
373
- core.sanitize.sanitizeSensitiveFields(safe, { matcher: ['email'], replacement: '***' })
408
+ core.sanitize.sanitizeSensitiveFields(safe, {
409
+ matcher: ['email'],
410
+ replacement: '***'
411
+ })
374
412
  // => { password: '[REDACTED]', email: '***' }
375
413
  core.sanitize.sanitizeUrl('https://user:example-only@example.com/?token=example-only')
376
414
  // => 'https://[REDACTED]:[REDACTED]@example.com/?token=[REDACTED]'
@@ -449,6 +487,10 @@ HTTP/UI 境界では `core.error.toPublic(error, traceId?, locale?)` を使い
449
487
 
450
488
  これらは `HaiConfigError` の定数です。モジュール共通のエラーは `HaiCommonError` を使います。
451
489
 
490
+ `HaiCommonError.RATE_LIMIT_EXCEEDED` は `hai:common:203` / HTTP 429 に対応します。Kit の応答ヘルパーとガードも共通エラー定義を使用します。
491
+
492
+ HTTP/UI 境界で業務メッセージを公開するのは、明示された有効な4xx状態のみです。`httpStatus` が未指定または不正な場合は500として安全なメッセージを返します。独自の業務エラーには完全なエラー定義または明示的な状態を指定してください。
493
+
452
494
  ### メッセージと言語:`core.i18n`
453
495
 
454
496
  パラメーター置換付きのメッセージ取得関数を作り、Core 内蔵メッセージや言語を取得・設定します。定数は内蔵言語一覧であり、アプリ翻訳の自動登録は行いません。
@@ -478,7 +520,9 @@ const message = core.i18n.createMessageGetter({
478
520
  // createMessageGetter 返回消息函数;如 message('welcome', { locale: 'zh-CN', params: { name: 'Alice' } }) 得到 '欢迎 Alice'。
479
521
  const greeting = await core.i18n.runWithLocale('en-US', async () => {
480
522
  await Promise.resolve()
481
- return message('welcome', { params: { name: 'Alice' } })
523
+ return message('welcome', {
524
+ params: { name: 'Alice' }
525
+ })
482
526
  })
483
527
  // greeting:'Welcome Alice';runWithLocale 返回回调的结果,此处等待 Promise 得到字符串。
484
528
  core.logger.info('Greeting prepared', { greeting })
@@ -500,7 +544,9 @@ const message = core.zodValidation.createPrefixedZodMessageGetter('core', key =>
500
544
  ? core.i18n.coreM('core_validationEmail', { locale: 'zh-CN' })
501
545
  : core.i18n.coreM('core_errorValidation', { locale: 'zh-CN' }))
502
546
  // 返回消息函数;message('validationEmail') 得到 '请输入有效的邮箱地址'。
503
- const parsed = z.object({ email: z.email() }).safeParse({ email: 'invalid' })
547
+ const parsed = z.object({
548
+ email: z.email()
549
+ }).safeParse({ email: 'invalid' })
504
550
  if (!parsed.success) {
505
551
  const errors = core.zodValidation.mapZodErrorToFormErrors(parsed.error, message)
506
552
  // errors:[{ field: 'email', message: '请输入有效的邮箱地址' }]。
@@ -617,10 +663,14 @@ import { AccessConditionSchema, core } from '@h-ai/core'
617
663
 
618
664
  core.access.normalizeClientIp('::ffff:198.51.100.23') // 198.51.100.23。
619
665
  const resolve = core.access.createClientIpResolver(['192.0.2.10']) // 返回 IP 解析函数,只信任这个代理。
620
- const request = new Request('https://example.test/api/orders', { headers: { 'x-forwarded-for': '198.51.100.23' } })
666
+ const request = new Request('https://example.test/api/orders', {
667
+ headers: { 'x-forwarded-for': '198.51.100.23' }
668
+ })
621
669
  // 第二参数必须来自真实 socket/可信平台;这里用测试地址模拟代理对端。
622
670
  const clientIp = resolve(request, '192.0.2.10') // '198.51.100.23',读取受信代理的转发头。
623
- const check = core.access.createConditionCheck(AccessConditionSchema.parse({ allowIps: ['198.51.100.0/24'] }))
671
+ const check = core.access.createConditionCheck(AccessConditionSchema.parse({
672
+ allowIps: ['198.51.100.0/24']
673
+ }))
624
674
  // createConditionCheck 返回检查函数;check(clientIp) 得到 { allowed: true }。
625
675
  core.logger.info('Address checked', { allowed: check(clientIp).allowed })
626
676
  ```
@@ -632,7 +682,15 @@ import { core } from '@h-ai/core'
632
682
 
633
683
  // 可改为 loadPolicy({ file: './config/security/request-access.yml' }) 在启动时读独立文件。
634
684
  const policy = core.access.loadPolicy({
635
- rules: [{ id: 'external-api', paths: ['/api/external/**'], methods: ['POST'], allowIps: ['192.0.2.0/24'], encryption: 'disabled' }],
685
+ rules: [
686
+ {
687
+ id: 'external-api',
688
+ paths: ['/api/external/**'],
689
+ methods: ['POST'],
690
+ allowIps: ['192.0.2.0/24'],
691
+ encryption: 'disabled'
692
+ }
693
+ ],
636
694
  })
637
695
  // policy.rules[0] 保留上方规则,并补 denyIps: []、windows: []、timezone: 'UTC'。
638
696
  const check = core.access.createRequestCheck(policy) // 返回请求检查函数,另带 encryption 方法和 requiresEncryption 属性。
@@ -702,7 +760,9 @@ core.object.omit(settings, ['port']) // { name: 'orders' }。
702
760
  core.object.keys(settings) // ['name', 'port'],元素类型为 'name' | 'port'。
703
761
  core.object.values(settings) // ['orders', 4000]。
704
762
  core.object.entries(settings) // [['name', 'orders'], ['port', 4000]]。
705
- core.object.fromEntries([['mode', 'local']]) // { mode: 'local' }。
763
+ core.object.fromEntries([
764
+ ['mode', 'local']
765
+ ]) // { mode: 'local' }。
706
766
  ```
707
767
 
708
768
  deepClone は structuredClone で Date/Map/Set/循環など複製可能な値を扱い、関数など非対応値では例外です。deepMerge は通常オブジェクトのみ再帰結合し、配列・Date・集合・クラスは後の値で置換し、入力は変更しません。インスタンスの複製ではありません。
@@ -739,11 +799,17 @@ import { core } from '@h-ai/core'
739
799
 
740
800
  core.array.unique([1, 1, 2]) // [1, 2]。
741
801
  core.array.chunk([1, 2, 3], 2) // [[1, 2], [3]]。
742
- core.array.groupBy([{ role: 'admin' }, { role: 'user' }], item => item.role)
802
+ core.array.groupBy([
803
+ { role: 'admin' },
804
+ { role: 'user' }
805
+ ], item => item.role)
743
806
  // => { admin: [{ role: 'admin' }], user: [{ role: 'user' }] }
744
807
  core.array.first([1, 2, 3]) // 1;空数组为 undefined。
745
808
  core.array.last([1, 2, 3]) // 3;空数组为 undefined。
746
- core.array.flatten([[1], [2, 3]]) // [1, 2, 3],只展开一层。
809
+ core.array.flatten([
810
+ [1],
811
+ [2, 3]
812
+ ]) // [1, 2, 3],只展开一层。
747
813
  core.array.compact([0, null, 1, undefined]) // [0, 1],保留 0/false/空字符串。
748
814
  core.array.shuffle([1, 2, 3]) // 如 [2, 3, 1],次序随机;新数组不修改输入。
749
815
  core.array.intersection([1, 2], [2, 3]) // [2]。
package/README.md CHANGED
@@ -93,7 +93,9 @@ core.logger.info('Application ready', { name: config.data.name }) // 返回 unde
93
93
  import { core } from '@h-ai/core'
94
94
 
95
95
  // 浏览器不读取 YAML;此处只设置日志,输出 warn 及以上级别。
96
- core.init({ logging: { level: 'warn' } }) // 返回 undefined;之后 info 日志被过滤。
96
+ core.init({
97
+ logging: { level: 'warn' }
98
+ }) // 返回 undefined;之后 info 日志被过滤。
97
99
 
98
100
  // 设置当前页面使用的默认消息语言。
99
101
  core.i18n.setGlobalLocale('zh-CN') // 返回 undefined;页面默认消息使用中文。
@@ -131,7 +133,9 @@ import { core } from '@h-ai/core'
131
133
  import { z } from 'zod'
132
134
 
133
135
  // 读取上方 config/app.yml;Schema 同时描述类型和校验规则。
134
- const AppSchema = z.object({ port: z.number().int().min(1).max(65535) })
136
+ const AppSchema = z.object({
137
+ port: z.number().int().min(1).max(65535)
138
+ })
135
139
  const loaded = core.config.load('app', './config/app.yml', AppSchema)
136
140
  if (!loaded.success)
137
141
  throw new Error(loaded.error.message, { cause: loaded.error })
@@ -230,11 +234,25 @@ import { core } from '@h-ai/core'
230
234
  import { z } from 'zod'
231
235
 
232
236
  // 用显式环境数据演示覆盖顺序、数值/布尔转换与字符串保留。
233
- const AppSchema = z.object({ port: z.number(), enabled: z.boolean(), label: z.string(), code: z.string() })
237
+ const AppSchema = z.object({
238
+ port: z.number(),
239
+ enabled: z.boolean(),
240
+ label: z.string(),
241
+ code: z.string()
242
+ })
234
243
  const resolved = core.config.resolve({
235
244
  name: 'app',
236
- // eslint-disable-next-line no-template-curly-in-string -- 保留 YAML 中等待框架解析的字面表达式。
237
- value: { port: '${PORT:3000}', enabled: '${ENABLED:false}', label: 'server-${PORT:3000}', code: '${APP_CODE:00123}' },
245
+
246
+ value: {
247
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
248
+ port: '${PORT:3000}',
249
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
250
+ enabled: '${ENABLED:false}',
251
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
252
+ label: 'server-${PORT:3000}',
253
+ // eslint-disable-next-line no-template-curly-in-string -- 保留供配置解析器处理的字面占位符。
254
+ code: '${APP_CODE:00123}'
255
+ },
238
256
  environment: { PORT: '4000', HAI_APP_PORT: '5000' },
239
257
  schema: AppSchema,
240
258
  })
@@ -265,9 +283,18 @@ core.logger.info('Config resolved', { settings: validated.data })
265
283
  import { core } from '@h-ai/core'
266
284
 
267
285
  // 配置生成器把普通对象转成 HAI_APP_PORT 等变量;数组保留为 JSON。
268
- const values = core.config.toEnv({ prefix: 'HAI_APP', value: { port: 3000, tags: ['local'] } })
286
+ const values = core.config.toEnv({
287
+ prefix: 'HAI_APP',
288
+ value: {
289
+ port: 3000,
290
+ tags: ['local']
291
+ }
292
+ })
269
293
  // values:{ HAI_APP_PORT: '3000', HAI_APP_TAGS: '["local"]' }。
270
- const merged = core.config.mergeEnv([values, { HAI_APP_PORT: '4000' }])
294
+ const merged = core.config.mergeEnv([
295
+ values,
296
+ { HAI_APP_PORT: '4000' }
297
+ ])
271
298
  // merged:{ HAI_APP_PORT: '4000', HAI_APP_TAGS: '["local"]' }。
272
299
  const text = core.config.stringifyEnv(merged) // 编码为可写入 .env 的文本。
273
300
  // text 含以下两行,每行以换行符结尾:
@@ -295,7 +322,9 @@ core.logger.trace('Processing detail', { step: 1 })
295
322
  core.logger.debug('Read started')
296
323
  core.logger.info('Order created', { orderId: 'order-1' })
297
324
  core.logger.warn('Retry scheduled')
298
- core.logger.error('Operation failed', { error: new Error('Example failure') })
325
+ core.logger.error('Operation failed', {
326
+ error: new Error('Example failure')
327
+ })
299
328
  core.logger.fatal('Service cannot continue')
300
329
  // 六次日志调用均返回 undefined;分别输出对应级别、上面的消息及所传字段。
301
330
  // 例如 info 日志的消息为 'Order created',字段 orderId 为 'order-1'。
@@ -307,10 +336,16 @@ core.logger.fatal('Service cannot continue')
307
336
  import { core } from '@h-ai/core'
308
337
 
309
338
  // 全局配置可作用于已经创建的 Logger;redact 补充业务隐私字段。
310
- core.logger.configure({ level: 'info', format: 'json', redact: ['customer.email'] }) // 返回 undefined;后续日志按此配置输出。
339
+ core.logger.configure({
340
+ level: 'info',
341
+ format: 'json',
342
+ redact: ['customer.email']
343
+ }) // 返回 undefined;后续日志按此配置输出。
311
344
  const orders = core.logger.create({ name: 'orders' }) // 返回 Logger 对象,可调用 info/warn 等方法。
312
345
  const request = core.logger.child({ requestId: 'request-1' }) // 返回带固定 requestId 的 Logger。
313
- orders.info('Order created', { customer: { email: 'user@example.com' } }) // 返回 undefined;输出中 customer.email 为 '[REDACTED]'。
346
+ orders.info('Order created', {
347
+ customer: { email: 'user@example.com' }
348
+ }) // 返回 undefined;输出中 customer.email 为 '[REDACTED]'。
314
349
  request.warn('Request needs attention') // 返回 undefined;输出中 requestId 为 'request-1'。
315
350
  core.logger.getLevel() // 'info'。
316
351
  ```
@@ -370,7 +405,10 @@ core.sanitize.sanitizeSensitiveValue('example-only', 'password') // '[REDACTED]'
370
405
  const safe = core.sanitize.sanitizeSensitiveFields({ password: 'example-only', email: 'user@example.com' })
371
406
  // safe:{ password: '[REDACTED]', email: 'user@example.com' },原对象不变。
372
407
  // 默认仅遮蔽凭据;额外业务隐私可在已处理凭据的副本上继续清理。
373
- core.sanitize.sanitizeSensitiveFields(safe, { matcher: ['email'], replacement: '***' })
408
+ core.sanitize.sanitizeSensitiveFields(safe, {
409
+ matcher: ['email'],
410
+ replacement: '***'
411
+ })
374
412
  // => { password: '[REDACTED]', email: '***' }
375
413
  core.sanitize.sanitizeUrl('https://user:example-only@example.com/?token=example-only')
376
414
  // => 'https://[REDACTED]:[REDACTED]@example.com/?token=[REDACTED]'
@@ -449,6 +487,10 @@ if (!failed.success)
449
487
 
450
488
  以上常量来自 `HaiConfigError`;跨模块通用错误使用 `HaiCommonError`。
451
489
 
490
+ 通用错误 `HaiCommonError.RATE_LIMIT_EXCEEDED` 对应 `hai:common:203` / HTTP 429;Kit 的快捷响应和守卫同样使用通用错误码。
491
+
492
+ HTTP/UI 边界只对明确的有效 4xx 状态公开业务提示;错误缺少 `httpStatus` 或状态非法时按 500 返回安全提示。手写业务错误须提供完整错误定义或显式状态,不能依赖缺省状态展示原文。
493
+
452
494
  ### 消息与语言:`core.i18n`
453
495
 
454
496
  为业务字典创建带参数替换的消息获取器,也可取得 Core 内置消息和当前语言。常量列出框架内置语言,不会自动为应用注册翻译。
@@ -478,7 +520,9 @@ const message = core.i18n.createMessageGetter({
478
520
  // createMessageGetter 返回消息函数;如 message('welcome', { locale: 'zh-CN', params: { name: 'Alice' } }) 得到 '欢迎 Alice'。
479
521
  const greeting = await core.i18n.runWithLocale('en-US', async () => {
480
522
  await Promise.resolve()
481
- return message('welcome', { params: { name: 'Alice' } })
523
+ return message('welcome', {
524
+ params: { name: 'Alice' }
525
+ })
482
526
  })
483
527
  // greeting:'Welcome Alice';runWithLocale 返回回调的结果,此处等待 Promise 得到字符串。
484
528
  core.logger.info('Greeting prepared', { greeting })
@@ -500,7 +544,9 @@ const message = core.zodValidation.createPrefixedZodMessageGetter('core', key =>
500
544
  ? core.i18n.coreM('core_validationEmail', { locale: 'zh-CN' })
501
545
  : core.i18n.coreM('core_errorValidation', { locale: 'zh-CN' }))
502
546
  // 返回消息函数;message('validationEmail') 得到 '请输入有效的邮箱地址'。
503
- const parsed = z.object({ email: z.email() }).safeParse({ email: 'invalid' })
547
+ const parsed = z.object({
548
+ email: z.email()
549
+ }).safeParse({ email: 'invalid' })
504
550
  if (!parsed.success) {
505
551
  const errors = core.zodValidation.mapZodErrorToFormErrors(parsed.error, message)
506
552
  // errors:[{ field: 'email', message: '请输入有效的邮箱地址' }]。
@@ -617,10 +663,14 @@ import { AccessConditionSchema, core } from '@h-ai/core'
617
663
 
618
664
  core.access.normalizeClientIp('::ffff:198.51.100.23') // 198.51.100.23。
619
665
  const resolve = core.access.createClientIpResolver(['192.0.2.10']) // 返回 IP 解析函数,只信任这个代理。
620
- const request = new Request('https://example.test/api/orders', { headers: { 'x-forwarded-for': '198.51.100.23' } })
666
+ const request = new Request('https://example.test/api/orders', {
667
+ headers: { 'x-forwarded-for': '198.51.100.23' }
668
+ })
621
669
  // 第二参数必须来自真实 socket/可信平台;这里用测试地址模拟代理对端。
622
670
  const clientIp = resolve(request, '192.0.2.10') // '198.51.100.23',读取受信代理的转发头。
623
- const check = core.access.createConditionCheck(AccessConditionSchema.parse({ allowIps: ['198.51.100.0/24'] }))
671
+ const check = core.access.createConditionCheck(AccessConditionSchema.parse({
672
+ allowIps: ['198.51.100.0/24']
673
+ }))
624
674
  // createConditionCheck 返回检查函数;check(clientIp) 得到 { allowed: true }。
625
675
  core.logger.info('Address checked', { allowed: check(clientIp).allowed })
626
676
  ```
@@ -632,7 +682,15 @@ import { core } from '@h-ai/core'
632
682
 
633
683
  // 可改为 loadPolicy({ file: './config/security/request-access.yml' }) 在启动时读独立文件。
634
684
  const policy = core.access.loadPolicy({
635
- rules: [{ id: 'external-api', paths: ['/api/external/**'], methods: ['POST'], allowIps: ['192.0.2.0/24'], encryption: 'disabled' }],
685
+ rules: [
686
+ {
687
+ id: 'external-api',
688
+ paths: ['/api/external/**'],
689
+ methods: ['POST'],
690
+ allowIps: ['192.0.2.0/24'],
691
+ encryption: 'disabled'
692
+ }
693
+ ],
636
694
  })
637
695
  // policy.rules[0] 保留上方规则,并补 denyIps: []、windows: []、timezone: 'UTC'。
638
696
  const check = core.access.createRequestCheck(policy) // 返回请求检查函数,另带 encryption 方法和 requiresEncryption 属性。
@@ -702,7 +760,9 @@ core.object.omit(settings, ['port']) // { name: 'orders' }。
702
760
  core.object.keys(settings) // ['name', 'port'],元素类型为 'name' | 'port'。
703
761
  core.object.values(settings) // ['orders', 4000]。
704
762
  core.object.entries(settings) // [['name', 'orders'], ['port', 4000]]。
705
- core.object.fromEntries([['mode', 'local']]) // { mode: 'local' }。
763
+ core.object.fromEntries([
764
+ ['mode', 'local']
765
+ ]) // { mode: 'local' }。
706
766
  ```
707
767
 
708
768
  deepClone 使用 structuredClone,支持 Date/Map/Set/循环等可克隆值;不可克隆的函数等会抛错。deepMerge 只递归普通对象,数组、Date、集合和类实例由后值覆盖,不修改输入;不把它当作实例克隆。
@@ -739,11 +799,17 @@ import { core } from '@h-ai/core'
739
799
 
740
800
  core.array.unique([1, 1, 2]) // [1, 2]。
741
801
  core.array.chunk([1, 2, 3], 2) // [[1, 2], [3]]。
742
- core.array.groupBy([{ role: 'admin' }, { role: 'user' }], item => item.role)
802
+ core.array.groupBy([
803
+ { role: 'admin' },
804
+ { role: 'user' }
805
+ ], item => item.role)
743
806
  // => { admin: [{ role: 'admin' }], user: [{ role: 'user' }] }
744
807
  core.array.first([1, 2, 3]) // 1;空数组为 undefined。
745
808
  core.array.last([1, 2, 3]) // 3;空数组为 undefined。
746
- core.array.flatten([[1], [2, 3]]) // [1, 2, 3],只展开一层。
809
+ core.array.flatten([
810
+ [1],
811
+ [2, 3]
812
+ ]) // [1, 2, 3],只展开一层。
747
813
  core.array.compact([0, null, 1, undefined]) // [0, 1],保留 0/false/空字符串。
748
814
  core.array.shuffle([1, 2, 3]) // 如 [2, 3, 1],次序随机;新数组不修改输入。
749
815
  core.array.intersection([1, 2], [2, 3]) // [2]。
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BrowserCoreFunctions } from './core-types-n2KjBtto.js';
2
- export { A as AccessCondition, a as AccessConditionSchema, b as AccessDecision, c as AccessFunctions, d as ArrayFn, e as AsyncFn, f as BrowserConfigFn, C as ConfigFn, g as CoreConfig, h as CoreConfigSchema, i as CoreFunctions, j as CoreLogger, k as CoreOptions, l as CreateNotInitializedKitFn, E as Env, m as EnvSchema, n as ErrorFn, o as ErrorInfo, p as ErrorInfoValue, H as HaiCommonError, q as HaiConfigError, r as HaiError, s as HaiErrorDef, t as HaiResult, I as I18nFn, u as IdFn, v as InterpolationParams, L as Locale, w as LocaleInfo, x as LocaleMessages, y as LogContext, z as LogFormat, D as LogFormatSchema, F as LogLevel, G as LogLevelSchema, J as LogObserver, K as LogRecord, M as Logger, N as LoggerFunctions, O as LoggerOptions, P as LoggingConfig, Q as LoggingConfigSchema, R as MatchHandlers, S as MessageDictionary, T as MessageOptions, U as ModuleFn, V as ModuleRuntime, W as NodeCoreFunctions, X as NotInitializedKit, Y as ObjectFn, Z as PaginatedResult, _ as PaginationOptions, $ as PaginationOptionsInput, a0 as RequestAccessCheck, a1 as RequestAccessConfigInput, a2 as RequestAccessConfigSchema, a3 as RequestAccessPolicy, a4 as RequestAccessPolicySchema, a5 as RequestContext, a6 as RequestEncryptionMode, a7 as RuntimeStep, a8 as SanitizeFn, a9 as SanitizeSensitiveFieldsOptions, aa as SensitiveKeyMatcher, ab as StringFn, ac as TimeFn, ad as TrustedProxies, ae as TrustedProxiesSchema, af as TypeUtilFn, ag as ValidationFormError, ah as WatchCallback, ai as ZodMessageGetter, aj as ZodValidationFn, ak as ZodValidationMessageKey, al as err, am as ok } from './core-types-n2KjBtto.js';
1
+ import { B as BrowserCoreFunctions } from './core-types-Dp3Vr2uI.js';
2
+ export { A as AccessCondition, a as AccessConditionSchema, b as AccessDecision, c as AccessFunctions, d as ArrayFn, e as AsyncFn, f as BrowserConfigFn, C as ConfigFn, g as CoreConfig, h as CoreConfigSchema, i as CoreFunctions, j as CoreLogger, k as CoreOptions, l as CreateNotInitializedKitFn, E as Env, m as EnvSchema, n as ErrorFn, o as ErrorInfo, p as ErrorInfoValue, H as HaiCommonError, q as HaiConfigError, r as HaiError, s as HaiErrorDef, t as HaiResult, I as I18nFn, u as IdFn, v as InterpolationParams, L as Locale, w as LocaleInfo, x as LocaleMessages, y as LogContext, z as LogFormat, D as LogFormatSchema, F as LogLevel, G as LogLevelSchema, J as LogObserver, K as LogRecord, M as Logger, N as LoggerFunctions, O as LoggerOptions, P as LoggingConfig, Q as LoggingConfigSchema, R as MatchHandlers, S as MessageDictionary, T as MessageOptions, U as ModuleFn, V as ModuleRuntime, W as NodeCoreFunctions, X as NotInitializedKit, Y as ObjectFn, Z as PaginatedResult, _ as PaginationOptions, $ as PaginationOptionsInput, a0 as RequestAccessCheck, a1 as RequestAccessConfigInput, a2 as RequestAccessConfigSchema, a3 as RequestAccessPolicy, a4 as RequestAccessPolicySchema, a5 as RequestContext, a6 as RequestEncryptionMode, a7 as RuntimeStep, a8 as SanitizeFn, a9 as SanitizeSensitiveFieldsOptions, aa as SensitiveKeyMatcher, ab as StringFn, ac as TimeFn, ad as TrustedProxies, ae as TrustedProxiesSchema, af as TypeUtilFn, ag as ValidationFormError, ah as WatchCallback, ai as ZodMessageGetter, aj as ZodValidationFn, ak as ZodValidationMessageKey, al as err, am as ok } from './core-types-Dp3Vr2uI.js';
3
3
  import 'zod';
4
4
 
5
5
  /**