@effected/config-file 0.1.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/ConfigCodec.js +27 -0
- package/ConfigEvent.js +119 -0
- package/ConfigFile.js +421 -0
- package/ConfigMigration.js +97 -0
- package/ConfigProvider.js +87 -0
- package/ConfigResolver.js +95 -0
- package/EncryptedCodec.js +123 -0
- package/JsonCodec.js +38 -0
- package/JsoncCodec.js +38 -0
- package/LICENSE +21 -0
- package/MergeStrategy.js +53 -0
- package/README.md +176 -0
- package/TomlCodec.js +39 -0
- package/YamlCodec.js +34 -0
- package/index.d.ts +932 -0
- package/index.js +14 -0
- package/internal/crypto.js +115 -0
- package/internal/deepMerge.js +80 -0
- package/package.json +54 -0
- package/tsdoc-metadata.json +11 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
import { ConfigProvider, Context, Effect, FileSystem, Layer, Option, Path, PubSub, Schema } from "effect";
|
|
2
|
+
//#region src/ConfigCodec.d.ts
|
|
3
|
+
declare const ConfigCodecError_base: Schema.Class<ConfigCodecError, Schema.TaggedStruct<"ConfigCodecError", {
|
|
4
|
+
/** The codec that failed, e.g. `"json"`. */
|
|
5
|
+
readonly codec: Schema.String;
|
|
6
|
+
/** Which direction failed. */
|
|
7
|
+
readonly operation: Schema.Literals<readonly ["parse", "stringify"]>;
|
|
8
|
+
/** The underlying failure, preserved structurally. */
|
|
9
|
+
readonly cause: Schema.Defect;
|
|
10
|
+
}>, import("effect/Cause").YieldableError>;
|
|
11
|
+
/**
|
|
12
|
+
* Indicates that a codec failed to parse or stringify configuration content.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* The underlying failure is preserved structurally in `cause` — it is never
|
|
16
|
+
* stringified. Route on the `"ConfigCodecError"` tag with `Effect.catchTag`.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
declare class ConfigCodecError extends ConfigCodecError_base {
|
|
21
|
+
get message(): string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A pluggable configuration file codec: how to turn file content into a value
|
|
25
|
+
* and back.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* `E` is the codec's error channel. It defaults to {@link ConfigCodecError};
|
|
29
|
+
* decorator codecs such as `EncryptedCodec` and `ConfigMigration.make` widen it
|
|
30
|
+
* rather than flattening their own failures into a string.
|
|
31
|
+
*
|
|
32
|
+
* The four built-in codecs — `JsonCodec`, `JsoncCodec`, `YamlCodec` and
|
|
33
|
+
* `TomlCodec` — are free-standing named exports, one per module, and are
|
|
34
|
+
* deliberately never collected into a namespace object. Collecting them would
|
|
35
|
+
* make this module a dispatch table: referencing it at all would reach every
|
|
36
|
+
* codec, and every codec would reach its parsing engine, so importing the JSON
|
|
37
|
+
* codec alone would drag the JSONC, YAML and TOML engines into the bundle.
|
|
38
|
+
* Name the one codec you use and a bundler drops the rest.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
interface ConfigCodec<E = ConfigCodecError> {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly parse: (raw: string) => Effect.Effect<unknown, E>;
|
|
45
|
+
readonly stringify: (value: unknown) => Effect.Effect<string, E>;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/ConfigEvent.d.ts
|
|
49
|
+
/**
|
|
50
|
+
* A reference to one configuration source that contributed to a load.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* Carries the resolver's name alongside the path so a subscriber can tell
|
|
54
|
+
* `/etc/app/.apprc` found by `systemEtc` from the same path passed explicitly.
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
58
|
+
declare const ConfigSourceRef: Schema.Struct<{
|
|
59
|
+
/** The filesystem path the value was read from. */
|
|
60
|
+
readonly path: Schema.String;
|
|
61
|
+
/** The name of the resolver that found it. */
|
|
62
|
+
readonly resolver: Schema.String;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Every event published during config discovery, parsing, validation and
|
|
66
|
+
* persistence.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* v3's `Stringified` and `ResolutionFailed` variants were declared and never
|
|
70
|
+
* emitted; they are not ported. `DiscoveryFailed` goes with them: under this
|
|
71
|
+
* package's resolver-absorption contract a resolver's error channel is `never`
|
|
72
|
+
* — every filesystem failure becomes `Option.none()` — so the pipeline can
|
|
73
|
+
* never observe a discovery failure to report one.
|
|
74
|
+
*
|
|
75
|
+
* The failure variants carry the **structured** typed error in `error`, not a
|
|
76
|
+
* `reason` string. v3 stringified them, destroying every field a subscriber
|
|
77
|
+
* might branch on; a subscriber that wants prose can read `error.message`.
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
declare const ConfigEventPayload: Schema.Union<readonly [Schema.TaggedStruct<"Discovered", {
|
|
82
|
+
readonly path: Schema.String;
|
|
83
|
+
readonly resolver: Schema.String;
|
|
84
|
+
}>, Schema.TaggedStruct<"NotFound", {}>, Schema.TaggedStruct<"Parsed", {
|
|
85
|
+
readonly path: Schema.String;
|
|
86
|
+
readonly codec: Schema.String;
|
|
87
|
+
}>, Schema.TaggedStruct<"ParseFailed", {
|
|
88
|
+
readonly path: Schema.String;
|
|
89
|
+
readonly codec: Schema.String;
|
|
90
|
+
readonly error: Schema.Defect;
|
|
91
|
+
}>, Schema.TaggedStruct<"Validated", {
|
|
92
|
+
readonly path: Schema.String;
|
|
93
|
+
}>, Schema.TaggedStruct<"ValidationFailed", {
|
|
94
|
+
readonly path: Schema.String;
|
|
95
|
+
readonly error: Schema.Defect;
|
|
96
|
+
}>, Schema.TaggedStruct<"Resolved", {
|
|
97
|
+
readonly sources: Schema.$Array<Schema.Struct<{
|
|
98
|
+
/** The filesystem path the value was read from. */
|
|
99
|
+
readonly path: Schema.String;
|
|
100
|
+
/** The name of the resolver that found it. */
|
|
101
|
+
readonly resolver: Schema.String;
|
|
102
|
+
}>>;
|
|
103
|
+
readonly strategy: Schema.String;
|
|
104
|
+
}>, Schema.TaggedStruct<"Loaded", {
|
|
105
|
+
readonly sources: Schema.$Array<Schema.Struct<{
|
|
106
|
+
/** The filesystem path the value was read from. */
|
|
107
|
+
readonly path: Schema.String;
|
|
108
|
+
/** The name of the resolver that found it. */
|
|
109
|
+
readonly resolver: Schema.String;
|
|
110
|
+
}>>;
|
|
111
|
+
}>, Schema.TaggedStruct<"StringifyFailed", {
|
|
112
|
+
readonly codec: Schema.String;
|
|
113
|
+
readonly error: Schema.Defect;
|
|
114
|
+
}>, Schema.TaggedStruct<"Written", {
|
|
115
|
+
readonly path: Schema.String;
|
|
116
|
+
}>, Schema.TaggedStruct<"Saved", {
|
|
117
|
+
readonly path: Schema.String;
|
|
118
|
+
}>, Schema.TaggedStruct<"Updated", {
|
|
119
|
+
readonly path: Schema.String;
|
|
120
|
+
}>]>;
|
|
121
|
+
/**
|
|
122
|
+
* The decoded form of {@link (ConfigEventPayload:variable)}: a tagged union a subscriber
|
|
123
|
+
* narrows with `switch (payload._tag)`.
|
|
124
|
+
*
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
type ConfigEventPayload = typeof ConfigEventPayload.Type;
|
|
128
|
+
declare const ConfigEvent_base: Schema.Class<ConfigEvent, Schema.Struct<{
|
|
129
|
+
/** When the event occurred. */
|
|
130
|
+
readonly timestamp: Schema.DateTimeUtc;
|
|
131
|
+
/** What happened. */
|
|
132
|
+
readonly event: Schema.Union<readonly [Schema.TaggedStruct<"Discovered", {
|
|
133
|
+
readonly path: Schema.String;
|
|
134
|
+
readonly resolver: Schema.String;
|
|
135
|
+
}>, Schema.TaggedStruct<"NotFound", {}>, Schema.TaggedStruct<"Parsed", {
|
|
136
|
+
readonly path: Schema.String;
|
|
137
|
+
readonly codec: Schema.String;
|
|
138
|
+
}>, Schema.TaggedStruct<"ParseFailed", {
|
|
139
|
+
readonly path: Schema.String;
|
|
140
|
+
readonly codec: Schema.String;
|
|
141
|
+
readonly error: Schema.Defect;
|
|
142
|
+
}>, Schema.TaggedStruct<"Validated", {
|
|
143
|
+
readonly path: Schema.String;
|
|
144
|
+
}>, Schema.TaggedStruct<"ValidationFailed", {
|
|
145
|
+
readonly path: Schema.String;
|
|
146
|
+
readonly error: Schema.Defect;
|
|
147
|
+
}>, Schema.TaggedStruct<"Resolved", {
|
|
148
|
+
readonly sources: Schema.$Array<Schema.Struct<{
|
|
149
|
+
/** The filesystem path the value was read from. */
|
|
150
|
+
readonly path: Schema.String;
|
|
151
|
+
/** The name of the resolver that found it. */
|
|
152
|
+
readonly resolver: Schema.String;
|
|
153
|
+
}>>;
|
|
154
|
+
readonly strategy: Schema.String;
|
|
155
|
+
}>, Schema.TaggedStruct<"Loaded", {
|
|
156
|
+
readonly sources: Schema.$Array<Schema.Struct<{
|
|
157
|
+
/** The filesystem path the value was read from. */
|
|
158
|
+
readonly path: Schema.String;
|
|
159
|
+
/** The name of the resolver that found it. */
|
|
160
|
+
readonly resolver: Schema.String;
|
|
161
|
+
}>>;
|
|
162
|
+
}>, Schema.TaggedStruct<"StringifyFailed", {
|
|
163
|
+
readonly codec: Schema.String;
|
|
164
|
+
readonly error: Schema.Defect;
|
|
165
|
+
}>, Schema.TaggedStruct<"Written", {
|
|
166
|
+
readonly path: Schema.String;
|
|
167
|
+
}>, Schema.TaggedStruct<"Saved", {
|
|
168
|
+
readonly path: Schema.String;
|
|
169
|
+
}>, Schema.TaggedStruct<"Updated", {
|
|
170
|
+
readonly path: Schema.String;
|
|
171
|
+
}>]>;
|
|
172
|
+
}>, {}>;
|
|
173
|
+
/**
|
|
174
|
+
* A published event: the payload plus the instant it occurred.
|
|
175
|
+
*
|
|
176
|
+
* @public
|
|
177
|
+
*/
|
|
178
|
+
declare class ConfigEvent extends ConfigEvent_base {}
|
|
179
|
+
/**
|
|
180
|
+
* The service shape {@link ConfigEvents} provides.
|
|
181
|
+
*
|
|
182
|
+
* @public
|
|
183
|
+
*/
|
|
184
|
+
interface ConfigEventsShape {
|
|
185
|
+
/** The hub every {@link ConfigEvent} is published to. */
|
|
186
|
+
readonly events: PubSub.PubSub<ConfigEvent>;
|
|
187
|
+
}
|
|
188
|
+
declare const ConfigEvents_base: Context.ServiceClass<ConfigEvents, "@effected/config-file/ConfigEvents", ConfigEventsShape>;
|
|
189
|
+
/**
|
|
190
|
+
* The opt-in event hook: a PubSub of {@link ConfigEvent} that consumers
|
|
191
|
+
* subscribe to.
|
|
192
|
+
*
|
|
193
|
+
* @remarks
|
|
194
|
+
* Opt-in and honestly zero-cost: when `ConfigFileOptions.events` is omitted the
|
|
195
|
+
* pipeline's `emit` is `Effect.void` and never even looks the service up.
|
|
196
|
+
*
|
|
197
|
+
* Events are a **consumer-facing hook**, not the package's observability
|
|
198
|
+
* channel — every public fallible method is also an `Effect.fn` named span, and
|
|
199
|
+
* the library stays telemetry-agnostic.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* const events = ConfigEvents.layer;
|
|
204
|
+
* const AppLayer = Layer.mergeAll(
|
|
205
|
+
* events,
|
|
206
|
+
* ConfigFile.layer(AppConfig, { schema, codec, resolvers, strategy, events: ConfigEvents }),
|
|
207
|
+
* );
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* @public
|
|
211
|
+
*/
|
|
212
|
+
declare class ConfigEvents extends ConfigEvents_base {
|
|
213
|
+
/**
|
|
214
|
+
* An unbounded PubSub of config events.
|
|
215
|
+
*
|
|
216
|
+
* @remarks
|
|
217
|
+
* Unbounded on purpose: a slow subscriber must never apply backpressure to a
|
|
218
|
+
* config load. Bind this to a const and provide that const — building it
|
|
219
|
+
* twice mints two hubs, and the subscriber would watch the one `emit` does
|
|
220
|
+
* not publish to.
|
|
221
|
+
*/
|
|
222
|
+
static readonly layer: Layer.Layer<ConfigEvents>;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/ConfigResolver.d.ts
|
|
226
|
+
/**
|
|
227
|
+
* A composable config file resolver: one lookup strategy.
|
|
228
|
+
*
|
|
229
|
+
* @remarks
|
|
230
|
+
* `resolve` yields `Option.some(path)` when a config file is found and
|
|
231
|
+
* `Option.none()` when it is not. **Its error channel is `never` by contract**:
|
|
232
|
+
* every filesystem failure — permission denied, ENOTDIR, a broken symlink — is
|
|
233
|
+
* absorbed into `Option.none()`, so a failure on one tier never aborts the
|
|
234
|
+
* chain. This is deliberate: discovery is best-effort.
|
|
235
|
+
*
|
|
236
|
+
* `R` carries the resolver's requirements. The built-ins require
|
|
237
|
+
* `FileSystem.FileSystem | Path.Path`, satisfied once by the consumer's
|
|
238
|
+
* platform layer at the edge.
|
|
239
|
+
*
|
|
240
|
+
* @public
|
|
241
|
+
*/
|
|
242
|
+
interface ConfigResolver<R = never> {
|
|
243
|
+
readonly name: string;
|
|
244
|
+
readonly resolve: Effect.Effect<Option.Option<string>, never, R>;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Built-in resolvers, in the order a typical chain uses them.
|
|
248
|
+
*
|
|
249
|
+
* @public
|
|
250
|
+
*/
|
|
251
|
+
declare const ConfigResolver: {
|
|
252
|
+
readonly explicitPath: (target: string) => ConfigResolver<FileSystem.FileSystem | Path.Path>;
|
|
253
|
+
readonly staticDir: (options: {
|
|
254
|
+
readonly dir: string;
|
|
255
|
+
readonly filename: string;
|
|
256
|
+
}) => ConfigResolver<FileSystem.FileSystem | Path.Path>;
|
|
257
|
+
readonly upwardWalk: (options: {
|
|
258
|
+
readonly filename: string;
|
|
259
|
+
readonly cwd?: string;
|
|
260
|
+
readonly stopAt?: string;
|
|
261
|
+
readonly subpaths?: ReadonlyArray<string>;
|
|
262
|
+
}) => ConfigResolver<FileSystem.FileSystem | Path.Path>;
|
|
263
|
+
readonly workspaceRoot: (options: {
|
|
264
|
+
readonly filename: string;
|
|
265
|
+
readonly cwd?: string;
|
|
266
|
+
readonly subpaths?: ReadonlyArray<string>;
|
|
267
|
+
}) => ConfigResolver<FileSystem.FileSystem | Path.Path>;
|
|
268
|
+
readonly gitRoot: (options: {
|
|
269
|
+
readonly filename: string;
|
|
270
|
+
readonly cwd?: string;
|
|
271
|
+
readonly subpaths?: ReadonlyArray<string>;
|
|
272
|
+
}) => ConfigResolver<FileSystem.FileSystem | Path.Path>;
|
|
273
|
+
readonly systemEtc: (options: {
|
|
274
|
+
readonly app: string;
|
|
275
|
+
readonly filename: string;
|
|
276
|
+
/**
|
|
277
|
+
* System config root. Defaults to `/etc`. Overridable primarily so tests
|
|
278
|
+
* can point at a writable temp directory — the real `/etc` is not writable
|
|
279
|
+
* in test environments — and as an escape hatch for non-standard layouts.
|
|
280
|
+
*/
|
|
281
|
+
readonly dir?: string;
|
|
282
|
+
}) => ConfigResolver<FileSystem.FileSystem | Path.Path>;
|
|
283
|
+
};
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/MergeStrategy.d.ts
|
|
286
|
+
/**
|
|
287
|
+
* A single configuration source discovered during a resolver-chain pass.
|
|
288
|
+
*
|
|
289
|
+
* @public
|
|
290
|
+
*/
|
|
291
|
+
interface ConfigSource<A> {
|
|
292
|
+
/** The filesystem path the value was read from. */
|
|
293
|
+
readonly path: string;
|
|
294
|
+
/** The name of the resolver that found it. */
|
|
295
|
+
readonly resolver: string;
|
|
296
|
+
/** The decoded, validated configuration value. */
|
|
297
|
+
readonly value: A;
|
|
298
|
+
}
|
|
299
|
+
/** A source list guaranteed non-empty by the caller. @public */
|
|
300
|
+
type NonEmptySources<A> = readonly [ConfigSource<A>, ...ConfigSource<A>[]];
|
|
301
|
+
/**
|
|
302
|
+
* Strategy for combining several {@link ConfigSource} entries into one value.
|
|
303
|
+
*
|
|
304
|
+
* @remarks
|
|
305
|
+
* Sources arrive in priority order, highest first. The list is non-empty by
|
|
306
|
+
* construction — the empty case is the pipeline's concern and raises
|
|
307
|
+
* `ConfigFileNotFoundError` before a strategy is ever consulted — so a strategy
|
|
308
|
+
* cannot fail.
|
|
309
|
+
*
|
|
310
|
+
* @public
|
|
311
|
+
*/
|
|
312
|
+
interface MergeStrategy<A> {
|
|
313
|
+
readonly name: string;
|
|
314
|
+
readonly resolve: (sources: NonEmptySources<A>) => Effect.Effect<A>;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Built-in merge strategies.
|
|
318
|
+
*
|
|
319
|
+
* @remarks
|
|
320
|
+
* Renamed from v3's `ConfigWalkStrategy`, which never walked anything — the
|
|
321
|
+
* thing that walks is the `upwardWalk` resolver.
|
|
322
|
+
*
|
|
323
|
+
* @public
|
|
324
|
+
*/
|
|
325
|
+
declare const MergeStrategy: {
|
|
326
|
+
readonly firstMatch: <A>() => MergeStrategy<A>;
|
|
327
|
+
readonly layeredMerge: <A>() => MergeStrategy<A>;
|
|
328
|
+
};
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/ConfigFile.d.ts
|
|
331
|
+
declare const ConfigFileNotFoundError_base: Schema.Class<ConfigFileNotFoundError, Schema.TaggedStruct<"ConfigFileNotFoundError", {
|
|
332
|
+
/** The names of the resolvers that were probed, in order. */
|
|
333
|
+
readonly searched: Schema.$Array<Schema.String>;
|
|
334
|
+
}>, import("effect/Cause").YieldableError>;
|
|
335
|
+
/**
|
|
336
|
+
* Indicates that the resolver chain produced no configuration source.
|
|
337
|
+
*
|
|
338
|
+
* @remarks
|
|
339
|
+
* Its own tag, so "no config anywhere" is routable with `Effect.catchTag`
|
|
340
|
+
* separately from "the config I found is broken" — the single most important
|
|
341
|
+
* distinction the v3 mega-error could not express.
|
|
342
|
+
*
|
|
343
|
+
* @public
|
|
344
|
+
*/
|
|
345
|
+
declare class ConfigFileNotFoundError extends ConfigFileNotFoundError_base {
|
|
346
|
+
get message(): string;
|
|
347
|
+
}
|
|
348
|
+
declare const ConfigFileReadError_base: Schema.Class<ConfigFileReadError, Schema.TaggedStruct<"ConfigFileReadError", {
|
|
349
|
+
/** The path that could not be read. */
|
|
350
|
+
readonly path: Schema.String;
|
|
351
|
+
/** The underlying failure, preserved structurally. */
|
|
352
|
+
readonly cause: Schema.Defect;
|
|
353
|
+
}>, import("effect/Cause").YieldableError>;
|
|
354
|
+
/**
|
|
355
|
+
* Indicates that a config file could not be read from the filesystem.
|
|
356
|
+
*
|
|
357
|
+
* @remarks
|
|
358
|
+
* `cause` preserves the underlying filesystem failure structurally. v3 flattened
|
|
359
|
+
* it to `reason: String(e)`.
|
|
360
|
+
*
|
|
361
|
+
* @public
|
|
362
|
+
*/
|
|
363
|
+
declare class ConfigFileReadError extends ConfigFileReadError_base {
|
|
364
|
+
get message(): string;
|
|
365
|
+
}
|
|
366
|
+
declare const ConfigFileWriteError_base: Schema.Class<ConfigFileWriteError, Schema.TaggedStruct<"ConfigFileWriteError", {
|
|
367
|
+
/** The path that could not be written. */
|
|
368
|
+
readonly path: Schema.String;
|
|
369
|
+
/** The underlying failure, preserved structurally. */
|
|
370
|
+
readonly cause: Schema.Defect;
|
|
371
|
+
}>, import("effect/Cause").YieldableError>;
|
|
372
|
+
/**
|
|
373
|
+
* Indicates that a config file could not be written to the filesystem.
|
|
374
|
+
*
|
|
375
|
+
* @public
|
|
376
|
+
*/
|
|
377
|
+
declare class ConfigFileWriteError extends ConfigFileWriteError_base {
|
|
378
|
+
get message(): string;
|
|
379
|
+
}
|
|
380
|
+
declare const ConfigDefaultPathMissingError_base: Schema.Class<ConfigDefaultPathMissingError, Schema.TaggedStruct<"ConfigDefaultPathMissingError", {}>, import("effect/Cause").YieldableError>;
|
|
381
|
+
/**
|
|
382
|
+
* Indicates that {@link ConfigFileShape.save} or {@link ConfigFileShape.update}
|
|
383
|
+
* was called on a service configured without a `defaultPath`.
|
|
384
|
+
*
|
|
385
|
+
* @remarks
|
|
386
|
+
* v3 reported this as a generic `ConfigError` carrying `operation: "save"` and
|
|
387
|
+
* `reason: "no default path configured"` — indistinguishable by tag from a real
|
|
388
|
+
* write failure.
|
|
389
|
+
*
|
|
390
|
+
* It carries no `path` field on purpose. The whole point of this failure is
|
|
391
|
+
* that there is no path; a {@link ConfigFileWriteError} with a fabricated path
|
|
392
|
+
* would be a lie, and lying error payloads are what this port exists to undo.
|
|
393
|
+
*
|
|
394
|
+
* @public
|
|
395
|
+
*/
|
|
396
|
+
declare class ConfigDefaultPathMissingError extends ConfigDefaultPathMissingError_base {
|
|
397
|
+
get message(): string;
|
|
398
|
+
}
|
|
399
|
+
declare const ConfigValidationError_base: Schema.Class<ConfigValidationError, Schema.TaggedStruct<"ConfigValidationError", {
|
|
400
|
+
/** The offending file, absent when `validate` was called on an in-memory value. */
|
|
401
|
+
readonly path: Schema.Option<Schema.String>;
|
|
402
|
+
/** The structured schema issue. Never a string. */
|
|
403
|
+
readonly issue: Schema.Defect;
|
|
404
|
+
}>, import("effect/Cause").YieldableError>;
|
|
405
|
+
/**
|
|
406
|
+
* Indicates that parsed config content did not satisfy the schema, or that a
|
|
407
|
+
* caller-supplied `validate` rejected it.
|
|
408
|
+
*
|
|
409
|
+
* @remarks
|
|
410
|
+
* `issue` carries the **structured** schema failure — at runtime a
|
|
411
|
+
* `SchemaIssue.Issue` tree, reachable through `_tag` and nested `issues`. v3
|
|
412
|
+
* flattened this to `String(ParseError)`, destroying every field a caller might
|
|
413
|
+
* branch on. It is typed `unknown` because v4 exposes no `Schema` for `Issue`;
|
|
414
|
+
* narrow it with the `SchemaIssue` module.
|
|
415
|
+
*
|
|
416
|
+
* @public
|
|
417
|
+
*/
|
|
418
|
+
declare class ConfigValidationError extends ConfigValidationError_base {
|
|
419
|
+
get message(): string;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* The failure modes of the full discovery-and-load path.
|
|
423
|
+
*
|
|
424
|
+
* @public
|
|
425
|
+
*/
|
|
426
|
+
type ConfigLoadError = ConfigFileNotFoundError | ConfigFileReadError | ConfigCodecError | ConfigValidationError;
|
|
427
|
+
/**
|
|
428
|
+
* The failure modes of reading one known path.
|
|
429
|
+
*
|
|
430
|
+
* @remarks
|
|
431
|
+
* Deliberately excludes {@link ConfigFileNotFoundError}: every method typed with
|
|
432
|
+
* this union either takes an explicit path or treats "nothing found" as success.
|
|
433
|
+
*
|
|
434
|
+
* @public
|
|
435
|
+
*/
|
|
436
|
+
type ConfigReadError = ConfigFileReadError | ConfigCodecError | ConfigValidationError;
|
|
437
|
+
/**
|
|
438
|
+
* The failure modes of encoding and writing one known path.
|
|
439
|
+
*
|
|
440
|
+
* @remarks
|
|
441
|
+
* Deliberately excludes {@link ConfigFileNotFoundError} — the path is explicit,
|
|
442
|
+
* so there is nothing to discover — and {@link ConfigDefaultPathMissingError},
|
|
443
|
+
* because no default path is consulted.
|
|
444
|
+
*
|
|
445
|
+
* @public
|
|
446
|
+
*/
|
|
447
|
+
type ConfigWriteError = ConfigFileWriteError | ConfigCodecError | ConfigValidationError;
|
|
448
|
+
/**
|
|
449
|
+
* The failure modes of {@link ConfigFileShape.save}.
|
|
450
|
+
*
|
|
451
|
+
* @public
|
|
452
|
+
*/
|
|
453
|
+
type ConfigSaveError = ConfigWriteError | ConfigDefaultPathMissingError;
|
|
454
|
+
/**
|
|
455
|
+
* The failure modes of {@link ConfigFileShape.update}, which loads and then saves.
|
|
456
|
+
*
|
|
457
|
+
* @public
|
|
458
|
+
*/
|
|
459
|
+
type ConfigUpdateError = ConfigLoadError | ConfigFileWriteError | ConfigDefaultPathMissingError;
|
|
460
|
+
/**
|
|
461
|
+
* The config file service, generic over the decoded config type `A`.
|
|
462
|
+
*
|
|
463
|
+
* @remarks
|
|
464
|
+
* Error unions are narrowed per method: `loadOrDefault` cannot fail with
|
|
465
|
+
* {@link ConfigFileNotFoundError} because that is the branch it handles, and
|
|
466
|
+
* `discover` treats an empty result as success.
|
|
467
|
+
*
|
|
468
|
+
* @public
|
|
469
|
+
*/
|
|
470
|
+
interface ConfigFileShape<A> {
|
|
471
|
+
/** Discover, decode and merge the highest-priority config source. */
|
|
472
|
+
readonly load: Effect.Effect<A, ConfigLoadError>;
|
|
473
|
+
/** Read, decode and validate one explicit path. */
|
|
474
|
+
readonly loadFrom: (path: string) => Effect.Effect<A, ConfigReadError>;
|
|
475
|
+
/**
|
|
476
|
+
* Every source the resolver chain found, in priority order. Empty is success.
|
|
477
|
+
*
|
|
478
|
+
* @remarks
|
|
479
|
+
* A found-but-corrupt source ABORTS discovery with a typed error rather than
|
|
480
|
+
* being silently skipped: silently skipping a corrupt file would mean running
|
|
481
|
+
* on the wrong config. This is deliberate, and is parity with v3.
|
|
482
|
+
*/
|
|
483
|
+
readonly discover: Effect.Effect<ReadonlyArray<ConfigSource<A>>, ConfigReadError>;
|
|
484
|
+
/**
|
|
485
|
+
* Like {@link ConfigFileShape.load}, but yields `defaultValue` when nothing is found.
|
|
486
|
+
*
|
|
487
|
+
* @remarks
|
|
488
|
+
* `defaultValue` is returned as-is: neither the schema nor `options.validate`
|
|
489
|
+
* is applied to it. It is trusted caller input, not a discovered document.
|
|
490
|
+
*/
|
|
491
|
+
readonly loadOrDefault: (defaultValue: A) => Effect.Effect<A, ConfigReadError>;
|
|
492
|
+
/** Decode and validate an in-memory value. */
|
|
493
|
+
readonly validate: (value: unknown) => Effect.Effect<A, ConfigValidationError>;
|
|
494
|
+
/**
|
|
495
|
+
* Encode `value` and write it to an explicit `path`.
|
|
496
|
+
*
|
|
497
|
+
* @remarks
|
|
498
|
+
* Does **not** create the parent directory — that is
|
|
499
|
+
* {@link ConfigFileShape.save}'s job, and the distinction is load-bearing:
|
|
500
|
+
* `write` targets a path the caller already vouched for.
|
|
501
|
+
*/
|
|
502
|
+
readonly write: (value: A, path: string) => Effect.Effect<void, ConfigWriteError>;
|
|
503
|
+
/**
|
|
504
|
+
* Resolve `defaultPath`, `mkdir -p` its parent, encode `value` into it, and
|
|
505
|
+
* return the path written.
|
|
506
|
+
*/
|
|
507
|
+
readonly save: (value: A) => Effect.Effect<string, ConfigSaveError>;
|
|
508
|
+
/**
|
|
509
|
+
* Load the current value, apply `fn`, {@link ConfigFileShape.save} the result
|
|
510
|
+
* and return it.
|
|
511
|
+
*
|
|
512
|
+
* @remarks
|
|
513
|
+
* With `defaultValue` the load cannot fail with
|
|
514
|
+
* {@link ConfigFileNotFoundError}; without it, it can.
|
|
515
|
+
*/
|
|
516
|
+
readonly update: (fn: (current: A) => A, defaultValue?: A) => Effect.Effect<A, ConfigUpdateError>;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Options for {@link ConfigFile.layer}.
|
|
520
|
+
*
|
|
521
|
+
* @remarks
|
|
522
|
+
* `RR` is the union of the resolvers' requirements. It flows into the layer's
|
|
523
|
+
* `R` rather than being cast away.
|
|
524
|
+
*
|
|
525
|
+
* @public
|
|
526
|
+
*/
|
|
527
|
+
interface ConfigFileOptions<A, I, RR> {
|
|
528
|
+
/**
|
|
529
|
+
* The schema every discovered document is decoded through.
|
|
530
|
+
*
|
|
531
|
+
* @remarks
|
|
532
|
+
* `Schema.Codec<A, I>` rather than v4's one-parameter `Schema.Schema<A>`,
|
|
533
|
+
* because the encoded form `I` matters on the write path. Its decoding and
|
|
534
|
+
* encoding service channels default to `never`, keeping `decode` free of
|
|
535
|
+
* requirements.
|
|
536
|
+
*/
|
|
537
|
+
readonly schema: Schema.Codec<A, I>;
|
|
538
|
+
/** How file content becomes an unknown document, and back. */
|
|
539
|
+
readonly codec: ConfigCodec;
|
|
540
|
+
/** The resolver chain, in priority order. */
|
|
541
|
+
readonly resolvers: ReadonlyArray<ConfigResolver<RR>>;
|
|
542
|
+
/** How several discovered sources become one value. */
|
|
543
|
+
readonly strategy: MergeStrategy<A>;
|
|
544
|
+
/** An optional caller-supplied check run after schema decoding. */
|
|
545
|
+
readonly validate?: (value: A) => Effect.Effect<A, ConfigValidationError>;
|
|
546
|
+
/**
|
|
547
|
+
* Where {@link ConfigFileShape.save} writes when given no explicit path.
|
|
548
|
+
*
|
|
549
|
+
* @remarks
|
|
550
|
+
* Its requirements join the resolvers' in `RR` and flow into the layer's `R`.
|
|
551
|
+
* v3 typed this `Effect<string, ConfigError, any>` and cast the requirements
|
|
552
|
+
* away at the call site.
|
|
553
|
+
*
|
|
554
|
+
* When absent, `save` and `update` fail with
|
|
555
|
+
* {@link ConfigDefaultPathMissingError}.
|
|
556
|
+
*/
|
|
557
|
+
readonly defaultPath?: Effect.Effect<string, never, RR>;
|
|
558
|
+
/**
|
|
559
|
+
* The opt-in event hook. Pass the `ConfigEvents` class itself.
|
|
560
|
+
*
|
|
561
|
+
* @remarks
|
|
562
|
+
* A **key**, not an instance: the service is looked up in the ambient context
|
|
563
|
+
* at call time with `Effect.serviceOption`, so it never enters the layer's
|
|
564
|
+
* `R`. When this is omitted, `emit` is `Effect.void` — it does not even
|
|
565
|
+
* perform the lookup. That is what "zero-cost when absent" means here.
|
|
566
|
+
*/
|
|
567
|
+
readonly events?: Context.Key<ConfigEvents, ConfigEventsShape>;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Options for {@link ConfigFile.testLayer}.
|
|
571
|
+
*
|
|
572
|
+
* @remarks
|
|
573
|
+
* Deliberately has no `resolvers`: `testLayer` synthesizes one
|
|
574
|
+
* {@link (ConfigResolver:variable).staticDir} per seeded file, in `files`
|
|
575
|
+
* insertion order, so the first key wins under
|
|
576
|
+
* {@link (MergeStrategy:variable).firstMatch}.
|
|
577
|
+
*
|
|
578
|
+
* It also has no `defaultPath`. Nothing in the temp directory is a defensible
|
|
579
|
+
* default write target, so `save` and `update` fail with
|
|
580
|
+
* {@link ConfigDefaultPathMissingError} under this layer — the honest answer.
|
|
581
|
+
* Exercise the write path with {@link ConfigFile.layer} instead.
|
|
582
|
+
*
|
|
583
|
+
* @public
|
|
584
|
+
*/
|
|
585
|
+
interface ConfigFileTestOptions<A, I> {
|
|
586
|
+
/** The schema every seeded document is decoded through. */
|
|
587
|
+
readonly schema: Schema.Codec<A, I>;
|
|
588
|
+
/** How file content becomes an unknown document, and back. */
|
|
589
|
+
readonly codec: ConfigCodec;
|
|
590
|
+
/** How several discovered sources become one value. */
|
|
591
|
+
readonly strategy: MergeStrategy<A>;
|
|
592
|
+
/**
|
|
593
|
+
* Filenames (relative to the temp dir) mapped to their raw contents.
|
|
594
|
+
*
|
|
595
|
+
* @remarks
|
|
596
|
+
* A name may contain separators (`"nested/.apprc"`); the parent directory is
|
|
597
|
+
* created for you.
|
|
598
|
+
*/
|
|
599
|
+
readonly files: Record<string, string>;
|
|
600
|
+
/** An optional caller-supplied check run after schema decoding. */
|
|
601
|
+
readonly validate?: (value: A) => Effect.Effect<A, ConfigValidationError>;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* The config file service: a per-schema service factory and its layers.
|
|
605
|
+
*
|
|
606
|
+
* @public
|
|
607
|
+
*/
|
|
608
|
+
declare const ConfigFile: {
|
|
609
|
+
readonly Service: <Self, A>() => <const Id extends string>(id: Id) => Context.ServiceClass<Self, Id, ConfigFileShape<A>>;
|
|
610
|
+
readonly layer: <Self, A, I, RR = never>(tag: Context.Key<Self, ConfigFileShape<A>>, options: ConfigFileOptions<A, I, RR>) => Layer.Layer<Self, never, FileSystem.FileSystem | Path.Path | RR>;
|
|
611
|
+
readonly testLayer: <Self, A, I>(tag: Context.Key<Self, ConfigFileShape<A>>, options: ConfigFileTestOptions<A, I>) => Layer.Layer<Self, never, FileSystem.FileSystem | Path.Path>;
|
|
612
|
+
};
|
|
613
|
+
//#endregion
|
|
614
|
+
//#region src/ConfigMigration.d.ts
|
|
615
|
+
declare const ConfigMigrationError_base: Schema.Class<ConfigMigrationError, Schema.TaggedStruct<"ConfigMigrationError", {
|
|
616
|
+
/** The target version of the step that failed. `0` when reading the version failed. */
|
|
617
|
+
readonly version: Schema.Number;
|
|
618
|
+
/** The name of the step that failed; empty when reading the version failed. */
|
|
619
|
+
readonly name: Schema.String;
|
|
620
|
+
/** Which stage of a migration step failed. */
|
|
621
|
+
readonly phase: Schema.Literals<readonly ["read-version", "apply", "write-version"]>;
|
|
622
|
+
/** The underlying failure, preserved structurally. */
|
|
623
|
+
readonly cause: Schema.Defect;
|
|
624
|
+
}>, import("effect/Cause").YieldableError>;
|
|
625
|
+
/**
|
|
626
|
+
* Indicates that a versioned config migration failed.
|
|
627
|
+
*
|
|
628
|
+
* @remarks
|
|
629
|
+
* `phase` says where: reading the current version, applying a step, or writing
|
|
630
|
+
* the new version back. `cause` preserves the underlying failure by identity
|
|
631
|
+
* when the failing step signals recoverable failure with `Effect.fail`. v3
|
|
632
|
+
* assembled all three into a prose `reason` string.
|
|
633
|
+
*
|
|
634
|
+
* @public
|
|
635
|
+
*/
|
|
636
|
+
declare class ConfigMigrationError extends ConfigMigrationError_base {
|
|
637
|
+
get message(): string;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* A single versioned migration step.
|
|
641
|
+
*
|
|
642
|
+
* @remarks
|
|
643
|
+
* v3 also declared a `down` reverse migration. It was never invoked anywhere in
|
|
644
|
+
* the codebase, so it is not ported.
|
|
645
|
+
*
|
|
646
|
+
* @public
|
|
647
|
+
*/
|
|
648
|
+
interface ConfigFileMigration {
|
|
649
|
+
readonly version: number;
|
|
650
|
+
readonly name: string;
|
|
651
|
+
/**
|
|
652
|
+
* Transforms the parsed config. Signal recoverable failure with `Effect.fail`;
|
|
653
|
+
* a synchronous `throw` is treated as a defect, not a `ConfigMigrationError`.
|
|
654
|
+
*/
|
|
655
|
+
readonly up: (raw: unknown) => Effect.Effect<unknown, unknown>;
|
|
656
|
+
}
|
|
657
|
+
/** How the version number is read from and written to the parsed config. @public */
|
|
658
|
+
interface VersionAccess {
|
|
659
|
+
readonly get: (raw: unknown) => Effect.Effect<number, unknown>;
|
|
660
|
+
readonly set: (raw: unknown, version: number) => Effect.Effect<unknown, unknown>;
|
|
661
|
+
}
|
|
662
|
+
/** Reads and writes a top-level `version` field. @public */
|
|
663
|
+
declare const VersionAccess: {
|
|
664
|
+
readonly default: VersionAccess;
|
|
665
|
+
};
|
|
666
|
+
/** Options for {@link ConfigMigration.make}. @public */
|
|
667
|
+
interface ConfigMigrationOptions {
|
|
668
|
+
readonly codec: ConfigCodec;
|
|
669
|
+
readonly migrations: ReadonlyArray<ConfigFileMigration>;
|
|
670
|
+
readonly versionAccess?: VersionAccess;
|
|
671
|
+
}
|
|
672
|
+
/** Versioned migration support for config codecs. @public */
|
|
673
|
+
declare const ConfigMigration: {
|
|
674
|
+
readonly make: (options: ConfigMigrationOptions) => ConfigCodec<ConfigCodecError | ConfigMigrationError>;
|
|
675
|
+
};
|
|
676
|
+
//#endregion
|
|
677
|
+
//#region src/ConfigProvider.d.ts
|
|
678
|
+
/**
|
|
679
|
+
* Expose a loaded, merged, schema-validated document as a v4 `ConfigProvider`,
|
|
680
|
+
* so it can be read through `Config.string("port")` and layered beneath other
|
|
681
|
+
* providers.
|
|
682
|
+
*
|
|
683
|
+
* @remarks
|
|
684
|
+
* Strictly additive, and deliberately in its own module so it never becomes a
|
|
685
|
+
* required import. The schema-validated whole-document
|
|
686
|
+
* {@link ConfigFileShape.load} remains the primary API: v4's `Config` has no
|
|
687
|
+
* whole-document story, and this function is the bridge, not a replacement.
|
|
688
|
+
*
|
|
689
|
+
* Three properties, in order of how easy they are to lose:
|
|
690
|
+
*
|
|
691
|
+
* 1. **A missing file is a failure, not an empty provider.** The
|
|
692
|
+
* {@link ConfigFileNotFoundError} propagates. Handing back an empty provider
|
|
693
|
+
* would silently turn every subsequent `Config` read into "not found", which
|
|
694
|
+
* is the exact class of lie this port exists to undo.
|
|
695
|
+
* 2. **Nested keys are structural, not dotted.** `fromUnknown` descends one path
|
|
696
|
+
* segment at a time, so `{ db: { host } }` is reached with
|
|
697
|
+
* `Config.nested(Config.string("host"), "db")` — never `Config.string("db.host")`,
|
|
698
|
+
* which is looked up as a single literal key and fails. No flattening happens
|
|
699
|
+
* here, because none is needed.
|
|
700
|
+
* 3. **The decoded value is handed over as-is.** `fromUnknown` descends with
|
|
701
|
+
* `Object.hasOwn`, and a `Schema.Class` instance carries its fields as own
|
|
702
|
+
* properties, so no encoding step is required. The corollary: leaves are read
|
|
703
|
+
* in their **decoded** form, and a field whose decoded type is not a JSON
|
|
704
|
+
* primitive is exposed **structurally**, not turned into a value — with no
|
|
705
|
+
* two decoded types exposed the same way. A `Date` has no own enumerable
|
|
706
|
+
* properties, so it descends as an empty record and any nested read finds
|
|
707
|
+
* nothing. Such a field reports the same `ConfigError` as a missing key —
|
|
708
|
+
* `Expected string, got undefined` — so a `Config` read of a present `Date`
|
|
709
|
+
* field looks exactly like a typo in the key name. An `Option.some`
|
|
710
|
+
* descends as a record carrying Effect's internal `value` own-property, so
|
|
711
|
+
* `Config.nested(Config.string("value"), "field")` happens to read the
|
|
712
|
+
* wrapped value straight through — an internal representation, not a
|
|
713
|
+
* supported spelling. An `Option.none` has no own keys and so reads as
|
|
714
|
+
* absent, making `Some` and `None` asymmetric. None of this is a supported
|
|
715
|
+
* way to read such a field: `load` is the API that was designed to carry it.
|
|
716
|
+
*
|
|
717
|
+
* Descent by own property is also why a prototype getter and `__proto__` are
|
|
718
|
+
* both unreachable through the returned provider.
|
|
719
|
+
*
|
|
720
|
+
* @example
|
|
721
|
+
* ```ts
|
|
722
|
+
* const fileProvider = yield* asConfigProvider(cfg);
|
|
723
|
+
* const provider = ConfigProvider.orElse(ConfigProvider.fromEnv(), fileProvider);
|
|
724
|
+
* ```
|
|
725
|
+
*
|
|
726
|
+
* @public
|
|
727
|
+
*/
|
|
728
|
+
declare const asConfigProvider: <A>(service: ConfigFileShape<A>) => Effect.Effect<ConfigProvider.ConfigProvider, ConfigLoadError>;
|
|
729
|
+
/**
|
|
730
|
+
* Options for {@link layerConfigProvider}.
|
|
731
|
+
*
|
|
732
|
+
* @public
|
|
733
|
+
*/
|
|
734
|
+
interface LayerConfigProviderOptions {
|
|
735
|
+
/**
|
|
736
|
+
* Make the config file the primary source, consulted before the ambient
|
|
737
|
+
* provider rather than after it.
|
|
738
|
+
*
|
|
739
|
+
* @remarks
|
|
740
|
+
* Defaults to `false`, which is the precedence almost every application
|
|
741
|
+
* wants: an environment variable overrides the file it was deployed with.
|
|
742
|
+
*/
|
|
743
|
+
readonly asPrimary?: boolean;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Install a loaded config document as a fallback beneath the **ambient**
|
|
747
|
+
* `ConfigProvider`, so `Config` accessors read env first and the file second.
|
|
748
|
+
*
|
|
749
|
+
* @remarks
|
|
750
|
+
* This is the composition the v3 library could not express. This layer
|
|
751
|
+
* composes beneath the **ambient** `ConfigProvider`, v4's `Context.Reference`
|
|
752
|
+
* for it, so a consumer supplying `ConfigProvider.layer(...)` controls
|
|
753
|
+
* precedence explicitly: whatever that provider resolves wins, and the config
|
|
754
|
+
* file supplies whatever it lacks. That reference's own default is
|
|
755
|
+
* `ConfigProvider.fromEnv()`, which is what most applications want; this
|
|
756
|
+
* module composes beneath whichever provider is ambient rather than pinning
|
|
757
|
+
* that default itself, so verify the precedence you need with the provider
|
|
758
|
+
* you actually wire in.
|
|
759
|
+
*
|
|
760
|
+
* The load happens when the layer is built, and a {@link ConfigFileNotFoundError}
|
|
761
|
+
* surfaces in the layer's error channel rather than degrading to an empty
|
|
762
|
+
* provider — the same honesty {@link asConfigProvider} keeps.
|
|
763
|
+
*
|
|
764
|
+
* @example
|
|
765
|
+
* ```ts
|
|
766
|
+
* const stack = layerConfigProvider(AppConfig).pipe(Layer.provide(AppConfigLive));
|
|
767
|
+
* ```
|
|
768
|
+
*
|
|
769
|
+
* @public
|
|
770
|
+
*/
|
|
771
|
+
declare const layerConfigProvider: <Self, A>(tag: Context.Key<Self, ConfigFileShape<A>>, options?: LayerConfigProviderOptions) => Layer.Layer<never, ConfigLoadError, Self>;
|
|
772
|
+
//#endregion
|
|
773
|
+
//#region src/EncryptedCodec.d.ts
|
|
774
|
+
declare const ConfigEncryptionError_base: Schema.Class<ConfigEncryptionError, Schema.TaggedStruct<"ConfigEncryptionError", {
|
|
775
|
+
/** Which cryptographic stage failed. */
|
|
776
|
+
readonly phase: Schema.Literals<readonly ["key-derivation", "encrypt", "decrypt", "encoding"]>;
|
|
777
|
+
/** The underlying failure, preserved structurally. */
|
|
778
|
+
readonly cause: Schema.Defect;
|
|
779
|
+
}>, import("effect/Cause").YieldableError>;
|
|
780
|
+
/**
|
|
781
|
+
* Indicates that an encryption, decryption, key-derivation or base64 step
|
|
782
|
+
* failed.
|
|
783
|
+
*
|
|
784
|
+
* @remarks
|
|
785
|
+
* Its own error rather than v3's `"key-derivation"` value on the generic
|
|
786
|
+
* `ConfigCodecError.operation` union — an encryption-only concern was leaking
|
|
787
|
+
* into every codec's error type. `cause` preserves the underlying host failure
|
|
788
|
+
* structurally; v3 assembled it into a prose `reason` string.
|
|
789
|
+
*
|
|
790
|
+
* @public
|
|
791
|
+
*/
|
|
792
|
+
declare class ConfigEncryptionError extends ConfigEncryptionError_base {
|
|
793
|
+
get message(): string;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Key source union for {@link EncryptedCodec}.
|
|
797
|
+
*
|
|
798
|
+
* @remarks
|
|
799
|
+
* Use {@link (EncryptedCodecKey:variable).fromCryptoKey} to supply a pre-derived
|
|
800
|
+
* `CryptoKey`, or {@link (EncryptedCodecKey:variable).fromPassphrase} to derive
|
|
801
|
+
* one via PBKDF2 at first use.
|
|
802
|
+
*
|
|
803
|
+
* @public
|
|
804
|
+
*/
|
|
805
|
+
type EncryptedCodecKey = {
|
|
806
|
+
readonly _tag: "CryptoKey";
|
|
807
|
+
readonly key: Effect.Effect<CryptoKey, ConfigEncryptionError>;
|
|
808
|
+
} | {
|
|
809
|
+
readonly _tag: "Passphrase";
|
|
810
|
+
readonly passphrase: string;
|
|
811
|
+
readonly salt: Uint8Array;
|
|
812
|
+
};
|
|
813
|
+
/**
|
|
814
|
+
* Convenience constructors for {@link (EncryptedCodecKey:type)}.
|
|
815
|
+
*
|
|
816
|
+
* @public
|
|
817
|
+
*/
|
|
818
|
+
declare const EncryptedCodecKey: {
|
|
819
|
+
/**
|
|
820
|
+
* Use a pre-derived `CryptoKey` effect directly.
|
|
821
|
+
*
|
|
822
|
+
* @remarks
|
|
823
|
+
* The effect is resolved once per codec instance and its **success** is
|
|
824
|
+
* reused for every encrypt/decrypt operation. A failure or an interruption
|
|
825
|
+
* is not cached — the next operation resolves it again. Supply your own
|
|
826
|
+
* `Effect.retry` inside this effect to bound retries; wrap it in
|
|
827
|
+
* `Effect.cached` yourself if you want a failure to be terminal.
|
|
828
|
+
*
|
|
829
|
+
* A `throw` from it is a programmer bug and stays a defect; signal
|
|
830
|
+
* recoverable failure with `Effect.fail`.
|
|
831
|
+
*/
|
|
832
|
+
readonly fromCryptoKey: (key: Effect.Effect<CryptoKey, ConfigEncryptionError>) => EncryptedCodecKey;
|
|
833
|
+
/**
|
|
834
|
+
* Derive a `CryptoKey` from a passphrase and salt via PBKDF2.
|
|
835
|
+
*
|
|
836
|
+
* @remarks
|
|
837
|
+
* Derivation runs lazily on the first encrypt/decrypt call. It is resolved
|
|
838
|
+
* once per codec instance and its **success** is reused for subsequent
|
|
839
|
+
* operations on that instance. A failure or an interruption is not cached —
|
|
840
|
+
* the next operation derives again.
|
|
841
|
+
*/
|
|
842
|
+
readonly fromPassphrase: (passphrase: string, salt: Uint8Array) => EncryptedCodecKey;
|
|
843
|
+
};
|
|
844
|
+
/**
|
|
845
|
+
* Wrap any {@link (ConfigCodec:interface)} with AES-GCM encryption.
|
|
846
|
+
*
|
|
847
|
+
* @remarks
|
|
848
|
+
* `stringify` serializes with the inner codec, generates a random 12-byte IV,
|
|
849
|
+
* encrypts, prepends the IV to the ciphertext and base64-encodes the result.
|
|
850
|
+
* `parse` reverses that: the first 12 bytes of the decoded envelope are the IV,
|
|
851
|
+
* the remainder is the ciphertext, and the plaintext is handed to the inner
|
|
852
|
+
* codec's `parse`.
|
|
853
|
+
*
|
|
854
|
+
* The error channel **widens** to `E | ConfigEncryptionError` rather than
|
|
855
|
+
* flattening — the inner codec's failures stay distinguishable from
|
|
856
|
+
* cryptographic ones.
|
|
857
|
+
*
|
|
858
|
+
* @public
|
|
859
|
+
*/
|
|
860
|
+
declare function EncryptedCodec<E>(inner: ConfigCodec<E>, keySource: EncryptedCodecKey): ConfigCodec<E | ConfigEncryptionError>;
|
|
861
|
+
//#endregion
|
|
862
|
+
//#region src/JsonCodec.d.ts
|
|
863
|
+
/**
|
|
864
|
+
* A `ConfigCodec` backed by the host `JSON` global: plain JSON as
|
|
865
|
+
* configuration file content.
|
|
866
|
+
*
|
|
867
|
+
* @remarks
|
|
868
|
+
* The only codec that reaches no parsing engine at all — it is why this
|
|
869
|
+
* package can be depended on for JSON config alone without pulling a parser
|
|
870
|
+
* into the bundle. Both directions preserve the underlying failure
|
|
871
|
+
* structurally in `cause` — never stringified.
|
|
872
|
+
*
|
|
873
|
+
* @public
|
|
874
|
+
*/
|
|
875
|
+
declare const JsonCodec: ConfigCodec;
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region src/JsoncCodec.d.ts
|
|
878
|
+
/**
|
|
879
|
+
* A `ConfigCodec` backed by `@effected/jsonc`: JSON with comments and
|
|
880
|
+
* trailing commas.
|
|
881
|
+
*
|
|
882
|
+
* @remarks
|
|
883
|
+
* `@effected/jsonc` does not expose a `stringify` — its schema layer's encode
|
|
884
|
+
* direction is `JSON.stringify` (comments never survive a round-trip encode;
|
|
885
|
+
* see `Jsonc.fromString`'s remarks), so `stringify` here calls `JSON.stringify`
|
|
886
|
+
* directly and wraps a thrown defect the same way `JsonCodec` does.
|
|
887
|
+
* Both directions preserve the underlying failure structurally in `cause` —
|
|
888
|
+
* never stringified.
|
|
889
|
+
*
|
|
890
|
+
* @public
|
|
891
|
+
*/
|
|
892
|
+
declare const JsoncCodec: ConfigCodec;
|
|
893
|
+
//#endregion
|
|
894
|
+
//#region src/TomlCodec.d.ts
|
|
895
|
+
/**
|
|
896
|
+
* A `ConfigCodec` backed by `@effected/toml`.
|
|
897
|
+
*
|
|
898
|
+
* @remarks
|
|
899
|
+
* `@effected/toml`'s input hardening — a nesting-depth cap on arrays and
|
|
900
|
+
* inline tables, enforced independently on both the parse and stringify
|
|
901
|
+
* sides — fails through the typed error channel, so a hostile config file
|
|
902
|
+
* surfaces as a `ConfigCodecError` rather than crashing the process. Both
|
|
903
|
+
* directions preserve the underlying failure structurally in `cause` —
|
|
904
|
+
* never stringified.
|
|
905
|
+
*
|
|
906
|
+
* Stringify is genuinely fallible beyond hostile input: TOML has no null,
|
|
907
|
+
* so a document carrying `null` (or any other unrepresentable value, an
|
|
908
|
+
* out-of-int64-range `bigint`, a circular reference) fails with a
|
|
909
|
+
* `ConfigCodecError` whose `cause` is the structured `TomlStringifyError`.
|
|
910
|
+
*
|
|
911
|
+
* @public
|
|
912
|
+
*/
|
|
913
|
+
declare const TomlCodec: ConfigCodec;
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/YamlCodec.d.ts
|
|
916
|
+
/**
|
|
917
|
+
* A `ConfigCodec` backed by `@effected/yaml`.
|
|
918
|
+
*
|
|
919
|
+
* @remarks
|
|
920
|
+
* `@effected/yaml`'s input hardening — an alias-expansion budget guarding
|
|
921
|
+
* against "billion laughs" alias bombs, and a collection-nesting depth cap —
|
|
922
|
+
* fails through the typed error channel, so a hostile config file surfaces
|
|
923
|
+
* as a `ConfigCodecError` rather than crashing the process. Both directions
|
|
924
|
+
* preserve the underlying failure structurally in `cause` — never
|
|
925
|
+
* stringified.
|
|
926
|
+
*
|
|
927
|
+
* @public
|
|
928
|
+
*/
|
|
929
|
+
declare const YamlCodec: ConfigCodec;
|
|
930
|
+
//#endregion
|
|
931
|
+
export { type ConfigCodec, ConfigCodecError, ConfigDefaultPathMissingError, ConfigEncryptionError, ConfigEvent, ConfigEventPayload, ConfigEvents, type ConfigEventsShape, ConfigFile, type ConfigFileMigration, ConfigFileNotFoundError, type ConfigFileOptions, ConfigFileReadError, type ConfigFileShape, type ConfigFileTestOptions, ConfigFileWriteError, type ConfigLoadError, ConfigMigration, ConfigMigrationError, type ConfigMigrationOptions, type ConfigReadError, ConfigResolver, type ConfigSaveError, type ConfigSource, ConfigSourceRef, type ConfigUpdateError, ConfigValidationError, type ConfigWriteError, EncryptedCodec, EncryptedCodecKey, JsonCodec, JsoncCodec, type LayerConfigProviderOptions, MergeStrategy, type NonEmptySources, TomlCodec, VersionAccess, YamlCodec, asConfigProvider, layerConfigProvider };
|
|
932
|
+
//# sourceMappingURL=index.d.ts.map
|