@fluojs/config 1.0.0-beta.2 → 1.0.0-beta.4
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 +18 -7
- package/README.md +17 -7
- package/dist/clone.d.ts +6 -0
- package/dist/clone.d.ts.map +1 -1
- package/dist/clone.js +7 -0
- package/dist/load.d.ts +2 -2
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +134 -20
- package/dist/module.js +2 -2
- package/dist/reload-module.d.ts.map +1 -1
- package/dist/reload-module.js +3 -3
- package/dist/service.d.ts +14 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +22 -0
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
package/README.ko.md
CHANGED
|
@@ -32,6 +32,12 @@ npm install @fluojs/config
|
|
|
32
32
|
```ts
|
|
33
33
|
import { ConfigModule } from '@fluojs/config';
|
|
34
34
|
import { Module } from '@fluojs/core';
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
|
|
37
|
+
const EnvSchema = z.object({
|
|
38
|
+
DATABASE_URL: z.string().url(),
|
|
39
|
+
PORT: z.coerce.number().default(3000),
|
|
40
|
+
});
|
|
35
41
|
|
|
36
42
|
@Module({
|
|
37
43
|
imports: [
|
|
@@ -41,10 +47,7 @@ import { Module } from '@fluojs/core';
|
|
|
41
47
|
DATABASE_URL: process.env.DATABASE_URL,
|
|
42
48
|
},
|
|
43
49
|
defaults: { PORT: '3000' },
|
|
44
|
-
|
|
45
|
-
if (!config.DATABASE_URL) throw new Error('DATABASE_URL이 필요합니다');
|
|
46
|
-
return config;
|
|
47
|
-
},
|
|
50
|
+
schema: EnvSchema,
|
|
48
51
|
}),
|
|
49
52
|
],
|
|
50
53
|
})
|
|
@@ -78,7 +81,15 @@ class MyService {
|
|
|
78
81
|
|
|
79
82
|
### 부트스트랩 전 검증
|
|
80
83
|
|
|
81
|
-
`
|
|
84
|
+
`schema` 옵션은 Zod, Valibot, ArkType 같은 동기식 [Standard Schema](https://standardschema.dev/schema) 호환 validator를 받습니다. 스키마는 모든 소스가 합쳐진 뒤 실행되고, 검증된 `value`가 최종 config snapshot이 됩니다. schema issue가 보고되면 bootstrap/load/reload는 `INVALID_CONFIG`로 실패합니다.
|
|
85
|
+
|
|
86
|
+
`@fluojs/config`의 load와 reload API는 동기식입니다. 비동기 Standard Schema 결과는 `INVALID_CONFIG`로 거부되므로 config 검증에는 동기 스키마를 사용하세요.
|
|
87
|
+
|
|
88
|
+
### 런타임 접근과 리로드 비용 모델
|
|
89
|
+
|
|
90
|
+
`ConfigService.get('a.b.c')`는 dot-path 세그먼트를 순서대로 탐색하므로 조회 비용은 path 깊이에 비례합니다. `get()`, `getOrThrow()`, `snapshot()`이 객체 형태의 값을 반환할 때는 분리된 clone을 반환합니다. 따라서 clone 비용은 반환되는 subtree 크기에 비례하며, 호출자 mutation은 활성 config snapshot에 영향을 주지 않습니다.
|
|
91
|
+
|
|
92
|
+
`ConfigReloadManager.reload()`는 리로드 작업을 직렬화합니다. 현재 리로드가 listener 알림을 수행하는 동안 다른 리로드가 요청되면 후속 리로드는 큐에 들어가 활성 알림이 끝난 뒤 적용됩니다. 활성 알림이 실패하면 이전 snapshot을 복구하고 큐에 있던 리로드는 폐기합니다. 동일한 직렬화와 rollback 계약은 `createConfigReloader(...).reload()`에도 적용되며, watch로 시작된 알림 중 큐에 들어간 manual reload도 이 계약을 따릅니다.
|
|
82
93
|
|
|
83
94
|
## 공개 API
|
|
84
95
|
|
|
@@ -92,12 +103,12 @@ class MyService {
|
|
|
92
103
|
| `loadConfig(options)` | 설정을 수동으로 로드하기 위한 함수형 엔트리 포인트입니다. |
|
|
93
104
|
| `createConfigReloader(options)` | 동적 설정 업데이트를 위한 리로더를 생성합니다. |
|
|
94
105
|
|
|
95
|
-
`ConfigReloadManager.reload()`는 기존 `ConfigService` 인스턴스를 갱신하므로 소비자는 주입받은 서비스 identity를 유지하면서 새 스냅샷을 관찰합니다. 리로드 listener가 에러를 던지면 매니저는 이전 스냅샷을 복구하고 listener 에러를 다시 던집니다.
|
|
106
|
+
`ConfigReloadManager.reload()`는 기존 `ConfigService` 인스턴스를 갱신하므로 소비자는 주입받은 서비스 identity를 유지하면서 새 스냅샷을 관찰합니다. 리로드 listener가 에러를 던지면 매니저는 이전 스냅샷을 복구하고 listener 에러를 다시 던집니다. `createConfigReloader(...).reload()`도 standalone reloader snapshot에 대해 동일한 listener 직렬화와 rollback 동작을 따릅니다.
|
|
96
107
|
|
|
97
108
|
## 관련 패키지
|
|
98
109
|
|
|
99
110
|
- `@fluojs/runtime`: 부트스트랩 중 `loadConfig()`를 호출합니다.
|
|
100
|
-
-
|
|
111
|
+
- Standard Schema validator: Zod, Valibot, ArkType 및 호환 schema 라이브러리를 `schema` 옵션으로 전달할 수 있습니다.
|
|
101
112
|
|
|
102
113
|
## 예제 소스
|
|
103
114
|
|
package/README.md
CHANGED
|
@@ -35,6 +35,12 @@ The `ConfigModule` handles loading and validating your configuration during boot
|
|
|
35
35
|
```typescript
|
|
36
36
|
import { Module } from '@fluojs/core';
|
|
37
37
|
import { ConfigModule } from '@fluojs/config';
|
|
38
|
+
import { z } from 'zod';
|
|
39
|
+
|
|
40
|
+
const EnvSchema = z.object({
|
|
41
|
+
DATABASE_URL: z.string().url(),
|
|
42
|
+
PORT: z.coerce.number().default(3000),
|
|
43
|
+
});
|
|
38
44
|
|
|
39
45
|
@Module({
|
|
40
46
|
imports: [
|
|
@@ -44,10 +50,7 @@ import { ConfigModule } from '@fluojs/config';
|
|
|
44
50
|
DATABASE_URL: process.env.DATABASE_URL,
|
|
45
51
|
},
|
|
46
52
|
defaults: { PORT: '3000' },
|
|
47
|
-
|
|
48
|
-
if (!config.DATABASE_URL) throw new Error('DATABASE_URL is required');
|
|
49
|
-
return config;
|
|
50
|
-
},
|
|
53
|
+
schema: EnvSchema,
|
|
51
54
|
}),
|
|
52
55
|
],
|
|
53
56
|
})
|
|
@@ -83,7 +86,14 @@ Configuration is merged in the following order (highest precedence wins):
|
|
|
83
86
|
Plain objects are deep-merged by key. Arrays and primitive values from higher-precedence sources completely replace lower-precedence ones.
|
|
84
87
|
|
|
85
88
|
### Validation
|
|
86
|
-
The `
|
|
89
|
+
The `schema` option accepts a synchronous [Standard Schema](https://standardschema.dev/schema)-compatible validator such as Zod, Valibot, or ArkType. The schema runs after all sources are merged but before the application starts. Its validated `value` becomes the final config snapshot, and reported issues fail bootstrap/load/reload with `INVALID_CONFIG`.
|
|
90
|
+
|
|
91
|
+
`@fluojs/config` keeps loading and reload APIs synchronous. Async Standard Schema results are rejected with `INVALID_CONFIG`; use a synchronous schema for config validation.
|
|
92
|
+
|
|
93
|
+
### Runtime Access and Reload Cost Model
|
|
94
|
+
`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.
|
|
95
|
+
|
|
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.
|
|
87
97
|
|
|
88
98
|
## Public API
|
|
89
99
|
|
|
@@ -97,12 +107,12 @@ The `validate` function runs after all sources are merged but before the applica
|
|
|
97
107
|
| `loadConfig(options)` | Functional entry point for loading configuration manually. |
|
|
98
108
|
| `createConfigReloader(options)` | Creates a reloader for dynamic configuration updates. |
|
|
99
109
|
|
|
100
|
-
`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.
|
|
110
|
+
`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.
|
|
101
111
|
|
|
102
112
|
## Related Packages
|
|
103
113
|
|
|
104
114
|
- **`@fluojs/runtime`**: Calls `loadConfig` internally during application bootstrap.
|
|
105
|
-
-
|
|
115
|
+
- **Standard Schema validators**: Zod, Valibot, ArkType, and other compatible schema libraries can be passed through the `schema` option.
|
|
106
116
|
|
|
107
117
|
## Example Sources
|
|
108
118
|
|
package/dist/clone.d.ts
CHANGED
package/dist/clone.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clone.d.ts","sourceRoot":"","sources":["../src/clone.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAMpD"}
|
|
1
|
+
{"version":3,"file":"clone.d.ts","sourceRoot":"","sources":["../src/clone.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAMpD"}
|
package/dist/clone.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { fallbackClone } from '@fluojs/core/internal';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Clone config dictionary.
|
|
5
|
+
*
|
|
6
|
+
* @param value The value.
|
|
7
|
+
* @returns The clone config dictionary result.
|
|
8
|
+
*/
|
|
2
9
|
export function cloneConfigDictionary(value) {
|
|
3
10
|
try {
|
|
4
11
|
return structuredClone(value);
|
package/dist/load.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ConfigDictionary, ConfigLoadOptions, ConfigReloader } from './type
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates a stateful config reloader that mirrors `loadConfig(...)` semantics and optionally watches the env file.
|
|
4
4
|
*
|
|
5
|
-
* @param options Configuration loading options, including optional watch mode and
|
|
5
|
+
* @param options Configuration loading options, including optional watch mode and a synchronous Standard Schema validator.
|
|
6
6
|
* @returns A reloader that exposes the current snapshot, manual reload, subscriptions, and cleanup.
|
|
7
7
|
* @throws {FluoError} When the initial config load or validation fails.
|
|
8
8
|
*
|
|
@@ -25,7 +25,7 @@ export declare function createConfigReloader(options: ConfigLoadOptions): Config
|
|
|
25
25
|
*
|
|
26
26
|
* Merge precedence stays aligned with the package README contract: `defaults` < env file < `processEnv` < `runtimeOverrides`.
|
|
27
27
|
*
|
|
28
|
-
* @param options Configuration loading options for source precedence, parsing, and validation.
|
|
28
|
+
* @param options Configuration loading options for source precedence, parsing, and synchronous schema validation.
|
|
29
29
|
* @returns A detached normalized configuration dictionary for the current load.
|
|
30
30
|
* @throws {FluoError} When validation throws or the config cannot be normalized.
|
|
31
31
|
*/
|
package/dist/load.d.ts.map
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../src/load.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAEjB,cAAc,EAKf,MAAM,YAAY,CAAC;AA0XpB;;;;;;;;;;;;;;;;;;;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);
|
|
@@ -20,7 +32,16 @@ function parseEnvContent(content, safeProcessEnv, customParser) {
|
|
|
20
32
|
function sanitizeProcessEnv(processEnv) {
|
|
21
33
|
return Object.fromEntries(Object.entries(processEnv).filter(entry => entry[1] !== undefined));
|
|
22
34
|
}
|
|
35
|
+
function rejectLegacyValidateOption(options) {
|
|
36
|
+
if ('validate' in options) {
|
|
37
|
+
throw new FluoError('Invalid configuration.', {
|
|
38
|
+
code: 'INVALID_CONFIG',
|
|
39
|
+
cause: new Error('The legacy `validate` option was removed. Use `schema` with a synchronous Standard Schema validator instead.')
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
23
43
|
function normalizeLoadOptions(options) {
|
|
44
|
+
rejectLegacyValidateOption(options);
|
|
24
45
|
const cwd = options.cwd ?? process.cwd();
|
|
25
46
|
const envFile = options.envFilePath ?? options.envFile ?? join(cwd, '.env');
|
|
26
47
|
const defaults = options.defaults ?? {};
|
|
@@ -33,31 +54,36 @@ function normalizeLoadOptions(options) {
|
|
|
33
54
|
parse: options.parse,
|
|
34
55
|
runtimeOverrides,
|
|
35
56
|
safeProcessEnv,
|
|
36
|
-
|
|
57
|
+
schema: options.schema
|
|
37
58
|
};
|
|
38
59
|
}
|
|
39
60
|
function readEnvFileValues(options) {
|
|
40
|
-
|
|
41
|
-
return
|
|
61
|
+
try {
|
|
62
|
+
return parseEnvContent(readFileSync(options.envFile, 'utf8'), options.safeProcessEnv, options.parse);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
42
68
|
}
|
|
43
|
-
return parseEnvContent(readFileSync(options.envFile, 'utf8'), options.safeProcessEnv, options.parse);
|
|
44
69
|
}
|
|
45
70
|
function isPlainObject(value) {
|
|
46
71
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
47
72
|
}
|
|
48
73
|
function mergeConfigEntries(target, source) {
|
|
49
|
-
const merged = {
|
|
50
|
-
...target
|
|
51
|
-
};
|
|
52
74
|
for (const [key, sourceValue] of Object.entries(source)) {
|
|
53
|
-
const targetValue =
|
|
75
|
+
const targetValue = target[key];
|
|
54
76
|
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
55
|
-
|
|
77
|
+
mergeConfigEntries(targetValue, sourceValue);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (isPlainObject(sourceValue)) {
|
|
81
|
+
target[key] = mergeConfigEntries({}, sourceValue);
|
|
56
82
|
continue;
|
|
57
83
|
}
|
|
58
|
-
|
|
84
|
+
target[key] = cloneConfigDictionary(sourceValue);
|
|
59
85
|
}
|
|
60
|
-
return
|
|
86
|
+
return target;
|
|
61
87
|
}
|
|
62
88
|
function mergeConfigSources(...sources) {
|
|
63
89
|
let merged = {};
|
|
@@ -70,14 +96,76 @@ function buildMergedConfig(options) {
|
|
|
70
96
|
const envFileValues = readEnvFileValues(options);
|
|
71
97
|
return mergeConfigSources(options.defaults, envFileValues, options.safeProcessEnv, options.runtimeOverrides);
|
|
72
98
|
}
|
|
99
|
+
function isPromiseLike(value) {
|
|
100
|
+
return (typeof value === 'object' || typeof value === 'function') && value !== null && 'then' in value && typeof value.then === 'function';
|
|
101
|
+
}
|
|
102
|
+
function isConfigSchemaIssue(value) {
|
|
103
|
+
return typeof value === 'object' && value !== null && 'message' in value && typeof value.message === 'string';
|
|
104
|
+
}
|
|
105
|
+
function isConfigSchemaFailureResult(value) {
|
|
106
|
+
if (typeof value !== 'object' || value === null || !('issues' in value)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return Array.isArray(value.issues) && value.issues.every(isConfigSchemaIssue);
|
|
110
|
+
}
|
|
111
|
+
function isConfigSchemaSuccessResult(value) {
|
|
112
|
+
return typeof value === 'object' && value !== null && 'value' in value && isPlainObject(value.value);
|
|
113
|
+
}
|
|
114
|
+
function isConfigSchemaPathKeySegment(value) {
|
|
115
|
+
if (typeof value !== 'object' || value === null || !('key' in value)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return typeof value.key === 'string' || typeof value.key === 'number' || typeof value.key === 'symbol';
|
|
119
|
+
}
|
|
120
|
+
function formatConfigSchemaPathSegment(segment) {
|
|
121
|
+
if (typeof segment === 'string' || typeof segment === 'number') {
|
|
122
|
+
return String(segment);
|
|
123
|
+
}
|
|
124
|
+
if (isConfigSchemaPathKeySegment(segment)) {
|
|
125
|
+
return String(segment.key);
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
function formatConfigSchemaIssue(issue) {
|
|
130
|
+
const path = issue.path?.map(formatConfigSchemaPathSegment).filter(segment => segment !== undefined).join('.');
|
|
131
|
+
return path && path.length > 0 ? `${path}: ${issue.message}` : issue.message;
|
|
132
|
+
}
|
|
133
|
+
function createInvalidConfigError(cause, issues) {
|
|
134
|
+
return new FluoError('Invalid configuration.', {
|
|
135
|
+
code: 'INVALID_CONFIG',
|
|
136
|
+
cause,
|
|
137
|
+
meta: issues ? {
|
|
138
|
+
issues: issues.map(formatConfigSchemaIssue)
|
|
139
|
+
} : undefined
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function isInvalidConfigError(error) {
|
|
143
|
+
return error instanceof FluoError && error.code === 'INVALID_CONFIG';
|
|
144
|
+
}
|
|
145
|
+
function readConfigSchemaResult(result) {
|
|
146
|
+
if (isConfigSchemaFailureResult(result)) {
|
|
147
|
+
throw createInvalidConfigError(new Error('Standard Schema config validation failed.'), result.issues);
|
|
148
|
+
}
|
|
149
|
+
if (!isConfigSchemaSuccessResult(result)) {
|
|
150
|
+
throw createInvalidConfigError(new Error('Standard Schema config validator returned a malformed result.'));
|
|
151
|
+
}
|
|
152
|
+
return result.value;
|
|
153
|
+
}
|
|
73
154
|
function validateConfig(options, merged) {
|
|
155
|
+
if (!options.schema) {
|
|
156
|
+
return merged;
|
|
157
|
+
}
|
|
74
158
|
try {
|
|
75
|
-
|
|
159
|
+
const result = options.schema['~standard'].validate(merged);
|
|
160
|
+
if (isPromiseLike(result)) {
|
|
161
|
+
throw new Error('Config schemas must validate synchronously. Async Standard Schema validation is not supported by the synchronous config API.');
|
|
162
|
+
}
|
|
163
|
+
return readConfigSchemaResult(result);
|
|
76
164
|
} catch (error) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
165
|
+
if (isInvalidConfigError(error)) {
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
throw createInvalidConfigError(error);
|
|
81
169
|
}
|
|
82
170
|
}
|
|
83
171
|
function resolveConfig(options) {
|
|
@@ -101,7 +189,7 @@ function notifyReloadErrorListeners(listeners, error, reason) {
|
|
|
101
189
|
listener(error, reason);
|
|
102
190
|
}
|
|
103
191
|
}
|
|
104
|
-
function
|
|
192
|
+
function applyReloadNow(normalized, state, listeners, reason) {
|
|
105
193
|
const previous = state.current;
|
|
106
194
|
const next = resolveConfig(normalized);
|
|
107
195
|
state.current = next;
|
|
@@ -113,6 +201,30 @@ function applyReload(normalized, state, listeners, reason) {
|
|
|
113
201
|
}
|
|
114
202
|
return cloneConfigDictionary(next);
|
|
115
203
|
}
|
|
204
|
+
function applyReload(normalized, state, listeners, reason) {
|
|
205
|
+
if (state.reloading) {
|
|
206
|
+
state.pendingReloadReason = reason;
|
|
207
|
+
return cloneConfigDictionary(state.current);
|
|
208
|
+
}
|
|
209
|
+
state.reloading = true;
|
|
210
|
+
let activeReason = reason;
|
|
211
|
+
try {
|
|
212
|
+
let latest = applyReloadNow(normalized, state, listeners, activeReason);
|
|
213
|
+
while (state.pendingReloadReason) {
|
|
214
|
+
const pendingReason = state.pendingReloadReason;
|
|
215
|
+
state.pendingReloadReason = undefined;
|
|
216
|
+
activeReason = pendingReason;
|
|
217
|
+
latest = applyReloadNow(normalized, state, listeners, pendingReason);
|
|
218
|
+
}
|
|
219
|
+
return latest;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
markReloadFailure(error, activeReason);
|
|
222
|
+
throw error;
|
|
223
|
+
} finally {
|
|
224
|
+
state.pendingReloadReason = undefined;
|
|
225
|
+
state.reloading = false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
116
228
|
function startReloaderWatcher(normalized, options, state, listeners, errorListeners) {
|
|
117
229
|
if (!options.watch || !existsSync(normalized.envFile)) {
|
|
118
230
|
return undefined;
|
|
@@ -123,7 +235,7 @@ function startReloaderWatcher(normalized, options, state, listeners, errorListen
|
|
|
123
235
|
try {
|
|
124
236
|
applyReload(normalized, state, listeners, 'watch');
|
|
125
237
|
} catch (error) {
|
|
126
|
-
notifyReloadErrorListeners(errorListeners, error, 'watch');
|
|
238
|
+
notifyReloadErrorListeners(errorListeners, error, getReloadFailureReason(error) ?? 'watch');
|
|
127
239
|
}
|
|
128
240
|
});
|
|
129
241
|
}
|
|
@@ -139,7 +251,7 @@ function closeReloader(state, listeners, errorListeners) {
|
|
|
139
251
|
/**
|
|
140
252
|
* Creates a stateful config reloader that mirrors `loadConfig(...)` semantics and optionally watches the env file.
|
|
141
253
|
*
|
|
142
|
-
* @param options Configuration loading options, including optional watch mode and
|
|
254
|
+
* @param options Configuration loading options, including optional watch mode and a synchronous Standard Schema validator.
|
|
143
255
|
* @returns A reloader that exposes the current snapshot, manual reload, subscriptions, and cleanup.
|
|
144
256
|
* @throws {FluoError} When the initial config load or validation fails.
|
|
145
257
|
*
|
|
@@ -160,6 +272,8 @@ export function createConfigReloader(options) {
|
|
|
160
272
|
const normalized = normalizeLoadOptions(options);
|
|
161
273
|
const state = {
|
|
162
274
|
current: resolveConfig(normalized),
|
|
275
|
+
pendingReloadReason: undefined,
|
|
276
|
+
reloading: false,
|
|
163
277
|
watcher: undefined
|
|
164
278
|
};
|
|
165
279
|
const listeners = new Set();
|
|
@@ -189,7 +303,7 @@ export function createConfigReloader(options) {
|
|
|
189
303
|
*
|
|
190
304
|
* Merge precedence stays aligned with the package README contract: `defaults` < env file < `processEnv` < `runtimeOverrides`.
|
|
191
305
|
*
|
|
192
|
-
* @param options Configuration loading options for source precedence, parsing, and validation.
|
|
306
|
+
* @param options Configuration loading options for source precedence, parsing, and synchronous schema validation.
|
|
193
307
|
* @returns A detached normalized configuration dictionary for the current load.
|
|
194
308
|
* @throws {FluoError} When validation throws or the config cannot be normalized.
|
|
195
309
|
*/
|
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: () =>
|
|
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,
|
|
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"}
|
package/dist/reload-module.js
CHANGED
|
@@ -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,
|
|
11
|
+
import { ConfigService, replaceConfigServiceSnapshotUnchecked } from './service.js';
|
|
12
12
|
const CONFIG_RELOAD_OPTIONS = Symbol('fluo.config.reload-options');
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
package/dist/service.d.ts.map
CHANGED
|
@@ -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/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
1
2
|
/**
|
|
2
3
|
* Plain JSON-like object used as the normalized configuration snapshot shape.
|
|
3
4
|
*/
|
|
4
5
|
export type ConfigDictionary = Record<string, unknown>;
|
|
6
|
+
/**
|
|
7
|
+
* Standard Schema v1-compatible config validator accepted by `@fluojs/config` loaders.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam Input Raw merged config shape consumed by the schema validator.
|
|
10
|
+
* @typeParam Output Normalized config shape produced by the schema validator.
|
|
11
|
+
*/
|
|
12
|
+
export type ConfigSchema<Input = unknown, Output extends ConfigDictionary = ConfigDictionary> = StandardSchemaV1<Input, Output>;
|
|
5
13
|
/**
|
|
6
14
|
* Nested dot-path key helper.
|
|
7
15
|
* Produces "a" | "a.b" | "a.b.c" keys from a Record type.
|
|
@@ -20,7 +28,7 @@ export interface ConfigModuleOptions {
|
|
|
20
28
|
envFile?: string;
|
|
21
29
|
envFilePath?: string;
|
|
22
30
|
processEnv?: NodeJS.ProcessEnv;
|
|
23
|
-
|
|
31
|
+
schema?: ConfigSchema;
|
|
24
32
|
defaults?: ConfigDictionary;
|
|
25
33
|
/** Supply a custom file parser (e.g. for YAML or TOML). Receives raw file content,
|
|
26
34
|
* returns a flat key-value record. Defaults to dotenv parsing. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEvD;;;GAGG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnF;KACG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAClB,GAAG,MAAM,GAAG,CAAC,EAAE,GACf,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC;CACrC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GACnB,KAAK,CAAC;AAEV;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,CAAC,GACzD,CAAC,CAAC,CAAC,CAAC,GACJ,CAAC,SAAS,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,GACrC,IAAI,SAAS,MAAM,CAAC,GAClB,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,GACvB,KAAK,GACP,KAAK,CAAC;AAEZ;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,SAAS,gBAAgB,GAAG,gBAAgB,IAAI,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAEhI;;;GAGG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnF;KACG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAClB,GAAG,MAAM,GAAG,CAAC,EAAE,GACf,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC;CACrC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GACnB,KAAK,CAAC;AAEV;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,CAAC,GACzD,CAAC,CAAC,CAAC,CAAC,GACJ,CAAC,SAAS,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,GACrC,IAAI,SAAS,MAAM,CAAC,GAClB,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,GACvB,KAAK,GACP,KAAK,CAAC;AAEZ;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B;uEACmE;IACnE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAEpG;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAE7F;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,WAAW,IAAI,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,IAAI,gBAAgB,CAAC;IAC5B,MAAM,IAAI,gBAAgB,CAAC;IAC3B,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,wBAAwB,CAAC;IACpE,cAAc,CAAC,QAAQ,EAAE,yBAAyB,GAAG,wBAAwB,CAAC;IAC9E,KAAK,IAAI,IAAI,CAAC;CACf"}
|
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.4",
|
|
12
12
|
"private": false,
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
@@ -35,9 +35,10 @@
|
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@standard-schema/spec": "^1.1.0",
|
|
38
39
|
"dotenv": "^16.0.0",
|
|
39
40
|
"dotenv-expand": "^11.0.0",
|
|
40
|
-
"@fluojs/core": "^1.0.0-beta.
|
|
41
|
+
"@fluojs/core": "^1.0.0-beta.2"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"vitest": "^3.2.4"
|