@zucker-framework/config 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1307 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ CONFIG_CHANGED_EVENT: () => CONFIG_CHANGED_EVENT,
25
+ CONFIG_CHANGED_TOPIC: () => CONFIG_CHANGED_TOPIC,
26
+ CONFIG_ENCRYPTOR: () => CONFIG_ENCRYPTOR,
27
+ CONFIG_LAYER_PRIORITY: () => CONFIG_LAYER_PRIORITY,
28
+ CONFIG_MODULE_OPTIONS: () => CONFIG_MODULE_OPTIONS,
29
+ CONFIG_SCOPE_REGISTRY: () => CONFIG_SCOPE_REGISTRY,
30
+ ConfigFileWatcher: () => ConfigFileWatcher,
31
+ ConfigGuard: () => ConfigGuard,
32
+ ConfigScopeManager: () => ConfigScopeManager,
33
+ ConfigService: () => ConfigService,
34
+ DATABASE_ADAPTER: () => import_crud.DATABASE_ADAPTER,
35
+ FeatureFlagEvaluator: () => import_feature_flags2.FeatureFlagEvaluator,
36
+ FeatureFlagService: () => FeatureFlagService,
37
+ LAYERED_CONFIG_CHANGED_EVENT: () => LAYERED_CONFIG_CHANGED_EVENT,
38
+ LAYERED_CONFIG_CHANGED_TOPIC: () => LAYERED_CONFIG_CHANGED_TOPIC,
39
+ LayeredConfigService: () => LayeredConfigService,
40
+ ZuckerConfigModule: () => ZuckerConfigModule,
41
+ defaultFeatureFlagEnvKey: () => import_feature_flags2.defaultFeatureFlagEnvKey
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/config.service.ts
46
+ var import_common2 = require("@nestjs/common");
47
+ var import_core = require("@zucker-framework/core");
48
+ var import_eventemitter2 = require("eventemitter2");
49
+
50
+ // src/config-scope-manager.ts
51
+ var import_common = require("@nestjs/common");
52
+ function _ts_decorate(decorators, target, key, desc) {
53
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
54
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
55
+ r = Reflect.decorate(decorators, target, key, desc);
56
+ } else {
57
+ for (var i = decorators.length - 1; i >= 0; i--) {
58
+ if (d = decorators[i]) {
59
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
60
+ }
61
+ }
62
+ }
63
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
64
+ }
65
+ __name(_ts_decorate, "_ts_decorate");
66
+ var ConfigScopeManager = class _ConfigScopeManager {
67
+ static {
68
+ __name(this, "ConfigScopeManager");
69
+ }
70
+ logger = new import_common.Logger(_ConfigScopeManager.name);
71
+ scopes = /* @__PURE__ */ new Map();
72
+ /**
73
+ * Register a fully defined scope.
74
+ */
75
+ register(scope) {
76
+ this.scopes.set(scope.id, scope);
77
+ this.logger.debug(`Config scope registered: ${scope.id} (${scope.name})`);
78
+ }
79
+ /**
80
+ * Add a scope with separate property definitions — mirrors jetlinks ConfigScopeManager.addScope().
81
+ *
82
+ * Reference: jetlinks ConfigScopeManager.addScope(ConfigScope, List<ConfigPropertyDef>)
83
+ */
84
+ addScope(scopePartial, properties) {
85
+ const existing = this.scopes.get(scopePartial.id);
86
+ if (existing) {
87
+ const existingKeys = new Set(existing.properties.map((p) => p.key));
88
+ for (const prop of properties) {
89
+ if (!existingKeys.has(prop.key)) {
90
+ existing.properties.push(prop);
91
+ existingKeys.add(prop.key);
92
+ }
93
+ }
94
+ return;
95
+ }
96
+ const scope = {
97
+ id: scopePartial.id,
98
+ name: scopePartial.name,
99
+ isPublic: scopePartial.publicAccess ?? false,
100
+ publicAccess: scopePartial.publicAccess ?? false,
101
+ properties
102
+ };
103
+ this.scopes.set(scope.id, scope);
104
+ this.logger.debug(`Config scope added: ${scope.id} (${scope.name})`);
105
+ }
106
+ /**
107
+ * Apply customizers — call during module initialization.
108
+ *
109
+ * Reference: jetlinks ConfigScopeProperties.custom()
110
+ */
111
+ applyCustomizers(customizers) {
112
+ for (const customizer of customizers) {
113
+ customizer.custom(this);
114
+ }
115
+ }
116
+ get(scopeId) {
117
+ return this.scopes.get(scopeId);
118
+ }
119
+ getAll() {
120
+ return Array.from(this.scopes.values());
121
+ }
122
+ has(scopeId) {
123
+ return this.scopes.has(scopeId);
124
+ }
125
+ unregister(scopeId) {
126
+ return this.scopes.delete(scopeId);
127
+ }
128
+ /**
129
+ * Get all scopes marked as public access.
130
+ *
131
+ * Reference: jetlinks SystemConfigManagerController checks isPublicAccess()
132
+ * for unauthenticated access.
133
+ */
134
+ getPublicScopes() {
135
+ return Array.from(this.scopes.values()).filter((s) => s.publicAccess);
136
+ }
137
+ getProperties(scopeId) {
138
+ return this.scopes.get(scopeId)?.properties ?? [];
139
+ }
140
+ /**
141
+ * Validate a config value against its scope property definition.
142
+ * Returns error messages if invalid.
143
+ */
144
+ validate(scopeId, key, value) {
145
+ const scope = this.scopes.get(scopeId);
146
+ if (!scope) return [
147
+ `Unknown scope: ${scopeId}`
148
+ ];
149
+ const prop = scope.properties.find((p) => p.key === key);
150
+ if (!prop) return [
151
+ `Unknown property: ${key} in scope ${scopeId}`
152
+ ];
153
+ const errors = [];
154
+ if (prop.readonly) {
155
+ errors.push(`Property ${key} is read-only`);
156
+ }
157
+ if (value === null || value === void 0) {
158
+ return errors;
159
+ }
160
+ const actualType = typeof value;
161
+ switch (prop.type) {
162
+ case "string":
163
+ if (actualType !== "string") errors.push(`Expected string for ${key}, got ${actualType}`);
164
+ break;
165
+ case "number":
166
+ if (actualType !== "number") errors.push(`Expected number for ${key}, got ${actualType}`);
167
+ break;
168
+ case "boolean":
169
+ if (actualType !== "boolean") errors.push(`Expected boolean for ${key}, got ${actualType}`);
170
+ break;
171
+ case "json":
172
+ case "object":
173
+ if (actualType !== "object") errors.push(`Expected object for ${key}, got ${actualType}`);
174
+ break;
175
+ case "password":
176
+ case "encrypted":
177
+ if (actualType !== "string") errors.push(`Expected string for encrypted ${key}, got ${actualType}`);
178
+ break;
179
+ }
180
+ return errors;
181
+ }
182
+ };
183
+ ConfigScopeManager = _ts_decorate([
184
+ (0, import_common.Injectable)()
185
+ ], ConfigScopeManager);
186
+
187
+ // src/config.service.ts
188
+ var import_crud = require("@zucker-framework/crud");
189
+ function _ts_decorate2(decorators, target, key, desc) {
190
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
191
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
192
+ r = Reflect.decorate(decorators, target, key, desc);
193
+ } else {
194
+ for (var i = decorators.length - 1; i >= 0; i--) {
195
+ if (d = decorators[i]) {
196
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
197
+ }
198
+ }
199
+ }
200
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
201
+ }
202
+ __name(_ts_decorate2, "_ts_decorate");
203
+ function _ts_metadata(metadataKey, metadataValue) {
204
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
205
+ return Reflect.metadata(metadataKey, metadataValue);
206
+ }
207
+ }
208
+ __name(_ts_metadata, "_ts_metadata");
209
+ function _ts_param(paramIndex, decorator) {
210
+ return function(target, key) {
211
+ decorator(target, key, paramIndex);
212
+ };
213
+ }
214
+ __name(_ts_param, "_ts_param");
215
+ var CONFIG_CHANGED_EVENT = "zucker.config-changed";
216
+ var CONFIG_CHANGED_TOPIC = "system/config/changed";
217
+ var CONFIG_ENCRYPTOR = "CONFIG_ENCRYPTOR";
218
+ var ConfigService = class _ConfigService {
219
+ static {
220
+ __name(this, "ConfigService");
221
+ }
222
+ db;
223
+ eventEmitter;
224
+ scopeManager;
225
+ encryptor;
226
+ topicEventBus;
227
+ logger = new import_common2.Logger(_ConfigService.name);
228
+ cache = /* @__PURE__ */ new Map();
229
+ static CACHE_MAX_SIZE = 1024;
230
+ constructor(db, eventEmitter, scopeManager, encryptor, topicEventBus) {
231
+ this.db = db;
232
+ this.eventEmitter = eventEmitter;
233
+ this.scopeManager = scopeManager;
234
+ this.encryptor = encryptor;
235
+ this.topicEventBus = topicEventBus;
236
+ }
237
+ /**
238
+ * 获取指定 scope 和 key 的配置值
239
+ * @param scope 配置域
240
+ * @param key 配置键
241
+ * @returns 配置值,不存在时返回 undefined
242
+ */
243
+ async get(scope, key) {
244
+ const cacheKey = `${scope}:${key}`;
245
+ if (this.cache.has(cacheKey)) {
246
+ return this.cache.get(cacheKey);
247
+ }
248
+ const record = await this.db.model("config").findUnique({
249
+ where: {
250
+ scope_key: {
251
+ scope,
252
+ key
253
+ }
254
+ }
255
+ });
256
+ if (!record) return void 0;
257
+ let value = record.value;
258
+ if (this.encryptor && this.isEncryptedProperty(scope, key)) {
259
+ try {
260
+ value = await this.encryptor.decrypt(value);
261
+ } catch (decryptError) {
262
+ this.logger.error(`Failed to decrypt config ${scope}.${key}: ${decryptError instanceof Error ? decryptError.message : String(decryptError)}`);
263
+ return void 0;
264
+ }
265
+ }
266
+ while (this.cache.size >= _ConfigService.CACHE_MAX_SIZE) {
267
+ const firstKey = this.cache.keys().next().value;
268
+ this.cache.delete(firstKey);
269
+ }
270
+ this.cache.set(cacheKey, value);
271
+ return value;
272
+ }
273
+ /**
274
+ * Get a config value with a fallback default.
275
+ * If no stored value exists, checks the scope definition default,
276
+ * then falls back to the provided default.
277
+ *
278
+ * Reference: jetlinks getProperties merges defaults from ConfigPropertyDef.
279
+ */
280
+ async getOrDefault(scope, key, defaultValue) {
281
+ const value = await this.get(scope, key);
282
+ if (value !== void 0) return value;
283
+ if (this.scopeManager?.has(scope)) {
284
+ const prop = this.scopeManager.getProperties(scope).find((p) => p.key === key);
285
+ if (prop?.defaultValue !== void 0) return prop.defaultValue;
286
+ }
287
+ return defaultValue;
288
+ }
289
+ /**
290
+ * 设置配置值(自动触发变更事件)
291
+ * @param scope 配置域
292
+ * @param key 配置键
293
+ * @param value 配置值
294
+ * @throws ValidationException 校验失败时
295
+ */
296
+ async set(scope, key, value) {
297
+ if (this.scopeManager?.has(scope)) {
298
+ const errors = this.scopeManager.validate(scope, key, value);
299
+ if (errors.length > 0) {
300
+ throw new import_core.ValidationException({
301
+ [key]: errors
302
+ }, `Config validation failed: ${errors.join(", ")}`);
303
+ }
304
+ }
305
+ const cacheKey = `${scope}:${key}`;
306
+ let oldValue = this.cache.get(cacheKey);
307
+ if (oldValue === void 0) {
308
+ const existing = await this.db.model("config").findUnique({
309
+ where: {
310
+ scope_key: {
311
+ scope,
312
+ key
313
+ }
314
+ }
315
+ });
316
+ if (existing) {
317
+ oldValue = existing.value;
318
+ }
319
+ }
320
+ let storedValue = value;
321
+ if (this.encryptor && this.isEncryptedProperty(scope, key) && typeof value === "string") {
322
+ storedValue = await this.encryptor.encrypt(value);
323
+ }
324
+ await this.db.model("config").update({
325
+ where: {
326
+ scope_key: {
327
+ scope,
328
+ key
329
+ }
330
+ },
331
+ data: {
332
+ value: storedValue
333
+ }
334
+ }).catch(async (updateError) => {
335
+ this.logger.debug(`Config key ${scope}.${key} not found, creating: ${updateError instanceof Error ? updateError.message : String(updateError)}`);
336
+ await this.db.model("config").create({
337
+ data: {
338
+ scope,
339
+ key,
340
+ value: storedValue
341
+ }
342
+ });
343
+ });
344
+ while (this.cache.size >= _ConfigService.CACHE_MAX_SIZE) {
345
+ const firstKey = this.cache.keys().next().value;
346
+ this.cache.delete(firstKey);
347
+ }
348
+ this.cache.set(cacheKey, value);
349
+ await this.recordHistory(scope, key, oldValue, value);
350
+ const payload = {
351
+ scope,
352
+ key,
353
+ oldValue,
354
+ newValue: value
355
+ };
356
+ this.eventEmitter?.emit(CONFIG_CHANGED_EVENT, payload);
357
+ if (this.topicEventBus) {
358
+ await this.topicEventBus.publish(CONFIG_CHANGED_TOPIC, payload);
359
+ }
360
+ this.logger.debug(`Config updated: ${scope}.${key}`);
361
+ }
362
+ /**
363
+ * Set multiple properties at once for a scope.
364
+ * Performs batch validation first, then persists all values and emits
365
+ * a single consolidated event — mirrors jetlinks ConfigManager.setProperties(scope, Map)
366
+ * which does repository.save + cache.evict instead of N individual updates.
367
+ */
368
+ async setProperties(scope, values) {
369
+ const entries = Object.entries(values);
370
+ if (entries.length === 0) return;
371
+ if (this.scopeManager?.has(scope)) {
372
+ const allErrors = {};
373
+ for (const [key, value] of entries) {
374
+ const errors = this.scopeManager.validate(scope, key, value);
375
+ if (errors.length > 0) {
376
+ allErrors[key] = errors;
377
+ }
378
+ }
379
+ if (Object.keys(allErrors).length > 0) {
380
+ throw new import_core.ValidationException(allErrors, `Config batch validation failed`);
381
+ }
382
+ }
383
+ const changes = [];
384
+ await this.db.$transaction(async (tx) => {
385
+ for (const [key, value] of entries) {
386
+ const cacheKey = `${scope}:${key}`;
387
+ const oldValue = this.cache.get(cacheKey);
388
+ let storedValue = value;
389
+ if (this.encryptor && this.isEncryptedProperty(scope, key) && typeof value === "string") {
390
+ storedValue = await this.encryptor.encrypt(value);
391
+ }
392
+ await tx.model("config").update({
393
+ where: {
394
+ scope_key: {
395
+ scope,
396
+ key
397
+ }
398
+ },
399
+ data: {
400
+ value: storedValue
401
+ }
402
+ }).catch(async (updateError) => {
403
+ this.logger.debug(`Config key ${scope}.${key} not found, creating: ${updateError instanceof Error ? updateError.message : String(updateError)}`);
404
+ await tx.model("config").create({
405
+ data: {
406
+ scope,
407
+ key,
408
+ value: storedValue
409
+ }
410
+ });
411
+ });
412
+ if (this.cache.size >= _ConfigService.CACHE_MAX_SIZE) {
413
+ const firstKey = this.cache.keys().next().value;
414
+ this.cache.delete(firstKey);
415
+ }
416
+ this.cache.set(cacheKey, value);
417
+ changes.push({
418
+ scope,
419
+ key,
420
+ oldValue,
421
+ newValue: value
422
+ });
423
+ }
424
+ });
425
+ for (const payload of changes) {
426
+ this.eventEmitter?.emit(CONFIG_CHANGED_EVENT, payload);
427
+ }
428
+ if (this.topicEventBus) {
429
+ for (const payload of changes) {
430
+ await this.topicEventBus.publish(CONFIG_CHANGED_TOPIC, payload);
431
+ }
432
+ }
433
+ this.logger.debug(`Config batch updated: ${scope} (${entries.length} properties)`);
434
+ }
435
+ /**
436
+ * 获取指定 scope 下的所有配置
437
+ * @param scope 配置域
438
+ * @returns 键值对集合(包含默认值)
439
+ */
440
+ async getAll(scope) {
441
+ const records = await this.db.model("config").findMany({
442
+ where: {
443
+ scope
444
+ }
445
+ });
446
+ const result = {};
447
+ if (this.scopeManager?.has(scope)) {
448
+ const props = this.scopeManager.getProperties(scope);
449
+ for (const prop of props) {
450
+ if (prop.defaultValue !== void 0) {
451
+ result[prop.key] = prop.defaultValue;
452
+ }
453
+ }
454
+ }
455
+ for (const record of records) {
456
+ let value = record.value;
457
+ if (this.encryptor && this.isEncryptedProperty(scope, record.key)) {
458
+ try {
459
+ value = await this.encryptor.decrypt(value);
460
+ } catch (decryptError) {
461
+ this.logger.error(`Failed to decrypt config ${scope}.${record.key}: ${decryptError instanceof Error ? decryptError.message : String(decryptError)}`);
462
+ continue;
463
+ }
464
+ }
465
+ result[record.key] = value;
466
+ if (this.cache.size >= _ConfigService.CACHE_MAX_SIZE) {
467
+ const firstKey = this.cache.keys().next().value;
468
+ this.cache.delete(firstKey);
469
+ }
470
+ this.cache.set(`${scope}:${record.key}`, value);
471
+ }
472
+ return result;
473
+ }
474
+ /**
475
+ * Remove a specific config property.
476
+ */
477
+ async remove(scope, key) {
478
+ const cacheKey = `${scope}:${key}`;
479
+ let oldValue = this.cache.get(cacheKey);
480
+ if (oldValue === void 0) {
481
+ const existing = await this.db.model("config").findUnique({
482
+ where: {
483
+ scope_key: {
484
+ scope,
485
+ key
486
+ }
487
+ }
488
+ });
489
+ if (existing) {
490
+ oldValue = existing.value;
491
+ }
492
+ }
493
+ await this.db.model("config").delete({
494
+ where: {
495
+ scope_key: {
496
+ scope,
497
+ key
498
+ }
499
+ }
500
+ }).catch(() => {
501
+ });
502
+ this.cache.delete(cacheKey);
503
+ const payload = {
504
+ scope,
505
+ key,
506
+ oldValue,
507
+ newValue: void 0
508
+ };
509
+ this.eventEmitter?.emit(CONFIG_CHANGED_EVENT, payload);
510
+ if (this.topicEventBus) {
511
+ await this.topicEventBus.publish(CONFIG_CHANGED_TOPIC, payload);
512
+ }
513
+ }
514
+ /**
515
+ * 监听配置变更事件
516
+ * @param callback 变更回调
517
+ * @returns 订阅句柄,调用 unsubscribe() 取消监听
518
+ */
519
+ onChange(callback) {
520
+ if (!this.eventEmitter) return {
521
+ unsubscribe: /* @__PURE__ */ __name(() => void 0, "unsubscribe")
522
+ };
523
+ const ee = this.eventEmitter;
524
+ ee.on(CONFIG_CHANGED_EVENT, callback);
525
+ return {
526
+ unsubscribe: /* @__PURE__ */ __name(() => ee.off(CONFIG_CHANGED_EVENT, callback), "unsubscribe")
527
+ };
528
+ }
529
+ /**
530
+ * 监听指定 scope.key 的配置变更,每次变更时执行回调。
531
+ *
532
+ * @param scope 配置域
533
+ * @param key 配置键(可选,不传则监听整个 scope)
534
+ * @param callback 变更回调
535
+ * @returns 取消监听函数
536
+ */
537
+ watch(scope, key, callback) {
538
+ const handler = /* @__PURE__ */ __name((payload) => {
539
+ if (payload.scope !== scope) return;
540
+ if (key && payload.key !== key) return;
541
+ callback(payload.newValue, payload.oldValue);
542
+ }, "handler");
543
+ if (!this.eventEmitter) return {
544
+ unsubscribe: /* @__PURE__ */ __name(() => void 0, "unsubscribe")
545
+ };
546
+ const ee = this.eventEmitter;
547
+ ee.on(CONFIG_CHANGED_EVENT, handler);
548
+ return {
549
+ unsubscribe: /* @__PURE__ */ __name(() => ee.off(CONFIG_CHANGED_EVENT, handler), "unsubscribe")
550
+ };
551
+ }
552
+ /**
553
+ * 清除配置缓存
554
+ * @param scope 可选的配置域,不传则清除所有缓存
555
+ */
556
+ clearCache(scope) {
557
+ if (scope) {
558
+ const prefix = `${scope}:`;
559
+ for (const key of this.cache.keys()) {
560
+ if (key.startsWith(prefix)) this.cache.delete(key);
561
+ }
562
+ } else {
563
+ this.cache.clear();
564
+ }
565
+ }
566
+ // ─── 配置版本控制 ──────────────────────────────────────────────────
567
+ /**
568
+ * 记录配置变更历史(自动版本号递增)
569
+ * 配置版本控制
570
+ */
571
+ async recordHistory(scope, key, oldValue, newValue) {
572
+ try {
573
+ await this.db.model("configHistory").create({
574
+ data: {
575
+ scope,
576
+ key,
577
+ oldValue: oldValue !== void 0 ? JSON.stringify(oldValue) : null,
578
+ newValue: JSON.stringify(newValue),
579
+ changedAt: /* @__PURE__ */ new Date()
580
+ }
581
+ });
582
+ } catch {
583
+ }
584
+ }
585
+ /**
586
+ * 查询配置变更历史
587
+ * @param scope 配置域
588
+ * @param key 配置键(可选,不传则查询该 scope 所有变更)
589
+ * @param limit 返回条数(默认 20)
590
+ */
591
+ async getHistory(scope, key, limit = 20) {
592
+ try {
593
+ const where = {
594
+ scope
595
+ };
596
+ if (key) where.key = key;
597
+ return await this.db.model("configHistory").findMany({
598
+ where,
599
+ orderBy: {
600
+ changedAt: "desc"
601
+ },
602
+ take: limit
603
+ });
604
+ } catch {
605
+ return [];
606
+ }
607
+ }
608
+ /**
609
+ * 回滚配置到指定历史版本
610
+ * @param historyId 历史记录 ID
611
+ */
612
+ async rollback(historyId) {
613
+ try {
614
+ const record = await this.db.model("configHistory").findUnique({
615
+ where: {
616
+ id: historyId
617
+ }
618
+ });
619
+ if (!record || record.oldValue === null) return;
620
+ const value = JSON.parse(record.oldValue);
621
+ await this.set(record.scope, record.key, value);
622
+ } catch (error) {
623
+ this.logger.warn(`Config rollback failed: ${error instanceof Error ? error.message : String(error)}`);
624
+ }
625
+ }
626
+ /**
627
+ * Check if a property is marked as encrypted in its scope definition.
628
+ */
629
+ isEncryptedProperty(scope, key) {
630
+ if (!this.scopeManager) return false;
631
+ const props = this.scopeManager.getProperties(scope);
632
+ const prop = props.find((p) => p.key === key);
633
+ return prop?.encrypted === true;
634
+ }
635
+ };
636
+ ConfigService = _ts_decorate2([
637
+ (0, import_common2.Injectable)(),
638
+ _ts_param(0, (0, import_common2.Inject)(import_crud.DATABASE_ADAPTER)),
639
+ _ts_param(1, (0, import_common2.Optional)()),
640
+ _ts_param(2, (0, import_common2.Optional)()),
641
+ _ts_param(3, (0, import_common2.Optional)()),
642
+ _ts_param(3, (0, import_common2.Inject)(CONFIG_ENCRYPTOR)),
643
+ _ts_param(4, (0, import_common2.Optional)()),
644
+ _ts_param(4, (0, import_common2.Inject)("TOPIC_EVENT_BUS")),
645
+ _ts_metadata("design:type", Function),
646
+ _ts_metadata("design:paramtypes", [
647
+ typeof DatabaseAdapter === "undefined" ? Object : DatabaseAdapter,
648
+ typeof import_eventemitter2.EventEmitter2 === "undefined" ? Object : import_eventemitter2.EventEmitter2,
649
+ typeof ConfigScopeManager === "undefined" ? Object : ConfigScopeManager,
650
+ typeof ConfigEncryptor === "undefined" ? Object : ConfigEncryptor,
651
+ typeof TopicEventBusLike === "undefined" ? Object : TopicEventBusLike
652
+ ])
653
+ ], ConfigService);
654
+
655
+ // src/config.module.ts
656
+ var import_common4 = require("@nestjs/common");
657
+
658
+ // src/layered-config.service.ts
659
+ var import_common3 = require("@nestjs/common");
660
+ var import_core2 = require("@zucker-framework/core");
661
+ var import_eventemitter22 = require("eventemitter2");
662
+ var import_crud2 = require("@zucker-framework/crud");
663
+ function _ts_decorate3(decorators, target, key, desc) {
664
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
665
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
666
+ r = Reflect.decorate(decorators, target, key, desc);
667
+ } else {
668
+ for (var i = decorators.length - 1; i >= 0; i--) {
669
+ if (d = decorators[i]) {
670
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
671
+ }
672
+ }
673
+ }
674
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
675
+ }
676
+ __name(_ts_decorate3, "_ts_decorate");
677
+ function _ts_metadata2(metadataKey, metadataValue) {
678
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
679
+ return Reflect.metadata(metadataKey, metadataValue);
680
+ }
681
+ }
682
+ __name(_ts_metadata2, "_ts_metadata");
683
+ function _ts_param2(paramIndex, decorator) {
684
+ return function(target, key) {
685
+ decorator(target, key, paramIndex);
686
+ };
687
+ }
688
+ __name(_ts_param2, "_ts_param");
689
+ var CONFIG_LAYER_PRIORITY = {
690
+ system: 0,
691
+ tenant: 1,
692
+ user: 2
693
+ };
694
+ var LAYERED_CONFIG_CHANGED_EVENT = "zucker.layered-config-changed";
695
+ var LAYERED_CONFIG_CHANGED_TOPIC = "system/config/layered-changed";
696
+ var LayeredConfigService = class _LayeredConfigService {
697
+ static {
698
+ __name(this, "LayeredConfigService");
699
+ }
700
+ db;
701
+ scopeManager;
702
+ eventEmitter;
703
+ topicEventBus;
704
+ logger = new import_common3.Logger(_LayeredConfigService.name);
705
+ cache = /* @__PURE__ */ new Map();
706
+ constructor(db, scopeManager, eventEmitter, topicEventBus) {
707
+ this.db = db;
708
+ this.scopeManager = scopeManager;
709
+ this.eventEmitter = eventEmitter;
710
+ this.topicEventBus = topicEventBus;
711
+ }
712
+ /**
713
+ * Get a config value, resolving through layers (user > tenant > system > default).
714
+ */
715
+ async get(scope, key, context) {
716
+ if (context?.userId) {
717
+ const userValue = await this.getLayerValue(scope, key, "user", context.userId);
718
+ if (userValue !== void 0) return userValue;
719
+ }
720
+ if (context?.tenantId) {
721
+ const tenantValue = await this.getLayerValue(scope, key, "tenant", context.tenantId);
722
+ if (tenantValue !== void 0) return tenantValue;
723
+ }
724
+ const systemValue = await this.getLayerValue(scope, key, "system");
725
+ if (systemValue !== void 0) return systemValue;
726
+ const scopeDef = this.scopeManager.get(scope);
727
+ if (scopeDef) {
728
+ const prop = scopeDef.properties.find((p) => p.key === key);
729
+ if (prop) return prop.defaultValue;
730
+ }
731
+ return void 0;
732
+ }
733
+ /**
734
+ * Set a config value at a specific layer.
735
+ */
736
+ async set(scope, key, value, layer, ownerId) {
737
+ const errors = this.scopeManager.validate(scope, key, value);
738
+ if (errors.length > 0) {
739
+ throw new import_core2.ValidationException({
740
+ [key]: errors
741
+ }, `Config validation failed: ${errors.join(", ")}`);
742
+ }
743
+ const compositeKey = this.buildLayerKey(scope, key, layer, ownerId);
744
+ const oldValue = await this.getLayerValue(scope, key, layer, ownerId);
745
+ await this.db.model("config").update({
746
+ where: {
747
+ scope_key: {
748
+ scope: compositeKey,
749
+ key
750
+ }
751
+ },
752
+ data: {
753
+ value,
754
+ updatedAt: /* @__PURE__ */ new Date()
755
+ }
756
+ }).catch(async () => {
757
+ await this.db.model("config").create({
758
+ data: {
759
+ scope: compositeKey,
760
+ key,
761
+ value
762
+ }
763
+ });
764
+ });
765
+ const payload = {
766
+ scope,
767
+ key,
768
+ layer,
769
+ oldValue,
770
+ newValue: value
771
+ };
772
+ this.eventEmitter?.emit(LAYERED_CONFIG_CHANGED_EVENT, payload);
773
+ if (this.topicEventBus) {
774
+ await this.topicEventBus.publish(LAYERED_CONFIG_CHANGED_TOPIC, payload);
775
+ }
776
+ }
777
+ /**
778
+ * Get all values for a scope, merged across layers.
779
+ */
780
+ async getAll(scope, context) {
781
+ const result = {};
782
+ const scopeDef = this.scopeManager.get(scope);
783
+ if (scopeDef) {
784
+ for (const prop of scopeDef.properties) {
785
+ if (prop.defaultValue !== void 0) {
786
+ result[prop.key] = prop.defaultValue;
787
+ }
788
+ }
789
+ }
790
+ const systemRecords = await this.getLayerRecords(scope, "system");
791
+ for (const [k, v] of Object.entries(systemRecords)) {
792
+ result[k] = v;
793
+ }
794
+ if (context?.tenantId) {
795
+ const tenantRecords = await this.getLayerRecords(scope, "tenant", context.tenantId);
796
+ for (const [k, v] of Object.entries(tenantRecords)) {
797
+ result[k] = v;
798
+ }
799
+ }
800
+ if (context?.userId) {
801
+ const userRecords = await this.getLayerRecords(scope, "user", context.userId);
802
+ for (const [k, v] of Object.entries(userRecords)) {
803
+ result[k] = v;
804
+ }
805
+ }
806
+ return result;
807
+ }
808
+ /**
809
+ * Set multiple properties at a specific layer.
810
+ * Validates all entries first, then persists in batch.
811
+ *
812
+ * Reference: jetlinks ConfigManager.setProperties(scope, Map)
813
+ */
814
+ async setProperties(scope, values, layer, ownerId) {
815
+ const entries = Object.entries(values);
816
+ if (entries.length === 0) return;
817
+ const allErrors = {};
818
+ for (const [key, value] of entries) {
819
+ const errors = this.scopeManager.validate(scope, key, value);
820
+ if (errors.length > 0) {
821
+ allErrors[key] = errors;
822
+ }
823
+ }
824
+ if (Object.keys(allErrors).length > 0) {
825
+ throw new import_core2.ValidationException(allErrors, `Layered config batch validation failed`);
826
+ }
827
+ const changes = [];
828
+ await this.db.$transaction(async (txDb) => {
829
+ for (const [key, value] of entries) {
830
+ const compositeKey = this.buildLayerKey(scope, key, layer, ownerId);
831
+ const cacheKey = `${compositeKey}:${key}`;
832
+ const oldValue = this.cache.get(cacheKey);
833
+ await txDb.model("config").update({
834
+ where: {
835
+ scope_key: {
836
+ scope: compositeKey,
837
+ key
838
+ }
839
+ },
840
+ data: {
841
+ value,
842
+ updatedAt: /* @__PURE__ */ new Date()
843
+ }
844
+ }).catch(async () => {
845
+ await txDb.model("config").create({
846
+ data: {
847
+ scope: compositeKey,
848
+ key,
849
+ value
850
+ }
851
+ });
852
+ });
853
+ this.cache.set(cacheKey, value);
854
+ changes.push({
855
+ scope,
856
+ key,
857
+ layer,
858
+ oldValue,
859
+ newValue: value
860
+ });
861
+ }
862
+ });
863
+ for (const payload of changes) {
864
+ this.eventEmitter?.emit(LAYERED_CONFIG_CHANGED_EVENT, payload);
865
+ }
866
+ if (this.topicEventBus) {
867
+ for (const payload of changes) {
868
+ await this.topicEventBus.publish(LAYERED_CONFIG_CHANGED_TOPIC, payload);
869
+ }
870
+ }
871
+ }
872
+ /**
873
+ * Remove a config value at a specific layer.
874
+ */
875
+ async remove(scope, key, layer, ownerId) {
876
+ const compositeKey = this.buildLayerKey(scope, key, layer, ownerId);
877
+ const cacheKey = `${compositeKey}:${key}`;
878
+ const oldValue = this.cache.get(cacheKey);
879
+ await this.db.model("config").delete({
880
+ where: {
881
+ scope_key: {
882
+ scope: compositeKey,
883
+ key
884
+ }
885
+ }
886
+ }).catch(() => {
887
+ });
888
+ this.cache.delete(cacheKey);
889
+ const payload = {
890
+ scope,
891
+ key,
892
+ layer,
893
+ oldValue,
894
+ newValue: void 0
895
+ };
896
+ this.eventEmitter?.emit(LAYERED_CONFIG_CHANGED_EVENT, payload);
897
+ if (this.topicEventBus) {
898
+ await this.topicEventBus.publish(LAYERED_CONFIG_CHANGED_TOPIC, payload);
899
+ }
900
+ }
901
+ /**
902
+ * Clear the in-memory cache.
903
+ * @param scope Optional — only clear entries for this scope prefix
904
+ */
905
+ clearCache(scope) {
906
+ if (scope) {
907
+ for (const key of this.cache.keys()) {
908
+ if (key.startsWith(scope)) this.cache.delete(key);
909
+ }
910
+ } else {
911
+ this.cache.clear();
912
+ }
913
+ }
914
+ async getLayerValue(scope, key, layer, ownerId) {
915
+ const compositeKey = this.buildLayerKey(scope, key, layer, ownerId);
916
+ const cacheKey = `${compositeKey}:${key}`;
917
+ if (this.cache.has(cacheKey)) {
918
+ return this.cache.get(cacheKey);
919
+ }
920
+ const record = await this.db.model("config").findUnique({
921
+ where: {
922
+ scope_key: {
923
+ scope: compositeKey,
924
+ key
925
+ }
926
+ }
927
+ });
928
+ if (record) {
929
+ this.cache.set(cacheKey, record.value);
930
+ }
931
+ return record?.value;
932
+ }
933
+ async getLayerRecords(scope, layer, ownerId) {
934
+ const compositeScope = ownerId ? `${scope}@${layer}:${ownerId}` : `${scope}@${layer}`;
935
+ const records = await this.db.model("config").findMany({
936
+ where: {
937
+ scope: compositeScope
938
+ }
939
+ });
940
+ const result = {};
941
+ for (const record of records) {
942
+ result[record.key] = record.value;
943
+ }
944
+ return result;
945
+ }
946
+ buildLayerKey(scope, _key, layer, ownerId) {
947
+ if (ownerId) {
948
+ return `${scope}@${layer}:${ownerId}`;
949
+ }
950
+ return `${scope}@${layer}`;
951
+ }
952
+ };
953
+ LayeredConfigService = _ts_decorate3([
954
+ (0, import_common3.Injectable)(),
955
+ _ts_param2(0, (0, import_common3.Inject)(import_crud2.DATABASE_ADAPTER)),
956
+ _ts_param2(2, (0, import_common3.Optional)()),
957
+ _ts_param2(3, (0, import_common3.Optional)()),
958
+ _ts_param2(3, (0, import_common3.Inject)("TOPIC_EVENT_BUS")),
959
+ _ts_metadata2("design:type", Function),
960
+ _ts_metadata2("design:paramtypes", [
961
+ typeof DatabaseAdapter === "undefined" ? Object : DatabaseAdapter,
962
+ typeof ConfigScopeManager === "undefined" ? Object : ConfigScopeManager,
963
+ typeof import_eventemitter22.EventEmitter2 === "undefined" ? Object : import_eventemitter22.EventEmitter2,
964
+ typeof TopicEventBusLike === "undefined" ? Object : TopicEventBusLike
965
+ ])
966
+ ], LayeredConfigService);
967
+
968
+ // src/config.module.ts
969
+ function _ts_decorate4(decorators, target, key, desc) {
970
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
971
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
972
+ r = Reflect.decorate(decorators, target, key, desc);
973
+ } else {
974
+ for (var i = decorators.length - 1; i >= 0; i--) {
975
+ if (d = decorators[i]) {
976
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
977
+ }
978
+ }
979
+ }
980
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
981
+ }
982
+ __name(_ts_decorate4, "_ts_decorate");
983
+ var CONFIG_MODULE_OPTIONS = /* @__PURE__ */ Symbol("CONFIG_MODULE_OPTIONS");
984
+ var ZuckerConfigModule = class _ZuckerConfigModule {
985
+ static {
986
+ __name(this, "ZuckerConfigModule");
987
+ }
988
+ static forRoot(options) {
989
+ const providers = [
990
+ ConfigService,
991
+ ConfigScopeManager,
992
+ LayeredConfigService
993
+ ];
994
+ if (options?.encryptor) {
995
+ providers.push({
996
+ provide: CONFIG_ENCRYPTOR,
997
+ useValue: options.encryptor
998
+ });
999
+ }
1000
+ return {
1001
+ module: _ZuckerConfigModule,
1002
+ global: true,
1003
+ providers,
1004
+ exports: [
1005
+ ConfigService,
1006
+ ConfigScopeManager,
1007
+ LayeredConfigService
1008
+ ]
1009
+ };
1010
+ }
1011
+ static forRootAsync(options) {
1012
+ const asyncProviders = this.createAsyncProviders(options);
1013
+ return {
1014
+ module: _ZuckerConfigModule,
1015
+ global: true,
1016
+ imports: options.imports ?? [],
1017
+ providers: [
1018
+ ...asyncProviders,
1019
+ {
1020
+ provide: CONFIG_ENCRYPTOR,
1021
+ useFactory: /* @__PURE__ */ __name((opts) => opts.encryptor ?? null, "useFactory"),
1022
+ inject: [
1023
+ CONFIG_MODULE_OPTIONS
1024
+ ]
1025
+ },
1026
+ ConfigService,
1027
+ ConfigScopeManager,
1028
+ LayeredConfigService
1029
+ ],
1030
+ exports: [
1031
+ ConfigService,
1032
+ ConfigScopeManager,
1033
+ LayeredConfigService
1034
+ ]
1035
+ };
1036
+ }
1037
+ static createAsyncProviders(options) {
1038
+ if (options.useFactory) {
1039
+ return [
1040
+ {
1041
+ provide: CONFIG_MODULE_OPTIONS,
1042
+ useFactory: options.useFactory,
1043
+ inject: options.inject ?? []
1044
+ }
1045
+ ];
1046
+ }
1047
+ const optionsProvider = {
1048
+ provide: CONFIG_MODULE_OPTIONS,
1049
+ useFactory: /* @__PURE__ */ __name(async (factory) => factory.createConfigOptions(), "useFactory"),
1050
+ inject: [
1051
+ options.useClass ?? options.useExisting
1052
+ ]
1053
+ };
1054
+ if (options.useClass) {
1055
+ return [
1056
+ {
1057
+ provide: options.useClass,
1058
+ useClass: options.useClass
1059
+ },
1060
+ optionsProvider
1061
+ ];
1062
+ }
1063
+ return [
1064
+ optionsProvider
1065
+ ];
1066
+ }
1067
+ };
1068
+ ZuckerConfigModule = _ts_decorate4([
1069
+ (0, import_common4.Global)(),
1070
+ (0, import_common4.Module)({})
1071
+ ], ZuckerConfigModule);
1072
+
1073
+ // src/config.guard.ts
1074
+ var import_common5 = require("@nestjs/common");
1075
+ function _ts_decorate5(decorators, target, key, desc) {
1076
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1077
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1078
+ r = Reflect.decorate(decorators, target, key, desc);
1079
+ } else {
1080
+ for (var i = decorators.length - 1; i >= 0; i--) {
1081
+ if (d = decorators[i]) {
1082
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1083
+ }
1084
+ }
1085
+ }
1086
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1087
+ }
1088
+ __name(_ts_decorate5, "_ts_decorate");
1089
+ function _ts_metadata3(metadataKey, metadataValue) {
1090
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
1091
+ return Reflect.metadata(metadataKey, metadataValue);
1092
+ }
1093
+ }
1094
+ __name(_ts_metadata3, "_ts_metadata");
1095
+ function _ts_param3(paramIndex, decorator) {
1096
+ return function(target, key) {
1097
+ decorator(target, key, paramIndex);
1098
+ };
1099
+ }
1100
+ __name(_ts_param3, "_ts_param");
1101
+ var CONFIG_SCOPE_REGISTRY = "CONFIG_SCOPE_REGISTRY";
1102
+ var ConfigGuard = class _ConfigGuard {
1103
+ static {
1104
+ __name(this, "ConfigGuard");
1105
+ }
1106
+ scopeRegistry;
1107
+ logger = new import_common5.Logger(_ConfigGuard.name);
1108
+ constructor(scopeRegistry) {
1109
+ this.scopeRegistry = scopeRegistry;
1110
+ }
1111
+ canActivate(context) {
1112
+ const request = context.switchToHttp().getRequest();
1113
+ const scope = request.params?.scope ?? request.query?.scope;
1114
+ if (!scope) {
1115
+ return this.isAuthenticated(request);
1116
+ }
1117
+ const entry = this.scopeRegistry.get(scope);
1118
+ if (entry?.publicAccess) {
1119
+ this.logger.debug(`\u516C\u5F00\u8BBF\u95EE scope: ${scope}`);
1120
+ return true;
1121
+ }
1122
+ return this.isAuthenticated(request);
1123
+ }
1124
+ isAuthenticated(request) {
1125
+ return !!request.user;
1126
+ }
1127
+ };
1128
+ ConfigGuard = _ts_decorate5([
1129
+ (0, import_common5.Injectable)(),
1130
+ _ts_param3(0, (0, import_common5.Inject)(CONFIG_SCOPE_REGISTRY)),
1131
+ _ts_metadata3("design:type", Function),
1132
+ _ts_metadata3("design:paramtypes", [
1133
+ typeof Map === "undefined" ? Object : Map
1134
+ ])
1135
+ ], ConfigGuard);
1136
+
1137
+ // src/feature-flags.ts
1138
+ var import_common6 = require("@nestjs/common");
1139
+ var import_feature_flags = require("@zucker-framework/feature-flags");
1140
+ var import_feature_flags2 = require("@zucker-framework/feature-flags");
1141
+ function _ts_decorate6(decorators, target, key, desc) {
1142
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1143
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1144
+ r = Reflect.decorate(decorators, target, key, desc);
1145
+ } else {
1146
+ for (var i = decorators.length - 1; i >= 0; i--) {
1147
+ if (d = decorators[i]) {
1148
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1149
+ }
1150
+ }
1151
+ }
1152
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1153
+ }
1154
+ __name(_ts_decorate6, "_ts_decorate");
1155
+ function _ts_metadata4(metadataKey, metadataValue) {
1156
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
1157
+ return Reflect.metadata(metadataKey, metadataValue);
1158
+ }
1159
+ }
1160
+ __name(_ts_metadata4, "_ts_metadata");
1161
+ var FeatureFlagService = class extends import_feature_flags.FeatureFlagEvaluator {
1162
+ static {
1163
+ __name(this, "FeatureFlagService");
1164
+ }
1165
+ constructor() {
1166
+ super((key) => process.env[key]);
1167
+ }
1168
+ };
1169
+ FeatureFlagService = _ts_decorate6([
1170
+ (0, import_common6.Injectable)(),
1171
+ _ts_metadata4("design:type", Function),
1172
+ _ts_metadata4("design:paramtypes", [])
1173
+ ], FeatureFlagService);
1174
+
1175
+ // src/config-watcher.ts
1176
+ var import_common7 = require("@nestjs/common");
1177
+ var import_fs = require("fs");
1178
+ var ConfigFileWatcher = class _ConfigFileWatcher {
1179
+ static {
1180
+ __name(this, "ConfigFileWatcher");
1181
+ }
1182
+ logger = new import_common7.Logger(_ConfigFileWatcher.name);
1183
+ stabilityMs;
1184
+ deleteGraceMs;
1185
+ watched = /* @__PURE__ */ new Map();
1186
+ constructor(options = {}) {
1187
+ this.stabilityMs = options.stabilityMs ?? 1e3;
1188
+ this.deleteGraceMs = options.deleteGraceMs ?? 1700;
1189
+ }
1190
+ /**
1191
+ * 开始监听文件
1192
+ * @returns 取消监听函数
1193
+ */
1194
+ watch(filePath, callback) {
1195
+ if (this.watched.has(filePath)) {
1196
+ this.unwatch(filePath);
1197
+ }
1198
+ const entry = {
1199
+ path: filePath,
1200
+ callback,
1201
+ watcher: null,
1202
+ debounceTimer: null,
1203
+ deleteGraceTimer: null,
1204
+ lastModified: this.getModifiedTime(filePath)
1205
+ };
1206
+ try {
1207
+ entry.watcher = (0, import_fs.watch)(filePath, (eventType) => {
1208
+ this.handleEvent(entry, eventType);
1209
+ });
1210
+ entry.watcher.on("error", (err) => {
1211
+ this.logger.warn(`Watch error on ${filePath}: ${err.message}`);
1212
+ });
1213
+ } catch {
1214
+ this.logger.warn(`Cannot watch ${filePath} \u2014 file may not exist yet`);
1215
+ }
1216
+ this.watched.set(filePath, entry);
1217
+ return () => this.unwatch(filePath);
1218
+ }
1219
+ /**
1220
+ * 停止监听指定文件
1221
+ */
1222
+ unwatch(filePath) {
1223
+ const entry = this.watched.get(filePath);
1224
+ if (!entry) return;
1225
+ entry.watcher?.close();
1226
+ if (entry.debounceTimer) clearTimeout(entry.debounceTimer);
1227
+ if (entry.deleteGraceTimer) clearTimeout(entry.deleteGraceTimer);
1228
+ this.watched.delete(filePath);
1229
+ }
1230
+ /**
1231
+ * 关闭所有监听
1232
+ */
1233
+ close() {
1234
+ for (const path of [
1235
+ ...this.watched.keys()
1236
+ ]) {
1237
+ this.unwatch(path);
1238
+ }
1239
+ }
1240
+ /**
1241
+ * 获取当前监听的文件列表
1242
+ */
1243
+ getWatchedFiles() {
1244
+ return [
1245
+ ...this.watched.keys()
1246
+ ];
1247
+ }
1248
+ handleEvent(entry, eventType) {
1249
+ if (eventType === "rename") {
1250
+ if (!(0, import_fs.existsSync)(entry.path)) {
1251
+ if (entry.deleteGraceTimer) clearTimeout(entry.deleteGraceTimer);
1252
+ entry.deleteGraceTimer = setTimeout(() => {
1253
+ entry.deleteGraceTimer = null;
1254
+ if (!(0, import_fs.existsSync)(entry.path)) {
1255
+ this.logger.warn(`Config file deleted: ${entry.path}`);
1256
+ }
1257
+ }, this.deleteGraceMs);
1258
+ return;
1259
+ }
1260
+ if (entry.deleteGraceTimer) {
1261
+ clearTimeout(entry.deleteGraceTimer);
1262
+ entry.deleteGraceTimer = null;
1263
+ }
1264
+ }
1265
+ if (entry.debounceTimer) clearTimeout(entry.debounceTimer);
1266
+ entry.debounceTimer = setTimeout(() => {
1267
+ entry.debounceTimer = null;
1268
+ const newModified = this.getModifiedTime(entry.path);
1269
+ if (newModified === entry.lastModified) return;
1270
+ entry.lastModified = newModified;
1271
+ this.logger.log(`Config changed: ${entry.path}`);
1272
+ try {
1273
+ entry.callback();
1274
+ } catch (err) {
1275
+ this.logger.error(`Config reload callback failed: ${err}`);
1276
+ }
1277
+ }, this.stabilityMs);
1278
+ }
1279
+ getModifiedTime(filePath) {
1280
+ try {
1281
+ return (0, import_fs.statSync)(filePath).mtimeMs;
1282
+ } catch {
1283
+ return 0;
1284
+ }
1285
+ }
1286
+ };
1287
+ // Annotate the CommonJS export names for ESM import in node:
1288
+ 0 && (module.exports = {
1289
+ CONFIG_CHANGED_EVENT,
1290
+ CONFIG_CHANGED_TOPIC,
1291
+ CONFIG_ENCRYPTOR,
1292
+ CONFIG_LAYER_PRIORITY,
1293
+ CONFIG_MODULE_OPTIONS,
1294
+ CONFIG_SCOPE_REGISTRY,
1295
+ ConfigFileWatcher,
1296
+ ConfigGuard,
1297
+ ConfigScopeManager,
1298
+ ConfigService,
1299
+ DATABASE_ADAPTER,
1300
+ FeatureFlagEvaluator,
1301
+ FeatureFlagService,
1302
+ LAYERED_CONFIG_CHANGED_EVENT,
1303
+ LAYERED_CONFIG_CHANGED_TOPIC,
1304
+ LayeredConfigService,
1305
+ ZuckerConfigModule,
1306
+ defaultFeatureFlagEnvKey
1307
+ });