@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/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