@fluojs/config 1.0.0-beta.4 → 1.0.0-beta.6
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.ko.md +2 -0
- package/README.md +2 -0
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +33 -6
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +4 -2
- package/dist/options.d.ts +16 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +51 -0
- package/dist/reload-module.d.ts.map +1 -1
- package/dist/reload-module.js +2 -1
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -91,6 +91,8 @@ class MyService {
|
|
|
91
91
|
|
|
92
92
|
`ConfigReloadManager.reload()`는 리로드 작업을 직렬화합니다. 현재 리로드가 listener 알림을 수행하는 동안 다른 리로드가 요청되면 후속 리로드는 큐에 들어가 활성 알림이 끝난 뒤 적용됩니다. 활성 알림이 실패하면 이전 snapshot을 복구하고 큐에 있던 리로드는 폐기합니다. 동일한 직렬화와 rollback 계약은 `createConfigReloader(...).reload()`에도 적용되며, watch로 시작된 알림 중 큐에 들어간 manual reload도 이 계약을 따릅니다.
|
|
93
93
|
|
|
94
|
+
Module registration과 reloader 생성은 caller-owned options를 저장하기 전에 snapshot으로 분리합니다. `ConfigModule.forRoot(...)`, `ConfigReloadModule.forRoot(...)`, `createConfigReloader(...)`에 넘긴 객체를 나중에 변경해도 bootstrap, manual reload, watch reload 입력은 바뀌지 않습니다. Watch mode에서 시작 시점에 env file이 없으면 빈 file snapshot처럼 취급하고 parent directory를 watch하므로, 나중에 env file을 생성해도 reload가 트리거될 수 있습니다. Watch reload는 reload 전에 최종 env file content를 마지막으로 commit된 watch baseline과 비교하므로, 내용이 바뀌지 않은 저장이나 변경 후 debounce 안에서 원래 내용으로 되돌린 burst는 인프로세스 config snapshot을 교체하지 않습니다.
|
|
95
|
+
|
|
94
96
|
## 공개 API
|
|
95
97
|
|
|
96
98
|
| 클래스/헬퍼 | 설명 |
|
package/README.md
CHANGED
|
@@ -95,6 +95,8 @@ The `schema` option accepts a synchronous [Standard Schema](https://standardsche
|
|
|
95
95
|
|
|
96
96
|
`ConfigReloadManager.reload()` serializes reload work. If another reload is requested while the current reload is notifying listeners, the follow-up reload is queued and applied after the active notification finishes; if the active notification fails, the previous snapshot is restored and the queued reload is discarded. The same serialization and rollback contract applies to `createConfigReloader(...).reload()`, including manual reloads queued during watch-triggered notifications.
|
|
97
97
|
|
|
98
|
+
Module registration and reloader creation snapshot caller-owned options before storing them. Later mutations to objects passed to `ConfigModule.forRoot(...)`, `ConfigReloadModule.forRoot(...)`, or `createConfigReloader(...)` do not affect bootstrap, manual reloads, or watch reloads. In watch mode, a missing env file at startup is treated as an empty file snapshot while the parent directory is watched so creating the env file later can still trigger reload. Watch reloads compare the final env file content with the last committed watch baseline before reloading, so unchanged saves and change-then-revert bursts do not replace the in-process config snapshot.
|
|
99
|
+
|
|
98
100
|
## Public API
|
|
99
101
|
|
|
100
102
|
| Class/Helper | Description |
|
package/dist/load.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../src/load.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../src/load.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAEjB,cAAc,EAKf,MAAM,YAAY,CAAC;AAwZpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAgC/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAEvE"}
|
package/dist/load.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { existsSync, readFileSync, watch } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
4
|
import { FluoError } from '@fluojs/core';
|
|
4
5
|
import { parse as dotenvParse } from 'dotenv';
|
|
5
6
|
import { expand as dotenvExpand } from 'dotenv-expand';
|
|
6
7
|
import { cloneConfigDictionary } from './clone.js';
|
|
8
|
+
import { snapshotConfigLoadOptions } from './options.js';
|
|
7
9
|
const reloadFailureReasons = new WeakMap();
|
|
8
10
|
function markReloadFailure(error, reason) {
|
|
9
11
|
if (typeof error === 'object' && error !== null) {
|
|
@@ -179,6 +181,16 @@ function createSubscription(listeners, listener) {
|
|
|
179
181
|
}
|
|
180
182
|
};
|
|
181
183
|
}
|
|
184
|
+
function hashEnvFileContent(envFile) {
|
|
185
|
+
try {
|
|
186
|
+
return createHash('sha256').update(readFileSync(envFile)).digest('hex');
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
182
194
|
function notifyReloadListeners(listeners, snapshot, reason) {
|
|
183
195
|
for (const listener of listeners) {
|
|
184
196
|
listener(cloneConfigDictionary(snapshot), reason);
|
|
@@ -226,14 +238,27 @@ function applyReload(normalized, state, listeners, reason) {
|
|
|
226
238
|
}
|
|
227
239
|
}
|
|
228
240
|
function startReloaderWatcher(normalized, options, state, listeners, errorListeners) {
|
|
229
|
-
if (!options.watch
|
|
241
|
+
if (!options.watch) {
|
|
230
242
|
return undefined;
|
|
231
243
|
}
|
|
232
|
-
|
|
244
|
+
const watchTarget = existsSync(normalized.envFile) ? normalized.envFile : dirname(normalized.envFile);
|
|
245
|
+
const watchedEnvFileName = basename(normalized.envFile);
|
|
246
|
+
if (!existsSync(watchTarget)) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
return watch(watchTarget, {
|
|
233
250
|
persistent: false
|
|
234
|
-
}, () => {
|
|
251
|
+
}, (_eventType, filename) => {
|
|
252
|
+
if (watchTarget !== normalized.envFile && filename !== null && filename.toString() !== watchedEnvFileName) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
235
255
|
try {
|
|
256
|
+
const nextEnvFileHash = hashEnvFileContent(normalized.envFile);
|
|
257
|
+
if (nextEnvFileHash === state.watchedEnvFileHash) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
236
260
|
applyReload(normalized, state, listeners, 'watch');
|
|
261
|
+
state.watchedEnvFileHash = nextEnvFileHash;
|
|
237
262
|
} catch (error) {
|
|
238
263
|
notifyReloadErrorListeners(errorListeners, error, getReloadFailureReason(error) ?? 'watch');
|
|
239
264
|
}
|
|
@@ -269,16 +294,18 @@ function closeReloader(state, listeners, errorListeners) {
|
|
|
269
294
|
* ```
|
|
270
295
|
*/
|
|
271
296
|
export function createConfigReloader(options) {
|
|
272
|
-
const
|
|
297
|
+
const loadOptions = snapshotConfigLoadOptions(options);
|
|
298
|
+
const normalized = normalizeLoadOptions(loadOptions);
|
|
273
299
|
const state = {
|
|
274
300
|
current: resolveConfig(normalized),
|
|
275
301
|
pendingReloadReason: undefined,
|
|
276
302
|
reloading: false,
|
|
303
|
+
watchedEnvFileHash: hashEnvFileContent(normalized.envFile),
|
|
277
304
|
watcher: undefined
|
|
278
305
|
};
|
|
279
306
|
const listeners = new Set();
|
|
280
307
|
const errorListeners = new Set();
|
|
281
|
-
state.watcher = startReloaderWatcher(normalized,
|
|
308
|
+
state.watcher = startReloaderWatcher(normalized, loadOptions, state, listeners, errorListeners);
|
|
282
309
|
return {
|
|
283
310
|
close() {
|
|
284
311
|
closeReloader(state, listeners, errorListeners);
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;GAEG;AACH,qBAAa,YAAY;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,UAAU,YAAY;CAiBtE"}
|
package/dist/module.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineModuleMetadata } from '@fluojs/core/internal';
|
|
2
2
|
import { loadConfig } from './load.js';
|
|
3
|
+
import { snapshotConfigModuleOptions } from './options.js';
|
|
3
4
|
import { ConfigService, createConfigServiceFromSnapshot } from './service.js';
|
|
4
5
|
/**
|
|
5
6
|
* Module facade that wires normalized configuration into the application container.
|
|
@@ -25,13 +26,14 @@ export class ConfigModule {
|
|
|
25
26
|
* ```
|
|
26
27
|
*/
|
|
27
28
|
static forRoot(options) {
|
|
29
|
+
const loadOptions = snapshotConfigModuleOptions(options);
|
|
28
30
|
class ConfigModuleImpl extends ConfigModule {}
|
|
29
31
|
defineModuleMetadata(ConfigModuleImpl, {
|
|
30
|
-
global:
|
|
32
|
+
global: loadOptions.isGlobal ?? true,
|
|
31
33
|
exports: [ConfigService],
|
|
32
34
|
providers: [{
|
|
33
35
|
provide: ConfigService,
|
|
34
|
-
useFactory: () => createConfigServiceFromSnapshot(loadConfig(
|
|
36
|
+
useFactory: () => createConfigServiceFromSnapshot(loadConfig(loadOptions))
|
|
35
37
|
}]
|
|
36
38
|
});
|
|
37
39
|
return ConfigModuleImpl;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ConfigLoadOptions, ConfigModuleOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a detached snapshot of config module registration options.
|
|
4
|
+
*
|
|
5
|
+
* @param options Caller-owned module options captured at registration time.
|
|
6
|
+
* @returns Options that cannot observe later caller mutations of config dictionaries.
|
|
7
|
+
*/
|
|
8
|
+
export declare function snapshotConfigModuleOptions(options?: ConfigModuleOptions): ConfigModuleOptions;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a detached snapshot of config load and reload options.
|
|
11
|
+
*
|
|
12
|
+
* @param options Caller-owned load options captured by loaders or reload modules.
|
|
13
|
+
* @returns Options that preserve registration-time config dictionary inputs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function snapshotConfigLoadOptions(options?: ConfigLoadOptions): ConfigLoadOptions;
|
|
16
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAsB3F;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,CAU9F;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CAWxF"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { cloneConfigDictionary } from './clone.js';
|
|
2
|
+
function snapshotConfigDictionary(value) {
|
|
3
|
+
return value === undefined ? undefined : cloneConfigDictionary(value);
|
|
4
|
+
}
|
|
5
|
+
function snapshotProcessEnv(processEnv) {
|
|
6
|
+
if (processEnv === undefined) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const snapshot = {};
|
|
10
|
+
for (const [key, value] of Object.entries(processEnv)) {
|
|
11
|
+
if (value !== undefined) {
|
|
12
|
+
snapshot[key] = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return Object.freeze(snapshot);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a detached snapshot of config module registration options.
|
|
20
|
+
*
|
|
21
|
+
* @param options Caller-owned module options captured at registration time.
|
|
22
|
+
* @returns Options that cannot observe later caller mutations of config dictionaries.
|
|
23
|
+
*/
|
|
24
|
+
export function snapshotConfigModuleOptions(options) {
|
|
25
|
+
if (options === undefined) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
...options,
|
|
30
|
+
defaults: snapshotConfigDictionary(options.defaults),
|
|
31
|
+
processEnv: snapshotProcessEnv(options.processEnv)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a detached snapshot of config load and reload options.
|
|
37
|
+
*
|
|
38
|
+
* @param options Caller-owned load options captured by loaders or reload modules.
|
|
39
|
+
* @returns Options that preserve registration-time config dictionary inputs.
|
|
40
|
+
*/
|
|
41
|
+
export function snapshotConfigLoadOptions(options) {
|
|
42
|
+
if (options === undefined) {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
return Object.freeze({
|
|
46
|
+
...options,
|
|
47
|
+
defaults: snapshotConfigDictionary(options.defaults),
|
|
48
|
+
processEnv: snapshotProcessEnv(options.processEnv),
|
|
49
|
+
runtimeOverrides: snapshotConfigDictionary(options.runtimeOverrides)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reload-module.d.ts","sourceRoot":"","sources":["../src/reload-module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reload-module.d.ts","sourceRoot":"","sources":["../src/reload-module.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,aAAa,EAEd,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,cAAc,EACd,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAIpB;;GAEG;AACH,eAAO,MAAM,eAAe,eAAiC,CAAC;AAY9D;;GAEG;AACH,qBACa,mBAAoB,YAAW,cAAc;IAQtD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAR1B,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,eAAe,CAAuC;IAC9D,OAAO,CAAC,cAAc,CAAuC;IAC7D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmC;IACnE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwC;gBAGpD,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,iBAAiB;IAG7C,OAAO,IAAI,gBAAgB;IAI3B,MAAM,IAAI,gBAAgB;IAI1B,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,wBAAwB;IAInE,cAAc,CAAC,QAAQ,EAAE,yBAAyB,GAAG,wBAAwB;IAI7E,KAAK,IAAI,IAAI;IAeb,sBAAsB,IAAI,IAAI;IAQ9B,eAAe,IAAI,IAAI;IAIvB,OAAO,CAAC,cAAc;CA8BvB;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,UAAU,kBAAkB;CAsB1E"}
|
package/dist/reload-module.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Inject } from '@fluojs/core';
|
|
|
8
8
|
import { defineModuleMetadata } from '@fluojs/core/internal';
|
|
9
9
|
import { cloneConfigDictionary } from './clone.js';
|
|
10
10
|
import { createConfigReloader } from './load.js';
|
|
11
|
+
import { snapshotConfigLoadOptions } from './options.js';
|
|
11
12
|
import { ConfigService, replaceConfigServiceSnapshotUnchecked } from './service.js';
|
|
12
13
|
const CONFIG_RELOAD_OPTIONS = Symbol('fluo.config.reload-options');
|
|
13
14
|
|
|
@@ -110,7 +111,7 @@ class ConfigReloadManager {
|
|
|
110
111
|
export { _ConfigReloadManager as ConfigReloadManager };
|
|
111
112
|
export class ConfigReloadModule {
|
|
112
113
|
static forRoot(options) {
|
|
113
|
-
const loadOptions = options
|
|
114
|
+
const loadOptions = snapshotConfigLoadOptions(options);
|
|
114
115
|
class ConfigReloadModuleImpl extends ConfigReloadModule {}
|
|
115
116
|
defineModuleMetadata(ConfigReloadModuleImpl, {
|
|
116
117
|
exports: [CONFIG_RELOADER],
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"environment",
|
|
9
9
|
"typed-config"
|
|
10
10
|
],
|
|
11
|
-
"version": "1.0.0-beta.
|
|
11
|
+
"version": "1.0.0-beta.6",
|
|
12
12
|
"private": false,
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@standard-schema/spec": "^1.1.0",
|
|
39
39
|
"dotenv": "^16.0.0",
|
|
40
40
|
"dotenv-expand": "^11.0.0",
|
|
41
|
-
"@fluojs/core": "^1.0.0-beta.
|
|
41
|
+
"@fluojs/core": "^1.0.0-beta.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"vitest": "^3.2.4"
|