@effected/config-file 0.3.1 → 0.4.1
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/ConfigFile.js +2 -2
- package/README.md +27 -0
- package/index.d.ts +34 -1
- package/package.json +1 -1
package/ConfigFile.js
CHANGED
|
@@ -133,7 +133,7 @@ const makeImpl = (options, fs, path, resolverEnv) => {
|
|
|
133
133
|
path: s.path,
|
|
134
134
|
resolver: s.resolver
|
|
135
135
|
}));
|
|
136
|
-
const decode = (parsed, at) => Schema.decodeUnknownEffect(options.schema)(parsed).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(new ConfigValidationError({
|
|
136
|
+
const decode = (parsed, at) => Schema.decodeUnknownEffect(options.schema)(parsed, options.parseOptions).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(new ConfigValidationError({
|
|
137
137
|
path: at,
|
|
138
138
|
issue: error.issue
|
|
139
139
|
}))));
|
|
@@ -354,7 +354,7 @@ const read = (path, options) => Effect.gen(function* () {
|
|
|
354
354
|
cause
|
|
355
355
|
})));
|
|
356
356
|
const parsed = yield* options.codec.parse(raw);
|
|
357
|
-
return yield* Schema.decodeUnknownEffect(options.schema)(parsed).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(new ConfigValidationError({
|
|
357
|
+
return yield* Schema.decodeUnknownEffect(options.schema)(parsed, options.parseOptions).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(new ConfigValidationError({
|
|
358
358
|
path: Option.some(path),
|
|
359
359
|
issue: error.issue
|
|
360
360
|
}))));
|
package/README.md
CHANGED
|
@@ -117,6 +117,32 @@ Effect.runPromise(program.pipe(Effect.provide(NodeFileSystem.layer))).then(conso
|
|
|
117
117
|
|
|
118
118
|
It is deliberately read-only and discovery-free — no resolver chain, no `save`/`update`. Reach for `ConfigFile.layer` the moment either is wanted.
|
|
119
119
|
|
|
120
|
+
## Rejecting keys the schema does not know
|
|
121
|
+
|
|
122
|
+
Effect's decoder ignores unknown keys by default, which for a config loader means a typo'd section is dropped in silence. The user gets no error, the setting they wrote has no effect, and nothing in the run says why. `parseOptions` threads decode options into every decode the loader performs, on `ConfigFile.layer` and `ConfigFile.read` alike:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { ConfigFile, ConfigResolver, JsonCodec, MergeStrategy } from "@effected/config-file";
|
|
126
|
+
import { Schema } from "effect";
|
|
127
|
+
|
|
128
|
+
class Settings extends Schema.Class<Settings>("Settings")({ port: Schema.Number }) {}
|
|
129
|
+
class SettingsConfig extends ConfigFile.Service<SettingsConfig, Settings>()("app/Settings") {}
|
|
130
|
+
|
|
131
|
+
export const SettingsLive = ConfigFile.layer(SettingsConfig, {
|
|
132
|
+
schema: Settings,
|
|
133
|
+
codec: JsonCodec,
|
|
134
|
+
resolvers: [ConfigResolver.upwardWalk({ filename: ".apprc" })],
|
|
135
|
+
strategy: MergeStrategy.firstMatch<Settings>(),
|
|
136
|
+
parseOptions: { onExcessProperty: "error", errors: "all" },
|
|
137
|
+
});
|
|
138
|
+
// A file carrying `{ "port": 3000, "prot": 3001 }` now fails with a
|
|
139
|
+
// ConfigValidationError whose issue tree names the offending path.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The `validate` option cannot stand in for this: it runs on the decoded value, by which point the excess keys are already gone. Pair `onExcessProperty: "error"` with `errors: "all"` — the decoder reports only the first problem otherwise, so a file with three typos costs the user three fix-and-rerun cycles. The extra work happens only on a document that is already failing.
|
|
143
|
+
|
|
144
|
+
A schema that deliberately admits a pass-through section keeps working under `"error"` — but know why, because the shape suggests the opposite: a `Schema.StructWithRest` rest **switches excess checking off for that struct entirely**, not merely for the keys the rest covers. Structs without a rest stay strict independently, so strictness is decided per level rather than per key. Omitting `parseOptions` changes nothing, which makes turning this on a per-loader decision rather than a migration.
|
|
145
|
+
|
|
120
146
|
## Errors
|
|
121
147
|
|
|
122
148
|
Every failure is a tagged error you route on with `Effect.catchTag`. The tags exist so that recovery can differ:
|
|
@@ -185,6 +211,7 @@ export const secret = EncryptedCodec(migrating, EncryptedCodecKey.fromPassphrase
|
|
|
185
211
|
## Features
|
|
186
212
|
|
|
187
213
|
- `ConfigFile.Service` / `ConfigFile.layer` / `ConfigFile.testLayer` — a per-schema service class and its layers. `testLayer` seeds files into a temp directory and wires the *real* implementation over them, so tests exercise the actual pipeline rather than a stub that can drift from it.
|
|
214
|
+
- `parseOptions` — decode options threaded into every decode, on the layer and on `read`. `onExcessProperty: "error"` is the only way to report a typo'd section or a field the schema deliberately removed.
|
|
188
215
|
- `ConfigFile.read` — the one-shot escape from the service: read, decode and validate one explicit path, schema and codec named per call, with no resolver chain and no write path.
|
|
189
216
|
- `ConfigResolver` — `explicitPath`, `staticDir`, `upwardWalk`, `workspaceRoot`, `gitRoot` and `systemEtc`. A resolver's error channel is `never` by contract: every filesystem failure becomes `Option.none()`, so one unreadable tier never aborts the chain.
|
|
190
217
|
- `MergeStrategy` — `firstMatch` and `layeredMerge`, combining discovered sources in priority order.
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfigProvider, Context, Effect, FileSystem, Layer, Option, Path, PubSub, Schema } from "effect";
|
|
1
|
+
import { ConfigProvider, Context, Effect, FileSystem, Layer, Option, Path, PubSub, Schema, SchemaAST } from "effect";
|
|
2
2
|
//#region src/ConfigCodec.d.ts
|
|
3
3
|
declare const ConfigCodecError_base: Schema.Class<ConfigCodecError, Schema.TaggedStruct<"ConfigCodecError", {
|
|
4
4
|
/** The codec that failed, e.g. `"json"`. */
|
|
@@ -564,6 +564,31 @@ interface ConfigFileOptions<A, I, RR> {
|
|
|
564
564
|
readonly strategy: MergeStrategy<A>;
|
|
565
565
|
/** An optional caller-supplied check run after schema decoding. */
|
|
566
566
|
readonly validate?: (value: A) => Effect.Effect<A, ConfigValidationError>;
|
|
567
|
+
/**
|
|
568
|
+
* Parse options threaded into every schema decode this performs.
|
|
569
|
+
*
|
|
570
|
+
* @remarks
|
|
571
|
+
* The field that matters here is `onExcessProperty`. It defaults to
|
|
572
|
+
* `"ignore"` in core, so a document's unknown keys are dropped silently and
|
|
573
|
+
* a loader cannot report a typo'd section — or enforce a field this schema
|
|
574
|
+
* deliberately removed. `{ onExcessProperty: "error" }` turns both into a
|
|
575
|
+
* {@link ConfigValidationError} whose issue names the offending path.
|
|
576
|
+
*
|
|
577
|
+
* It cannot be expressed with {@link ConfigFileOptions.validate}: that runs
|
|
578
|
+
* on the *decoded* value, by which point the excess keys are already gone.
|
|
579
|
+
*
|
|
580
|
+
* Keys covered by a `Schema.StructWithRest` rest are not excess, so a schema
|
|
581
|
+
* that deliberately admits a pass-through section keeps working under
|
|
582
|
+
* `"error"`.
|
|
583
|
+
*
|
|
584
|
+
* Absent, nothing changes: core's defaults apply.
|
|
585
|
+
*
|
|
586
|
+
* Pair it with `errors: "all"`. Core defaults to `"first"`, which for a
|
|
587
|
+
* *loader* means a file with three typos surfaces one per run — fix,
|
|
588
|
+
* re-run, discover the next. The extra work only happens on a document
|
|
589
|
+
* that is already failing.
|
|
590
|
+
*/
|
|
591
|
+
readonly parseOptions?: SchemaAST.ParseOptions;
|
|
567
592
|
/**
|
|
568
593
|
* Where {@link ConfigFileShape.save} writes when given no explicit path.
|
|
569
594
|
*
|
|
@@ -640,6 +665,14 @@ interface ConfigReadOptions<A, I> {
|
|
|
640
665
|
* their engines stay out of the bundle.
|
|
641
666
|
*/
|
|
642
667
|
readonly codec: ConfigCodec;
|
|
668
|
+
/**
|
|
669
|
+
* Parse options for the decode, chiefly `onExcessProperty`.
|
|
670
|
+
*
|
|
671
|
+
* @remarks
|
|
672
|
+
* See {@link ConfigFileOptions.parseOptions}; it means the same thing here.
|
|
673
|
+
* Absent, core's defaults apply and unknown keys are dropped silently.
|
|
674
|
+
*/
|
|
675
|
+
readonly parseOptions?: SchemaAST.ParseOptions;
|
|
643
676
|
}
|
|
644
677
|
/**
|
|
645
678
|
* The config file service: a per-schema service factory, its layers, and the
|
package/package.json
CHANGED