@fluojs/config
Advanced tools
@@ -1,1 +0,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"} | ||
| {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,mBAAmB,EAA4C,MAAM,YAAY,CAAC;AA4ChG;;GAEG;AACH,qBAAa,YAAY;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,UAAU,YAAY;CA4BtE"} |
+61
-6
@@ -0,5 +1,53 @@ | ||
| let _initClass; | ||
| function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; } | ||
| function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } | ||
| function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } | ||
| function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; } | ||
| function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; } | ||
| import { Inject } from '@fluojs/core'; | ||
| import { defineModuleMetadata } from '@fluojs/core/internal'; | ||
| import { loadConfig } from './load.js'; | ||
| import { createConfigReloader, loadConfig } from './load.js'; | ||
| import { snapshotConfigModuleOptions } from './options.js'; | ||
| import { ConfigService, createConfigServiceFromSnapshot } from './service.js'; | ||
| import { ConfigService, createConfigServiceFromSnapshot, replaceConfigServiceSnapshotUnchecked } from './service.js'; | ||
| const CONFIG_MODULE_WATCH_OPTIONS = Symbol('fluo.config.module-watch-options'); | ||
| let _ConfigModuleWatchMan; | ||
| class ConfigModuleWatchManager { | ||
| static { | ||
| [_ConfigModuleWatchMan, _initClass] = _applyDecs(this, [Inject(ConfigService, CONFIG_MODULE_WATCH_OPTIONS)], []).c; | ||
| } | ||
| reloader; | ||
| reloadForwarder; | ||
| constructor(config, options) { | ||
| this.config = config; | ||
| this.options = options; | ||
| } | ||
| onApplicationBootstrap() { | ||
| if (!this.options.watch) { | ||
| return; | ||
| } | ||
| if (this.reloader) { | ||
| return; | ||
| } | ||
| this.reloader = createConfigReloader(this.options); | ||
| this.reloadForwarder = this.reloader.subscribe(snapshot => { | ||
| const previousConfig = this.config.snapshot(); | ||
| try { | ||
| replaceConfigServiceSnapshotUnchecked(this.config, snapshot); | ||
| } catch (error) { | ||
| replaceConfigServiceSnapshotUnchecked(this.config, previousConfig); | ||
| throw error; | ||
| } | ||
| }); | ||
| } | ||
| onModuleDestroy() { | ||
| this.reloadForwarder?.unsubscribe(); | ||
| this.reloadForwarder = undefined; | ||
| this.reloader?.close(); | ||
| this.reloader = undefined; | ||
| } | ||
| static { | ||
| _initClass(); | ||
| } | ||
| } | ||
| /** | ||
@@ -31,9 +79,16 @@ * Module facade that wires normalized configuration into the application container. | ||
| class ConfigModuleImpl extends ConfigModule {} | ||
| const providers = [{ | ||
| provide: ConfigService, | ||
| useFactory: () => createConfigServiceFromSnapshot(loadConfig(loadOptions)) | ||
| }]; | ||
| if (loadOptions.watch) { | ||
| providers.push({ | ||
| provide: CONFIG_MODULE_WATCH_OPTIONS, | ||
| useValue: loadOptions | ||
| }, _ConfigModuleWatchMan); | ||
| } | ||
| defineModuleMetadata(ConfigModuleImpl, { | ||
| global: loadOptions.global ?? true, | ||
| exports: [ConfigService], | ||
| providers: [{ | ||
| provide: ConfigService, | ||
| useFactory: () => createConfigServiceFromSnapshot(loadConfig(loadOptions)) | ||
| }] | ||
| providers | ||
| }); | ||
@@ -40,0 +95,0 @@ return ConfigModuleImpl; |
+4
-3
@@ -11,3 +11,3 @@ { | ||
| ], | ||
| "version": "1.0.0-beta.7", | ||
| "version": "1.0.0-beta.8", | ||
| "private": false, | ||
@@ -42,6 +42,7 @@ "license": "MIT", | ||
| "dotenv-expand": "^11.0.0", | ||
| "@fluojs/core": "^1.0.0-beta.4" | ||
| "@fluojs/core": "^1.0.0-beta.5" | ||
| }, | ||
| "devDependencies": { | ||
| "vitest": "^3.2.4" | ||
| "vitest": "^3.2.4", | ||
| "@fluojs/di": "^1.0.0-beta.7" | ||
| }, | ||
@@ -48,0 +49,0 @@ "scripts": { |
+4
-3
@@ -65,4 +65,5 @@ # @fluojs/config | ||
| @Inject(ConfigService) | ||
| class MyService { | ||
| constructor(@Inject(ConfigService) private readonly config: ConfigService) { | ||
| constructor(private readonly config: ConfigService) { | ||
| const port = this.config.get('PORT'); | ||
@@ -100,5 +101,5 @@ const dbUrl = this.config.getOrThrow('DATABASE_URL'); | ||
| 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을 교체하지 않습니다. | ||
| Module registration과 reloader 생성은 caller-owned options를 저장하기 전에 snapshot으로 분리합니다. `ConfigModule.forRoot(...)`, `ConfigReloadModule.forRoot(...)`, `createConfigReloader(...)`에 넘긴 객체를 나중에 변경해도 bootstrap, manual reload, watch reload 입력은 바뀌지 않습니다. `ConfigModule.forRoot({ watch: true, ... })`를 사용하면 module은 application bootstrap 중 env-file watcher를 시작하고, watch reload가 성공한 뒤 같은 injected `ConfigService` instance를 갱신합니다. 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을 교체하지 않습니다. | ||
| `ConfigReloadModule`은 reload layer이며 standalone config source가 아닙니다. `ConfigModule` 또는 다른 `ConfigService` provider와 함께 사용하세요. 이 모듈은 `ConfigReloadManager`를 등록하고 `CONFIG_RELOADER`를 export합니다. Watcher는 `watch: true`일 때만 생성되며 module shutdown 중에 닫힙니다. | ||
| `ConfigReloadModule`은 명시적으로 주입 가능한 reload layer이며 standalone config source가 아닙니다. manual reload나 subscription을 위해 `CONFIG_RELOADER`가 필요한 caller는 `ConfigModule` 또는 다른 `ConfigService` provider와 함께 사용하세요. `ConfigModule` 또는 `ConfigReloadModule`이 만든 watcher는 `watch: true`일 때만 생성되며 module shutdown 중에 닫힙니다. 같은 env file에 대해서는 한 layer에서만 `watch: true`를 활성화하세요. 자동 `ConfigService` 갱신만 필요하면 `ConfigModule`을 사용하고, subscription/manual reload를 위한 injected reloader 계약이 필요하면 `ConfigReloadModule`을 사용합니다. | ||
@@ -105,0 +106,0 @@ ## 공개 API |
+4
-3
@@ -68,4 +68,5 @@ # @fluojs/config | ||
| @Inject(ConfigService) | ||
| class MyService { | ||
| constructor(@Inject(ConfigService) private config: ConfigService) { | ||
| constructor(private readonly config: ConfigService) { | ||
| const port = this.config.get('PORT'); | ||
@@ -103,5 +104,5 @@ const dbUrl = this.config.getOrThrow('DATABASE_URL'); | ||
| 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. | ||
| 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. When `ConfigModule.forRoot({ watch: true, ... })` is used, the module starts an env-file watcher during application bootstrap and updates the same injected `ConfigService` instance after successful 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. | ||
| `ConfigReloadModule` is the reload layer, not a standalone config source. Pair it with `ConfigModule` or another `ConfigService` provider; it registers `ConfigReloadManager` and exports `CONFIG_RELOADER`. The watcher is created only when `watch: true`, and it is closed during module shutdown. | ||
| `ConfigReloadModule` is the explicit injectable reload layer, not a standalone config source. Pair it with `ConfigModule` or another `ConfigService` provider when callers need `CONFIG_RELOADER` for manual reloads or subscriptions. Watchers created by `ConfigModule` or `ConfigReloadModule` are created only when `watch: true`, and they are closed during module shutdown. Enable `watch: true` on one layer for a given env file: use `ConfigModule` for automatic `ConfigService` updates, or `ConfigReloadModule` when callers need the injected reloader contract for subscriptions/manual reloads. | ||
@@ -108,0 +109,0 @@ ## Public API |
66212
11.71%979
7.11%136
0.74%2
100%Updated