@h-ai/core 0.1.0-alpha5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +422 -0
- package/dist/browser.d.ts +43 -0
- package/dist/browser.js +115 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunk-2GTCDC56.js +14761 -0
- package/dist/chunk-2GTCDC56.js.map +1 -0
- package/dist/core-util-module-B-wH5_ZT.d.ts +1626 -0
- package/dist/node.d.ts +82 -0
- package/dist/node.js +643 -0
- package/dist/node.js.map +1 -0
- package/package.json +56 -0
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { H as HaiError, a as HaiResult, C as CoreOptions, b as CoreLogger, I as I18nFn, c as IdFn, T as TypeUtilFn, O as ObjectFn, S as StringFn, A as ArrayFn, d as AsyncFn, e as TimeFn, E as ErrorFn, M as ModuleFn } from './core-util-module-B-wH5_ZT.js';
|
|
2
|
+
export { f as CoreConfig, g as CoreConfigSchema, h as CoreFunctions, i as Env, j as EnvSchema, k as ErrorInfo, l as ErrorInfoValue, m as HaiCommonError, n as HaiConfigError, o as HaiErrorDef, p as IdConfig, q as IdConfigSchema, r as InterpolationParams, L as Locale, s as LocaleInfo, t as LocaleMessages, u as LogContext, v as LogFormat, w as LogFormatSchema, x as LogLevel, y as LogLevelSchema, z as Logger, B as LoggerFunctions, D as LoggerOptions, F as LoggingConfig, G as LoggingConfigSchema, J as MatchHandlers, K as MessageDictionary, N as MessageOptions, P as PaginatedResult, Q as PaginationOptions, R as PaginationOptionsInput, U as err, V as ok } from './core-util-module-B-wH5_ZT.js';
|
|
3
|
+
import * as zod from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @h-ai/core — 配置管理(Node.js 专用)
|
|
7
|
+
*
|
|
8
|
+
* 提供 YAML 配置文件加载、环境变量插值、缓存管理。
|
|
9
|
+
* @module core-function-config
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 监听回调类型。
|
|
14
|
+
*
|
|
15
|
+
* @template T - 配置数据类型
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const callback: WatchCallback<AppConfig> = (cfg, error) => {
|
|
20
|
+
* if (error) { core.logger.error('Config reload failed', { error }); return }
|
|
21
|
+
* // 使用更新后的 cfg
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
type WatchCallback<T = unknown> = (config: T | null, error?: HaiError) => void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Core 服务对象 - 聚合常用功能(Node.js)。
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { core } from '@h-ai/core'
|
|
33
|
+
* core.init({ configDir: './config' })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare const core: {
|
|
37
|
+
/** 配置管理 */
|
|
38
|
+
config: {
|
|
39
|
+
load<T>(name: string, filePath: string, schema?: zod.ZodType<T>): HaiResult<T>;
|
|
40
|
+
validate<T>(name: string, schema: zod.ZodType<T>): HaiResult<T>;
|
|
41
|
+
get<T>(name: string): T | undefined;
|
|
42
|
+
getOrThrow<T>(name: string): T;
|
|
43
|
+
reload(name: string): HaiResult<unknown>;
|
|
44
|
+
has(name: string): boolean;
|
|
45
|
+
clear(name?: string): void;
|
|
46
|
+
keys(): string[];
|
|
47
|
+
watch<T = unknown>(name: string, callback: WatchCallback<T>): () => void;
|
|
48
|
+
unwatch(name?: string): void;
|
|
49
|
+
isWatching(name: string): boolean;
|
|
50
|
+
};
|
|
51
|
+
/** 初始化 Core */
|
|
52
|
+
init: typeof initCore;
|
|
53
|
+
logger: CoreLogger;
|
|
54
|
+
i18n: I18nFn;
|
|
55
|
+
id: IdFn;
|
|
56
|
+
typeUtils: TypeUtilFn;
|
|
57
|
+
object: ObjectFn;
|
|
58
|
+
string: StringFn;
|
|
59
|
+
array: ArrayFn;
|
|
60
|
+
async: AsyncFn;
|
|
61
|
+
time: TimeFn;
|
|
62
|
+
error: ErrorFn;
|
|
63
|
+
module: ModuleFn;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* 初始化 Core(内部实现,通过 `core.init()` 调用)。
|
|
67
|
+
*
|
|
68
|
+
* 执行流程:
|
|
69
|
+
* 1. 配置日志(若提供 `options.logging`)
|
|
70
|
+
* 2. 扫描并加载配置目录中的所有 YAML 文件
|
|
71
|
+
* 3. 启用配置文件监听(若 `options.watchConfig` 为 true)
|
|
72
|
+
*
|
|
73
|
+
* @param options - 初始化选项
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* core.init({ configDir: './config', watchConfig: true })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
declare function initCore(options?: CoreOptions): void;
|
|
81
|
+
|
|
82
|
+
export { CoreLogger, CoreOptions, HaiError, HaiResult, core };
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import { createCore, CoreConfigSchema, i18n, err, HaiConfigError, ok, typeUtils } from './chunk-2GTCDC56.js';
|
|
2
|
+
export { CoreConfigSchema, EnvSchema, HaiCommonError, HaiConfigError, IdConfigSchema, LogFormatSchema, LogLevelSchema, LoggingConfigSchema, err, ok } from './chunk-2GTCDC56.js';
|
|
3
|
+
import { existsSync, readdirSync, watch, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import process2 from 'process';
|
|
6
|
+
import { parse } from 'yaml';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import pino from 'pino';
|
|
9
|
+
|
|
10
|
+
var ENV_VAR_PATTERN = /\$\{([^}:]+)(?::([^}]*))?\}/g;
|
|
11
|
+
var FULL_VAR_PATTERN = /^\$\{[^}:]+(?::[^}]*)?\}$/;
|
|
12
|
+
function interpolateEnv(value) {
|
|
13
|
+
if (typeof value === "string") {
|
|
14
|
+
let result = value;
|
|
15
|
+
ENV_VAR_PATTERN.lastIndex = 0;
|
|
16
|
+
while (true) {
|
|
17
|
+
const match = ENV_VAR_PATTERN.exec(value);
|
|
18
|
+
if (!match)
|
|
19
|
+
break;
|
|
20
|
+
const [fullMatch, varName, defaultValue] = match;
|
|
21
|
+
const envValue = process2.env[varName];
|
|
22
|
+
if (envValue === void 0 && defaultValue === void 0) {
|
|
23
|
+
return err(HaiConfigError.CONFIG_ENV_VAR_MISSING, i18n.coreM("core_configEnvVarMissing", { params: { varName } }));
|
|
24
|
+
}
|
|
25
|
+
result = result.replace(fullMatch, envValue ?? defaultValue ?? "");
|
|
26
|
+
}
|
|
27
|
+
if (FULL_VAR_PATTERN.test(value)) {
|
|
28
|
+
try {
|
|
29
|
+
const coerced = parse(result);
|
|
30
|
+
if (coerced !== void 0)
|
|
31
|
+
return ok(coerced);
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return ok(result);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
const results = [];
|
|
39
|
+
for (const item of value) {
|
|
40
|
+
const r = interpolateEnv(item);
|
|
41
|
+
if (!r.success)
|
|
42
|
+
return r;
|
|
43
|
+
results.push(r.data);
|
|
44
|
+
}
|
|
45
|
+
return ok(results);
|
|
46
|
+
}
|
|
47
|
+
if (typeUtils.isObject(value)) {
|
|
48
|
+
const results = {};
|
|
49
|
+
for (const [k, v] of Object.entries(value)) {
|
|
50
|
+
const r = interpolateEnv(v);
|
|
51
|
+
if (!r.success)
|
|
52
|
+
return r;
|
|
53
|
+
results[k] = r.data;
|
|
54
|
+
}
|
|
55
|
+
return ok(results);
|
|
56
|
+
}
|
|
57
|
+
return ok(value);
|
|
58
|
+
}
|
|
59
|
+
var configCache = /* @__PURE__ */ new Map();
|
|
60
|
+
var watchEntries = /* @__PURE__ */ new Map();
|
|
61
|
+
function createNotLoadedError(name) {
|
|
62
|
+
const result = err(HaiConfigError.CONFIG_NOT_LOADED, i18n.coreM("core_configNotLoaded", { params: { name } }));
|
|
63
|
+
if (!result.success)
|
|
64
|
+
return result.error;
|
|
65
|
+
throw new Error("unreachable");
|
|
66
|
+
}
|
|
67
|
+
function notifyWatchCallbacks(name, result) {
|
|
68
|
+
const entry = watchEntries.get(name);
|
|
69
|
+
if (!entry)
|
|
70
|
+
return;
|
|
71
|
+
if (result.success) {
|
|
72
|
+
for (const callback of entry.callbacks) {
|
|
73
|
+
try {
|
|
74
|
+
callback(result.data, void 0);
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
for (const callback of entry.callbacks) {
|
|
80
|
+
try {
|
|
81
|
+
callback(null, result.error);
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function loadYaml(filePath) {
|
|
88
|
+
if (!existsSync(filePath)) {
|
|
89
|
+
return err(HaiConfigError.CONFIG_FILE_NOT_FOUND, i18n.coreM("core_configFileNotExist", { params: { filePath } }));
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const content = readFileSync(filePath, "utf-8");
|
|
93
|
+
const parsed = parse(content);
|
|
94
|
+
return interpolateEnv(parsed);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return err(HaiConfigError.CONFIG_PARSE_ERROR, i18n.coreM("core_configParseFailed", { params: { filePath } }), error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function loadConfig(filePath, schema) {
|
|
100
|
+
const yamlResult = loadYaml(filePath);
|
|
101
|
+
if (!yamlResult.success)
|
|
102
|
+
return yamlResult;
|
|
103
|
+
const parseResult = schema.safeParse(yamlResult.data);
|
|
104
|
+
if (!parseResult.success) {
|
|
105
|
+
return err(HaiConfigError.CONFIG_VALIDATION_ERROR, i18n.coreM("core_configValidationFailed"), parseResult.error.issues);
|
|
106
|
+
}
|
|
107
|
+
return ok(parseResult.data);
|
|
108
|
+
}
|
|
109
|
+
function loadAndCache(name, filePath, schema) {
|
|
110
|
+
const result = schema ? loadConfig(filePath, schema) : loadYaml(filePath);
|
|
111
|
+
if (result.success) {
|
|
112
|
+
configCache.set(name, {
|
|
113
|
+
data: result.data,
|
|
114
|
+
filePath,
|
|
115
|
+
schema,
|
|
116
|
+
loadedAt: Date.now()
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
function validateLoadedConfig(name, schema) {
|
|
122
|
+
const entry = configCache.get(name);
|
|
123
|
+
if (!entry) {
|
|
124
|
+
return err(createNotLoadedError(name));
|
|
125
|
+
}
|
|
126
|
+
const parseResult = schema.safeParse(entry.data);
|
|
127
|
+
if (!parseResult.success) {
|
|
128
|
+
return err(HaiConfigError.CONFIG_VALIDATION_ERROR, i18n.coreM("core_configValidationFailed"), parseResult.error.issues);
|
|
129
|
+
}
|
|
130
|
+
const validated = parseResult.data;
|
|
131
|
+
configCache.set(name, {
|
|
132
|
+
...entry,
|
|
133
|
+
data: validated,
|
|
134
|
+
schema,
|
|
135
|
+
loadedAt: Date.now()
|
|
136
|
+
});
|
|
137
|
+
return ok(validated);
|
|
138
|
+
}
|
|
139
|
+
function reloadAndNotify(name) {
|
|
140
|
+
const entry = configCache.get(name);
|
|
141
|
+
if (!entry) {
|
|
142
|
+
const result2 = err(createNotLoadedError(name));
|
|
143
|
+
notifyWatchCallbacks(name, result2);
|
|
144
|
+
return result2;
|
|
145
|
+
}
|
|
146
|
+
const result = entry.schema ? loadConfig(entry.filePath, entry.schema) : loadYaml(entry.filePath);
|
|
147
|
+
if (result.success) {
|
|
148
|
+
configCache.set(name, {
|
|
149
|
+
...entry,
|
|
150
|
+
data: result.data,
|
|
151
|
+
loadedAt: Date.now()
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
notifyWatchCallbacks(name, result);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
function startFileWatcher(name) {
|
|
158
|
+
const existing = watchEntries.get(name);
|
|
159
|
+
if (existing)
|
|
160
|
+
return existing;
|
|
161
|
+
const entry = configCache.get(name);
|
|
162
|
+
if (!entry)
|
|
163
|
+
return null;
|
|
164
|
+
try {
|
|
165
|
+
const watchEntry = {
|
|
166
|
+
watcher: null,
|
|
167
|
+
callbacks: /* @__PURE__ */ new Set(),
|
|
168
|
+
debounceTimer: null
|
|
169
|
+
};
|
|
170
|
+
const watcher = watch(entry.filePath, (eventType) => {
|
|
171
|
+
if (eventType === "change") {
|
|
172
|
+
if (watchEntry.debounceTimer)
|
|
173
|
+
clearTimeout(watchEntry.debounceTimer);
|
|
174
|
+
watchEntry.debounceTimer = setTimeout(() => {
|
|
175
|
+
watchEntry.debounceTimer = null;
|
|
176
|
+
reloadAndNotify(name);
|
|
177
|
+
}, 100);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
watchEntry.watcher = watcher;
|
|
181
|
+
watchEntries.set(name, watchEntry);
|
|
182
|
+
return watchEntry;
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function registerWatch(name, callback) {
|
|
188
|
+
const entry = startFileWatcher(name);
|
|
189
|
+
if (!entry) {
|
|
190
|
+
callback(null, createNotLoadedError(name));
|
|
191
|
+
return () => {
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
entry.callbacks.add(callback);
|
|
195
|
+
return () => {
|
|
196
|
+
const current = watchEntries.get(name);
|
|
197
|
+
if (!current)
|
|
198
|
+
return;
|
|
199
|
+
current.callbacks.delete(callback);
|
|
200
|
+
if (current.callbacks.size === 0) {
|
|
201
|
+
if (current.debounceTimer)
|
|
202
|
+
clearTimeout(current.debounceTimer);
|
|
203
|
+
current.watcher.close();
|
|
204
|
+
watchEntries.delete(name);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function stopWatching(name) {
|
|
209
|
+
if (name) {
|
|
210
|
+
const entry = watchEntries.get(name);
|
|
211
|
+
if (entry) {
|
|
212
|
+
if (entry.debounceTimer)
|
|
213
|
+
clearTimeout(entry.debounceTimer);
|
|
214
|
+
entry.watcher.close();
|
|
215
|
+
watchEntries.delete(name);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
for (const entry of watchEntries.values()) {
|
|
219
|
+
if (entry.debounceTimer)
|
|
220
|
+
clearTimeout(entry.debounceTimer);
|
|
221
|
+
entry.watcher.close();
|
|
222
|
+
}
|
|
223
|
+
watchEntries.clear();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function clearCache(name) {
|
|
227
|
+
if (name) {
|
|
228
|
+
configCache.delete(name);
|
|
229
|
+
stopWatching(name);
|
|
230
|
+
} else {
|
|
231
|
+
configCache.clear();
|
|
232
|
+
stopWatching();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
var config = {
|
|
236
|
+
/**
|
|
237
|
+
* 加载配置到缓存。
|
|
238
|
+
*
|
|
239
|
+
* 加载 YAML 文件并可选地用 Zod Schema 校验,成功后写入缓存。
|
|
240
|
+
*
|
|
241
|
+
* @param name - 配置名称(缓存 key)
|
|
242
|
+
* @param filePath - YAML 文件路径
|
|
243
|
+
* @param schema - 可选 Zod Schema(不传则跳过校验)
|
|
244
|
+
* @returns 成功时返回解析后的配置数据;失败时返回 HaiError
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* const result = config.load('core', './config/_core.yml', CoreConfigSchema)
|
|
249
|
+
* if (result.success) {
|
|
250
|
+
* // result.data 为校验后的配置
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
load(name, filePath, schema) {
|
|
255
|
+
return loadAndCache(name, filePath, schema);
|
|
256
|
+
},
|
|
257
|
+
/**
|
|
258
|
+
* 验证已加载的配置数据。
|
|
259
|
+
*
|
|
260
|
+
* 对缓存中的配置数据重新用 Schema 校验,校验通过后更新缓存。
|
|
261
|
+
*
|
|
262
|
+
* @param name - 配置名称
|
|
263
|
+
* @param schema - Zod 验证模式
|
|
264
|
+
* @returns 校验结果;未加载时返回 NOT_LOADED,格式错误返回 VALIDATION_ERROR
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```ts
|
|
268
|
+
* const result = config.validate('app', AppSchema)
|
|
269
|
+
* if (!result.success) {
|
|
270
|
+
* // result.error.code 可能为 NOT_LOADED 或 VALIDATION_ERROR
|
|
271
|
+
* }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
validate(name, schema) {
|
|
275
|
+
return validateLoadedConfig(name, schema);
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* 获取已加载的配置。
|
|
279
|
+
*
|
|
280
|
+
* @param name - 配置名称
|
|
281
|
+
* @returns 配置数据;未加载时返回 undefined
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* const cfg = config.get<CoreConfig>('core')
|
|
286
|
+
* if (cfg) {
|
|
287
|
+
* // 使用 cfg
|
|
288
|
+
* }
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
get(name) {
|
|
292
|
+
return configCache.get(name)?.data;
|
|
293
|
+
},
|
|
294
|
+
/**
|
|
295
|
+
* 获取配置,不存在时抛出错误。
|
|
296
|
+
*
|
|
297
|
+
* @param name - 配置名称
|
|
298
|
+
* @returns 配置数据
|
|
299
|
+
* @throws 配置未加载时抛出 Error
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```ts
|
|
303
|
+
* try {
|
|
304
|
+
* const cfg = config.getOrThrow<CoreConfig>('core')
|
|
305
|
+
* } catch (e) {
|
|
306
|
+
* // 配置未加载
|
|
307
|
+
* }
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
getOrThrow(name) {
|
|
311
|
+
const data = this.get(name);
|
|
312
|
+
if (data === void 0) {
|
|
313
|
+
throw new Error(i18n.coreM("core_configNotLoaded", { params: { name } }));
|
|
314
|
+
}
|
|
315
|
+
return data;
|
|
316
|
+
},
|
|
317
|
+
/**
|
|
318
|
+
* 重新加载配置。
|
|
319
|
+
*
|
|
320
|
+
* 从磁盘重新读取配置文件并更新缓存,同时通知所有 watch 回调。
|
|
321
|
+
*
|
|
322
|
+
* @param name - 配置名称
|
|
323
|
+
* @returns 重载结果;未加载时返回 NOT_LOADED
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* const result = config.reload('app')
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
reload(name) {
|
|
331
|
+
return reloadAndNotify(name);
|
|
332
|
+
},
|
|
333
|
+
/**
|
|
334
|
+
* 检查配置是否已加载。
|
|
335
|
+
*
|
|
336
|
+
* @param name - 配置名称
|
|
337
|
+
* @returns 是否已加载到缓存
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* if (config.has('db')) {
|
|
342
|
+
* const dbCfg = config.get('db')
|
|
343
|
+
* }
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
has(name) {
|
|
347
|
+
return configCache.has(name);
|
|
348
|
+
},
|
|
349
|
+
/**
|
|
350
|
+
* 清除配置缓存(同时停止对应监听)。
|
|
351
|
+
*
|
|
352
|
+
* @param name - 配置名称;不传则清除全部
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```ts
|
|
356
|
+
* config.clear('app') // 清除单个
|
|
357
|
+
* config.clear() // 清除全部
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
clear(name) {
|
|
361
|
+
clearCache(name);
|
|
362
|
+
},
|
|
363
|
+
/**
|
|
364
|
+
* 获取所有已加载的配置名称。
|
|
365
|
+
*
|
|
366
|
+
* @returns 配置名称数组
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* const names = config.keys() // ['core', 'db', 'app']
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
keys() {
|
|
374
|
+
return Array.from(configCache.keys());
|
|
375
|
+
},
|
|
376
|
+
/**
|
|
377
|
+
* 监听配置文件变更并自动重载。
|
|
378
|
+
*
|
|
379
|
+
* 文件变更时自动重新加载并调用回调。配置未加载时立即回调 NOT_LOADED 错误。
|
|
380
|
+
*
|
|
381
|
+
* @param name - 配置名称
|
|
382
|
+
* @param callback - 配置变更回调,接收新配置或错误
|
|
383
|
+
* @returns 取消监听函数
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* const unwatch = config.watch('app', (cfg, error) => {
|
|
388
|
+
* if (error) { core.logger.error('reload failed', { error }); return }
|
|
389
|
+
* core.logger.info('config updated', { cfg })
|
|
390
|
+
* })
|
|
391
|
+
* // 取消监听
|
|
392
|
+
* unwatch()
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
watch(name, callback) {
|
|
396
|
+
return registerWatch(name, callback);
|
|
397
|
+
},
|
|
398
|
+
/**
|
|
399
|
+
* 停止配置文件监听。
|
|
400
|
+
*
|
|
401
|
+
* @param name - 配置名称;不传则停止所有监听
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```ts
|
|
405
|
+
* config.unwatch('app') // 停止单个
|
|
406
|
+
* config.unwatch() // 停止全部
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
unwatch(name) {
|
|
410
|
+
stopWatching(name);
|
|
411
|
+
},
|
|
412
|
+
/**
|
|
413
|
+
* 检查是否正在监听某个配置。
|
|
414
|
+
*
|
|
415
|
+
* @param name - 配置名称
|
|
416
|
+
* @returns 是否有活跃的 watcher
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* if (config.isWatching('app')) {
|
|
421
|
+
* config.unwatch('app')
|
|
422
|
+
* }
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
isWatching(name) {
|
|
426
|
+
return watchEntries.has(name);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
if (process2.platform === "win32" && process2.stdout.isTTY) {
|
|
430
|
+
try {
|
|
431
|
+
execSync("chcp 65001 > nul", { stdio: "ignore" });
|
|
432
|
+
if (process2.stdout.setDefaultEncoding) {
|
|
433
|
+
process2.stdout.setDefaultEncoding("utf8");
|
|
434
|
+
}
|
|
435
|
+
if (process2.stderr.setDefaultEncoding) {
|
|
436
|
+
process2.stderr.setDefaultEncoding("utf8");
|
|
437
|
+
}
|
|
438
|
+
process2.env.LANG = "zh_CN.UTF-8";
|
|
439
|
+
process2.env.LC_ALL = "zh_CN.UTF-8";
|
|
440
|
+
process2.env.PYTHONIOENCODING = "utf-8";
|
|
441
|
+
} catch (_e) {
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
var globalLevel = "info";
|
|
445
|
+
var globalFormat = "pretty";
|
|
446
|
+
var globalContext = {};
|
|
447
|
+
var globalRedact = [];
|
|
448
|
+
var defaultLogger = null;
|
|
449
|
+
function configureLogger(config2) {
|
|
450
|
+
if (config2.level)
|
|
451
|
+
globalLevel = config2.level;
|
|
452
|
+
if (config2.format)
|
|
453
|
+
globalFormat = config2.format;
|
|
454
|
+
if (config2.context)
|
|
455
|
+
globalContext = { ...globalContext, ...config2.context };
|
|
456
|
+
if (config2.redact)
|
|
457
|
+
globalRedact = config2.redact;
|
|
458
|
+
defaultLogger = null;
|
|
459
|
+
}
|
|
460
|
+
function setLogLevel(level) {
|
|
461
|
+
globalLevel = level;
|
|
462
|
+
}
|
|
463
|
+
function getLogLevel() {
|
|
464
|
+
return globalLevel;
|
|
465
|
+
}
|
|
466
|
+
function wrapPino(pinoLogger, context) {
|
|
467
|
+
return {
|
|
468
|
+
trace(message, ctx) {
|
|
469
|
+
pinoLogger.trace({ ...context, ...ctx }, message);
|
|
470
|
+
},
|
|
471
|
+
debug(message, ctx) {
|
|
472
|
+
pinoLogger.debug({ ...context, ...ctx }, message);
|
|
473
|
+
},
|
|
474
|
+
info(message, ctx) {
|
|
475
|
+
pinoLogger.info({ ...context, ...ctx }, message);
|
|
476
|
+
},
|
|
477
|
+
warn(message, ctx) {
|
|
478
|
+
pinoLogger.warn({ ...context, ...ctx }, message);
|
|
479
|
+
},
|
|
480
|
+
error(message, ctx) {
|
|
481
|
+
pinoLogger.error({ ...context, ...ctx }, message);
|
|
482
|
+
},
|
|
483
|
+
fatal(message, ctx) {
|
|
484
|
+
pinoLogger.fatal({ ...context, ...ctx }, message);
|
|
485
|
+
},
|
|
486
|
+
child(childContext) {
|
|
487
|
+
return wrapPino(pinoLogger.child(childContext), { ...context, ...childContext });
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function createLogger(options = {}) {
|
|
492
|
+
const level = options.level ?? globalLevel;
|
|
493
|
+
const format = options.format ?? globalFormat;
|
|
494
|
+
const context = { ...globalContext, ...options.context };
|
|
495
|
+
const pinoOptions = {
|
|
496
|
+
level,
|
|
497
|
+
name: options.name,
|
|
498
|
+
formatters: {
|
|
499
|
+
level: (label) => ({ level: label })
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
if (globalRedact.length > 0) {
|
|
503
|
+
pinoOptions.redact = {
|
|
504
|
+
paths: globalRedact,
|
|
505
|
+
censor: "[REDACTED]"
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
let pinoInstance;
|
|
509
|
+
if (format === "pretty") {
|
|
510
|
+
pinoInstance = pino({
|
|
511
|
+
...pinoOptions,
|
|
512
|
+
transport: {
|
|
513
|
+
target: "pino-pretty",
|
|
514
|
+
options: {
|
|
515
|
+
colorize: true,
|
|
516
|
+
translateTime: "SYS:standard",
|
|
517
|
+
ignore: "pid,hostname"
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
} else {
|
|
522
|
+
pinoInstance = pino(pinoOptions);
|
|
523
|
+
}
|
|
524
|
+
return wrapPino(pinoInstance, context);
|
|
525
|
+
}
|
|
526
|
+
function getLogger() {
|
|
527
|
+
if (!defaultLogger) {
|
|
528
|
+
defaultLogger = createLogger();
|
|
529
|
+
}
|
|
530
|
+
return defaultLogger;
|
|
531
|
+
}
|
|
532
|
+
var logger = {
|
|
533
|
+
configureLogger,
|
|
534
|
+
setLogLevel,
|
|
535
|
+
getLogLevel,
|
|
536
|
+
createLogger,
|
|
537
|
+
getLogger
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// src/core-main.node.ts
|
|
541
|
+
function createNodeCore() {
|
|
542
|
+
const baseCore = createCore({
|
|
543
|
+
createLogger: logger.createLogger,
|
|
544
|
+
getLogger: logger.getLogger,
|
|
545
|
+
configureLogger: logger.configureLogger,
|
|
546
|
+
setLogLevel: logger.setLogLevel,
|
|
547
|
+
getLogLevel: logger.getLogLevel
|
|
548
|
+
});
|
|
549
|
+
return {
|
|
550
|
+
...baseCore,
|
|
551
|
+
/** 配置管理 */
|
|
552
|
+
config,
|
|
553
|
+
/** 初始化 Core */
|
|
554
|
+
init: initCore
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
var core = createNodeCore();
|
|
558
|
+
function scanConfigDir(configDir) {
|
|
559
|
+
const logger2 = core.logger;
|
|
560
|
+
const items = [];
|
|
561
|
+
if (!existsSync(configDir)) {
|
|
562
|
+
logger2.warn(`[core] Config directory not found: ${configDir}`);
|
|
563
|
+
return items;
|
|
564
|
+
}
|
|
565
|
+
const files = readdirSync(configDir).filter(
|
|
566
|
+
(f) => f.endsWith(".yml") || f.endsWith(".yaml")
|
|
567
|
+
);
|
|
568
|
+
for (const file of files) {
|
|
569
|
+
const filePath = join(configDir, file);
|
|
570
|
+
const baseName = file.replace(/\.ya?ml$/, "");
|
|
571
|
+
if (baseName.startsWith("_")) {
|
|
572
|
+
const moduleName = baseName.slice(1);
|
|
573
|
+
items.push({
|
|
574
|
+
name: moduleName,
|
|
575
|
+
filePath
|
|
576
|
+
});
|
|
577
|
+
} else {
|
|
578
|
+
items.push({
|
|
579
|
+
name: baseName,
|
|
580
|
+
filePath
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return items;
|
|
585
|
+
}
|
|
586
|
+
function initCore(options = {}) {
|
|
587
|
+
const startTime = Date.now();
|
|
588
|
+
const logger2 = core.logger;
|
|
589
|
+
if (options.logging) {
|
|
590
|
+
core.logger.configure(options.logging);
|
|
591
|
+
}
|
|
592
|
+
logger2.info("[core] Initializing...");
|
|
593
|
+
let configItems = [];
|
|
594
|
+
if (options.configDir) {
|
|
595
|
+
configItems = scanConfigDir(options.configDir);
|
|
596
|
+
}
|
|
597
|
+
for (const item of configItems) {
|
|
598
|
+
if (item.name === "core") {
|
|
599
|
+
const result = config.load(item.name, item.filePath, CoreConfigSchema);
|
|
600
|
+
if (result.success) {
|
|
601
|
+
if (!options.logging) {
|
|
602
|
+
core.logger.configure(result.data.logging || {});
|
|
603
|
+
}
|
|
604
|
+
logger2.info(`[core] Config loaded: ${item.name} <- ${item.filePath}`);
|
|
605
|
+
} else {
|
|
606
|
+
logger2.error(`[core] Config load failed: ${item.name}`, {
|
|
607
|
+
error: result.error
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
const result = config.load(item.name, item.filePath);
|
|
612
|
+
if (result.success) {
|
|
613
|
+
logger2.info(`[core] Config loaded: ${item.name} <- ${item.filePath}`);
|
|
614
|
+
} else {
|
|
615
|
+
logger2.error(`[core] Config load failed: ${item.name}`, {
|
|
616
|
+
error: result.error
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (options.watchConfig && configItems.length > 0) {
|
|
622
|
+
setupConfigWatch(configItems);
|
|
623
|
+
}
|
|
624
|
+
const duration = Date.now() - startTime;
|
|
625
|
+
logger2.info(`[core] Initialized (${duration}ms)`);
|
|
626
|
+
}
|
|
627
|
+
function setupConfigWatch(configs) {
|
|
628
|
+
const logger2 = core.logger;
|
|
629
|
+
for (const item of configs) {
|
|
630
|
+
config.watch(item.name, (_config, error) => {
|
|
631
|
+
if (error) {
|
|
632
|
+
logger2.error(`[core] Config reload failed: ${item.name}`, { error });
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
logger2.info(`[core] Config reloaded: ${item.name}`);
|
|
636
|
+
});
|
|
637
|
+
logger2.debug(`[core] Config watch enabled: ${item.name}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export { core };
|
|
642
|
+
//# sourceMappingURL=node.js.map
|
|
643
|
+
//# sourceMappingURL=node.js.map
|