@fluojs/config 1.0.0-beta.1 → 1.0.0-beta.3

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 CHANGED
@@ -80,15 +80,26 @@ class MyService {
80
80
 
81
81
  `validate` 함수는 모든 소스가 합쳐진 뒤 실행되며, 에러를 던지면 부트스트랩이 즉시 중단됩니다.
82
82
 
83
+ ### 런타임 접근과 리로드 비용 모델
84
+
85
+ `ConfigService.get('a.b.c')`는 dot-path 세그먼트를 순서대로 탐색하므로 조회 비용은 path 깊이에 비례합니다. `get()`, `getOrThrow()`, `snapshot()`이 객체 형태의 값을 반환할 때는 분리된 clone을 반환합니다. 따라서 clone 비용은 반환되는 subtree 크기에 비례하며, 호출자 mutation은 활성 config snapshot에 영향을 주지 않습니다.
86
+
87
+ `ConfigReloadManager.reload()`는 리로드 작업을 직렬화합니다. 현재 리로드가 listener 알림을 수행하는 동안 다른 리로드가 요청되면 후속 리로드는 큐에 들어가 활성 알림이 끝난 뒤 적용됩니다. 활성 알림이 실패하면 이전 snapshot을 복구하고 큐에 있던 리로드는 폐기합니다. 동일한 직렬화와 rollback 계약은 `createConfigReloader(...).reload()`에도 적용되며, watch로 시작된 알림 중 큐에 들어간 manual reload도 이 계약을 따릅니다.
88
+
83
89
  ## 공개 API
84
90
 
85
91
  | 클래스/헬퍼 | 설명 |
86
92
  |---|---|
87
93
  | `ConfigModule` | 설정을 전역 또는 지역으로 등록하기 위한 모듈입니다. |
94
+ | `ConfigReloadModule` | 리로드 매니저를 등록하고 의존성 주입용 공유 `CONFIG_RELOADER` 토큰을 내보냅니다. |
95
+ | `ConfigReloadManager` | 주입된 `ConfigService`의 리로드를 조정하며, 서비스 identity는 유지하고 스냅샷만 리로드 경로로 교체합니다. |
96
+ | `CONFIG_RELOADER` | 공유 config reloader 계약을 위한 주입 토큰입니다. |
88
97
  | `ConfigService` | 설정 값에 타입 안전하게 접근하기 위한 읽기 전용 서비스입니다. 스냅샷 교체는 config reload 경로 내부에만 남습니다. |
89
98
  | `loadConfig(options)` | 설정을 수동으로 로드하기 위한 함수형 엔트리 포인트입니다. |
90
99
  | `createConfigReloader(options)` | 동적 설정 업데이트를 위한 리로더를 생성합니다. |
91
100
 
101
+ `ConfigReloadManager.reload()`는 기존 `ConfigService` 인스턴스를 갱신하므로 소비자는 주입받은 서비스 identity를 유지하면서 새 스냅샷을 관찰합니다. 리로드 listener가 에러를 던지면 매니저는 이전 스냅샷을 복구하고 listener 에러를 다시 던집니다. `createConfigReloader(...).reload()`도 standalone reloader snapshot에 대해 동일한 listener 직렬화와 rollback 동작을 따릅니다.
102
+
92
103
  ## 관련 패키지
93
104
 
94
105
  - `@fluojs/runtime`: 부트스트랩 중 `loadConfig()`를 호출합니다.
package/README.md CHANGED
@@ -85,15 +85,25 @@ Plain objects are deep-merged by key. Arrays and primitive values from higher-pr
85
85
  ### Validation
86
86
  The `validate` function runs after all sources are merged but before the application starts. If it throws, the application bootstrap fails immediately.
87
87
 
88
+ ### Runtime Access and Reload Cost Model
89
+ `ConfigService.get('a.b.c')` resolves dot-path keys by walking each path segment, so lookup cost is proportional to path depth. When `get()`, `getOrThrow()`, or `snapshot()` returns an object-like value, the returned value is a detached clone; clone cost is proportional to the returned subtree size so caller mutations cannot affect the active config snapshot.
90
+
91
+ `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.
92
+
88
93
  ## Public API
89
94
 
90
95
  | Class/Helper | Description |
91
96
  |---|---|
92
97
  | `ConfigModule` | Module for registering configuration globally or locally. |
98
+ | `ConfigReloadModule` | Registers the reload manager and exports the shared `CONFIG_RELOADER` token for dependency injection. |
99
+ | `ConfigReloadManager` | Coordinates reloads for the injected `ConfigService`, preserving service identity while replacing snapshots through the reload path. |
100
+ | `CONFIG_RELOADER` | Injection token for the shared config reloader contract. |
93
101
  | `ConfigService` | Read-only service for typed access to configuration values. Snapshot replacement stays inside the config reload path. |
94
102
  | `loadConfig(options)` | Functional entry point for loading configuration manually. |
95
103
  | `createConfigReloader(options)` | Creates a reloader for dynamic configuration updates. |
96
104
 
105
+ `ConfigReloadManager.reload()` updates the existing `ConfigService` instance so consumers keep their injected service identity while observing the new snapshot. If a reload listener throws, the manager restores the previous snapshot and rethrows the listener error. `createConfigReloader(...).reload()` follows the same listener serialization and rollback behavior for its standalone reloader snapshot.
106
+
97
107
  ## Related Packages
98
108
 
99
109
  - **`@fluojs/runtime`**: Calls `loadConfig` internally during application bootstrap.
@@ -1 +1 @@
1
- {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../src/load.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAEjB,cAAc,EAIf,MAAM,YAAY,CAAC;AA0MpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CA4B/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAEvE"}
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../src/load.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAEjB,cAAc,EAIf,MAAM,YAAY,CAAC;AAqQpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CA8B/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAEvE"}
package/dist/load.js CHANGED
@@ -4,6 +4,18 @@ import { FluoError } from '@fluojs/core';
4
4
  import { parse as dotenvParse } from 'dotenv';
5
5
  import { expand as dotenvExpand } from 'dotenv-expand';
6
6
  import { cloneConfigDictionary } from './clone.js';
7
+ const reloadFailureReasons = new WeakMap();
8
+ function markReloadFailure(error, reason) {
9
+ if (typeof error === 'object' && error !== null) {
10
+ reloadFailureReasons.set(error, reason);
11
+ }
12
+ }
13
+ function getReloadFailureReason(error) {
14
+ if (typeof error !== 'object' || error === null) {
15
+ return undefined;
16
+ }
17
+ return reloadFailureReasons.get(error);
18
+ }
7
19
  function parseEnvContent(content, safeProcessEnv, customParser) {
8
20
  if (customParser) {
9
21
  return customParser(content);
@@ -37,27 +49,32 @@ function normalizeLoadOptions(options) {
37
49
  };
38
50
  }
39
51
  function readEnvFileValues(options) {
40
- if (!existsSync(options.envFile)) {
41
- return {};
52
+ try {
53
+ return parseEnvContent(readFileSync(options.envFile, 'utf8'), options.safeProcessEnv, options.parse);
54
+ } catch (error) {
55
+ if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') {
56
+ return {};
57
+ }
58
+ throw error;
42
59
  }
43
- return parseEnvContent(readFileSync(options.envFile, 'utf8'), options.safeProcessEnv, options.parse);
44
60
  }
45
61
  function isPlainObject(value) {
46
62
  return typeof value === 'object' && value !== null && !Array.isArray(value);
47
63
  }
48
64
  function mergeConfigEntries(target, source) {
49
- const merged = {
50
- ...target
51
- };
52
65
  for (const [key, sourceValue] of Object.entries(source)) {
53
- const targetValue = merged[key];
66
+ const targetValue = target[key];
54
67
  if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
55
- merged[key] = mergeConfigEntries(targetValue, sourceValue);
68
+ mergeConfigEntries(targetValue, sourceValue);
69
+ continue;
70
+ }
71
+ if (isPlainObject(sourceValue)) {
72
+ target[key] = mergeConfigEntries({}, sourceValue);
56
73
  continue;
57
74
  }
58
- merged[key] = cloneConfigDictionary(sourceValue);
75
+ target[key] = cloneConfigDictionary(sourceValue);
59
76
  }
60
- return merged;
77
+ return target;
61
78
  }
62
79
  function mergeConfigSources(...sources) {
63
80
  let merged = {};
@@ -101,7 +118,7 @@ function notifyReloadErrorListeners(listeners, error, reason) {
101
118
  listener(error, reason);
102
119
  }
103
120
  }
104
- function applyReload(normalized, state, listeners, reason) {
121
+ function applyReloadNow(normalized, state, listeners, reason) {
105
122
  const previous = state.current;
106
123
  const next = resolveConfig(normalized);
107
124
  state.current = next;
@@ -113,6 +130,30 @@ function applyReload(normalized, state, listeners, reason) {
113
130
  }
114
131
  return cloneConfigDictionary(next);
115
132
  }
133
+ function applyReload(normalized, state, listeners, reason) {
134
+ if (state.reloading) {
135
+ state.pendingReloadReason = reason;
136
+ return cloneConfigDictionary(state.current);
137
+ }
138
+ state.reloading = true;
139
+ let activeReason = reason;
140
+ try {
141
+ let latest = applyReloadNow(normalized, state, listeners, activeReason);
142
+ while (state.pendingReloadReason) {
143
+ const pendingReason = state.pendingReloadReason;
144
+ state.pendingReloadReason = undefined;
145
+ activeReason = pendingReason;
146
+ latest = applyReloadNow(normalized, state, listeners, pendingReason);
147
+ }
148
+ return latest;
149
+ } catch (error) {
150
+ markReloadFailure(error, activeReason);
151
+ throw error;
152
+ } finally {
153
+ state.pendingReloadReason = undefined;
154
+ state.reloading = false;
155
+ }
156
+ }
116
157
  function startReloaderWatcher(normalized, options, state, listeners, errorListeners) {
117
158
  if (!options.watch || !existsSync(normalized.envFile)) {
118
159
  return undefined;
@@ -123,7 +164,7 @@ function startReloaderWatcher(normalized, options, state, listeners, errorListen
123
164
  try {
124
165
  applyReload(normalized, state, listeners, 'watch');
125
166
  } catch (error) {
126
- notifyReloadErrorListeners(errorListeners, error, 'watch');
167
+ notifyReloadErrorListeners(errorListeners, error, getReloadFailureReason(error) ?? 'watch');
127
168
  }
128
169
  });
129
170
  }
@@ -160,6 +201,8 @@ export function createConfigReloader(options) {
160
201
  const normalized = normalizeLoadOptions(options);
161
202
  const state = {
162
203
  current: resolveConfig(normalized),
204
+ pendingReloadReason: undefined,
205
+ reloading: false,
163
206
  watcher: undefined
164
207
  };
165
208
  const listeners = new Set();
package/dist/module.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineModuleMetadata } from '@fluojs/core/internal';
2
2
  import { loadConfig } from './load.js';
3
- import { ConfigService } from './service.js';
3
+ import { ConfigService, createConfigServiceFromSnapshot } from './service.js';
4
4
  /**
5
5
  * Module facade that wires normalized configuration into the application container.
6
6
  */
@@ -31,7 +31,7 @@ export class ConfigModule {
31
31
  exports: [ConfigService],
32
32
  providers: [{
33
33
  provide: ConfigService,
34
- useFactory: () => new ConfigService(loadConfig(options ?? {}))
34
+ useFactory: () => createConfigServiceFromSnapshot(loadConfig(options ?? {}))
35
35
  }]
36
36
  });
37
37
  return ConfigModuleImpl;
@@ -1 +1 @@
1
- {"version":3,"file":"reload-module.d.ts","sourceRoot":"","sources":["../src/reload-module.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAgC,MAAM,cAAc,CAAC;AAC3E,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"}
1
+ {"version":3,"file":"reload-module.d.ts","sourceRoot":"","sources":["../src/reload-module.ts"],"names":[],"mappings":"AAKA,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"}
@@ -8,7 +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 { ConfigService, replaceConfigServiceSnapshot } from './service.js';
11
+ import { ConfigService, replaceConfigServiceSnapshotUnchecked } from './service.js';
12
12
  const CONFIG_RELOAD_OPTIONS = Symbol('fluo.config.reload-options');
13
13
 
14
14
  /**
@@ -42,7 +42,7 @@ class ConfigReloadManager {
42
42
  this.options = options;
43
43
  }
44
44
  current() {
45
- return this.ensureReloader().current();
45
+ return this.config.snapshot();
46
46
  }
47
47
  reload() {
48
48
  return this.ensureReloader().reload();
@@ -82,12 +82,12 @@ class ConfigReloadManager {
82
82
  this.reloadForwarder = reloader.subscribe((nextConfig, reason) => {
83
83
  const previousConfig = this.config.snapshot();
84
84
  try {
85
- replaceConfigServiceSnapshot(this.config, nextConfig);
85
+ replaceConfigServiceSnapshotUnchecked(this.config, nextConfig);
86
86
  for (const listener of this.reloadListeners) {
87
87
  listener(cloneConfigDictionary(nextConfig), reason);
88
88
  }
89
89
  } catch (error) {
90
- replaceConfigServiceSnapshot(this.config, previousConfig);
90
+ replaceConfigServiceSnapshotUnchecked(this.config, previousConfig);
91
91
  throw error;
92
92
  }
93
93
  });
package/dist/service.d.ts CHANGED
@@ -35,4 +35,18 @@ export declare class ConfigService<T extends Record<string, unknown> = ConfigDic
35
35
  * @param values The new configuration dictionary.
36
36
  */
37
37
  export declare function replaceConfigServiceSnapshot<T extends Record<string, unknown>>(service: ConfigService<T>, values: T): void;
38
+ /**
39
+ * Replaces the underlying configuration snapshot with an already-detached value.
40
+ *
41
+ * @param service The `ConfigService` instance to update.
42
+ * @param values A trusted configuration dictionary that must not be mutated after adoption.
43
+ */
44
+ export declare function replaceConfigServiceSnapshotUnchecked<T extends Record<string, unknown>>(service: ConfigService<T>, values: T): void;
45
+ /**
46
+ * Creates a `ConfigService` by adopting an already-detached configuration snapshot.
47
+ *
48
+ * @param values A trusted configuration dictionary produced by the config loader.
49
+ * @returns A `ConfigService` backed by the provided snapshot without an additional constructor clone.
50
+ */
51
+ export declare function createConfigServiceFromSnapshot<T extends Record<string, unknown>>(values: T): ConfigService<T>;
38
52
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMvE;;GAEG;AACH,qBAAa,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB;IAC7E,OAAO,CAAC,MAAM,CAAI;gBAEN,MAAM,EAAE,CAAC;IAIrB;;;;;OAKG;IACH,GAAG,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,GAAG,SAAS;IAIvE;;;;;;OAMG;IACH,UAAU,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;IAUlE;;;;OAIG;IACH,QAAQ,IAAI,gBAAgB;IAI5B,OAAO,CAAC,QAAQ;CAuBjB;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5E,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,CAAC,GACR,IAAI,CAEN"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMvE;;GAEG;AACH,qBAAa,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB;IAC7E,OAAO,CAAC,MAAM,CAAI;gBAEN,MAAM,EAAE,CAAC;IAIrB;;;;;OAKG;IACH,GAAG,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,GAAG,SAAS;IAIvE;;;;;;OAMG;IACH,UAAU,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;IAUlE;;;;OAIG;IACH,QAAQ,IAAI,gBAAgB;IAI5B,OAAO,CAAC,QAAQ;CAuBjB;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5E,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,CAAC,GACR,IAAI,CAEN;AAED;;;;;GAKG;AACH,wBAAgB,qCAAqC,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrF,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,CAAC,GACR,IAAI,CAEN;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAK9G"}
package/dist/service.js CHANGED
@@ -78,4 +78,26 @@ export class ConfigService {
78
78
  */
79
79
  export function replaceConfigServiceSnapshot(service, values) {
80
80
  service['values'] = cloneConfigDictionary(values);
81
+ }
82
+
83
+ /**
84
+ * Replaces the underlying configuration snapshot with an already-detached value.
85
+ *
86
+ * @param service The `ConfigService` instance to update.
87
+ * @param values A trusted configuration dictionary that must not be mutated after adoption.
88
+ */
89
+ export function replaceConfigServiceSnapshotUnchecked(service, values) {
90
+ service['values'] = values;
91
+ }
92
+
93
+ /**
94
+ * Creates a `ConfigService` by adopting an already-detached configuration snapshot.
95
+ *
96
+ * @param values A trusted configuration dictionary produced by the config loader.
97
+ * @returns A `ConfigService` backed by the provided snapshot without an additional constructor clone.
98
+ */
99
+ export function createConfigServiceFromSnapshot(values) {
100
+ const service = Object.create(ConfigService.prototype);
101
+ service['values'] = values;
102
+ return service;
81
103
  }
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "environment",
9
9
  "typed-config"
10
10
  ],
11
- "version": "1.0.0-beta.1",
11
+ "version": "1.0.0-beta.3",
12
12
  "private": false,
13
13
  "license": "MIT",
14
14
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "dotenv": "^16.0.0",
39
39
  "dotenv-expand": "^11.0.0",
40
- "@fluojs/core": "^1.0.0-beta.1"
40
+ "@fluojs/core": "^1.0.0-beta.2"
41
41
  },
42
42
  "devDependencies": {
43
43
  "vitest": "^3.2.4"