@effected/schemastore 0.1.2 → 0.2.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 CHANGED
@@ -181,11 +181,17 @@ declare class InvalidSchemaVersionError extends InvalidSchemaVersionError_base {
181
181
  get message(): string;
182
182
  }
183
183
  /**
184
- * A SchemaStore version label: a branded string validated against the
185
- * catalog's version grammar (`major[.minor[.patch]][-prerelease]`). The
186
- * label round-trips verbatim into file names and catalog `versions` keys;
187
- * ordering pads it to a full SemVer internally (see
188
- * {@link SchemaVersioning.Order}).
184
+ * A schema version label: a branded string holding a **full three-component
185
+ * SemVer** `major.minor.patch` with an optional prerelease, validated by
186
+ * `@effected/semver` itself. Build metadata is rejected (see below).
187
+ *
188
+ * `1.2` and `1` are NOT accepted, though SchemaStore's own corpus uses such
189
+ * labels: requiring all three components makes a label unambiguous to split
190
+ * back out of `<name>-<version>.json` or its URL, which is what consumers
191
+ * do with it. The file-name convention around the label stays SchemaStore's.
192
+ *
193
+ * The label round-trips verbatim into file names and catalog `versions`
194
+ * keys; ordering parses it directly (see {@link SchemaVersioning.Order}).
189
195
  *
190
196
  * @public
191
197
  */
@@ -206,23 +212,20 @@ interface CatalogUrls {
206
212
  /** The catalog `url` — the unversioned file, or the latest versioned file. */
207
213
  readonly url: string;
208
214
  /**
209
- * The versioned catalog's `versions` map (label → url). Labels are
210
- * inserted in ascending version order, but JavaScript object semantics
211
- * cap what insertion can promise: integer-like labels (bare majors such
212
- * as `2`) always enumerate first, numerically, ahead of every dotted
213
- * label — see {@link SchemaVersioning.catalogUrls} for the exact
214
- * enumeration contract. Read version ordering from the labels, never
215
- * from key position.
215
+ * The versioned catalog's `versions` map (label → url), inserted — and,
216
+ * since a three-component label can never be integer-like, enumerated
217
+ * and serialized in ascending version order.
216
218
  */
217
219
  readonly versions?: Readonly<Record<string, string>>;
218
220
  }
219
221
  /**
220
222
  * Both SchemaStore catalog modes as pure derivations: unversioned (a plain
221
- * `name.json` file, `url` only) and versioned (`name-<version>.json` files,
222
- * a `versions` map, and `url` pointing at the latest version).
223
+ * `name.json` file, `url` only) and versioned (`name-<version>.json` files
224
+ * — SchemaStore's own suffix convention — a `versions` map, and `url`
225
+ * pointing at the latest version).
223
226
  *
224
- * Version ordering follows SemVer precedence over labels padded to three
225
- * components, so `1.10` sorts above `1.9` and `2-beta` below `2`.
227
+ * Version labels are full three-component SemVer, so ordering is plain
228
+ * SemVer precedence: `1.10.0` above `1.9.0`, `2.0.0-beta` below `2.0.0`.
226
229
  *
227
230
  * @public
228
231
  */
@@ -240,9 +243,9 @@ declare class SchemaVersioning {
240
243
  */
241
244
  static readonly parse: (input: string) => Effect.Effect<string & import("effect/Brand").Brand<"SchemaVersion">, InvalidSchemaVersionError, never>;
242
245
  /**
243
- * `Order` instance over version labels: SemVer precedence after padding
244
- * missing components with zeros. `1.10` sorts above `1.9` (numeric, not
245
- * lexical) and `2-beta` below `2` (prerelease precedence).
246
+ * `Order` instance over version labels: plain SemVer precedence.
247
+ * `1.10.0` sorts above `1.9.0` (numeric, not lexical) and `2.0.0-beta`
248
+ * below `2.0.0` (prerelease precedence).
246
249
  */
247
250
  static readonly Order: Order.Order<SchemaVersion>;
248
251
  /**
@@ -273,16 +276,13 @@ declare class SchemaVersioning {
273
276
  * a contradiction (versioned mode with no versions) and throws — pass
274
277
  * `undefined` for the unversioned mode.
275
278
  *
276
- * Labels are inserted in ascending {@link SchemaVersioning.Order}, but
277
- * the SchemaStore catalog format requires `versions` to be a JSON
278
- * *object*, and JavaScript enumerates array-index-like keys first: a
279
- * bare-major label (`"2"`) always enumerates and therefore
280
- * serializes before every dotted label, regardless of insertion. The
281
- * resulting enumeration order is: bare-major labels ascending
282
- * numerically, then all other labels ascending. For label sets with no
283
- * bare majors this is fully ascending; mixed sets interleave, so
284
- * consumers must derive ordering from the labels themselves (as
285
- * {@link SchemaVersioning.latest} does), never from key position.
279
+ * Labels are inserted in ascending {@link SchemaVersioning.Order} and
280
+ * stay that way on serialization. Requiring three components is what
281
+ * buys this: JavaScript enumerates array-index-like keys first, so the
282
+ * old grammar's bare-major label (`"2"`) jumped ahead of every dotted
283
+ * one regardless of insertion order. No SemVer label is integer-like,
284
+ * so that hazard is gone. Deriving ordering from the labels themselves
285
+ * (as {@link SchemaVersioning.latest} does) is still the robust read.
286
286
  */
287
287
  static catalogUrls(options: {
288
288
  readonly baseUrl: string;
@@ -360,6 +360,84 @@ declare class CatalogEntry extends CatalogEntry_base {
360
360
  static lintFileMatch(patterns: ReadonlyArray<string>): ReadonlyArray<CatalogLintFinding>;
361
361
  }
362
362
  //#endregion
363
+ //#region src/DocumentDiff.d.ts
364
+ /**
365
+ * What differs between two schema documents:
366
+ *
367
+ * - `"none"` — the documents are equal in content (key ORDER is not a
368
+ * difference; array order is).
369
+ * - `"annotations"` — only documentation keywords differ (see
370
+ * {@link DocumentDiff.isAnnotationKeyword}). The validation contract is
371
+ * identical, so the new document replaces the old transparently for
372
+ * every consumer — republish without cutting a version.
373
+ * - `"contract"` — at least one assertion keyword differs, so a document
374
+ * valid against one is not necessarily valid against the other. Under
375
+ * {@link SchemaVersioning}, this is the change that warrants a new
376
+ * version rather than an in-place replacement.
377
+ *
378
+ * @public
379
+ */
380
+ type SchemaChange = "none" | "annotations" | "contract";
381
+ /**
382
+ * Classifies the difference between two emitted schema documents by
383
+ * meaning: identical, documentation-only, or a change to the validation
384
+ * contract.
385
+ *
386
+ * The walk is keyword-position aware in exactly the way {@link DocumentLint}'s
387
+ * is — a property NAMED `description` inside `properties` is data, not an
388
+ * annotation — and object key order is never a difference, so a document
389
+ * reformatted (or key-sorted) by another tool still classifies as `"none"`.
390
+ *
391
+ * Total: hostile nesting past the package's depth cap stops the structural
392
+ * walk and degrades to a whole-subtree comparison reported as `"contract"`
393
+ * when unequal, never a throw.
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * import { DocumentDiff } from "@effected/schemastore";
398
+ *
399
+ * const before = { type: "object", properties: { name: { type: "string", description: "A" } } };
400
+ * const after = { type: "object", properties: { name: { type: "string", description: "B" } } };
401
+ *
402
+ * console.log(DocumentDiff.classify(before, after));
403
+ * // => "annotations" (republish transparently; no new version needed)
404
+ *
405
+ * console.log(DocumentDiff.classify(before, { ...before, required: ["name"] }));
406
+ * // => "contract" (documents valid before may be invalid now)
407
+ * ```
408
+ *
409
+ * @public
410
+ */
411
+ declare class DocumentDiff {
412
+ private constructor();
413
+ /**
414
+ * Classify the difference between two emitted document values (the
415
+ * `toJson()` publication shape, or anything parsed from a written
416
+ * schema file). Both sides are plain JSON values, so this compares an
417
+ * on-disk document against a freshly built one without either being a
418
+ * {@link StoreDocument}.
419
+ */
420
+ static classify(existing: unknown, next: unknown): SchemaChange;
421
+ /**
422
+ * Whether a classification means "nothing changed" — `true` only for
423
+ * `"none"`.
424
+ *
425
+ * Exists so the clean case is not a string literal every consumer
426
+ * spells for itself. `"created"` is deliberately NOT clean: a file that
427
+ * did not exist is a change.
428
+ */
429
+ static isClean(change: SchemaChange | "created"): boolean;
430
+ /**
431
+ * Whether `key`, standing at a schema position, is a documentation
432
+ * keyword: `title`, `description`, `$comment`, or any declared
433
+ * non-standard language-server family ({@link KeywordFamilies}).
434
+ *
435
+ * `default`, `examples`, `readOnly` and `writeOnly` are deliberately
436
+ * NOT documentation — consumers act on them.
437
+ */
438
+ static isAnnotationKeyword(key: string): boolean;
439
+ }
440
+ //#endregion
363
441
  //#region src/StoreDocument.d.ts
364
442
  /**
365
443
  * The Draft-07 meta-schema URL SchemaStore documents declare as `$schema`.
@@ -442,6 +520,33 @@ declare const StoreDocument_base: Schema.Class<StoreDocument, Schema.Struct<{
442
520
  * @public
443
521
  */
444
522
  declare class StoreDocument extends StoreDocument_base {
523
+ /**
524
+ * Builds a Draft-07 document from its parts, filling `$schema` with
525
+ * {@link DRAFT_07_META_SCHEMA}.
526
+ *
527
+ * `fromSchema` sets the meta-schema unconditionally; hand-building a
528
+ * value with `make` otherwise means importing the constant just to
529
+ * repeat what the package already knows. `$schema` stays a real field
530
+ * rather than a defaulted one — it declares the document's dialect, and
531
+ * a document that does not say which dialect it is written in is worse
532
+ * than one that repeats itself — so this is a constructor, not a
533
+ * default.
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * import { StoreDocument } from "@effected/schemastore";
538
+ *
539
+ * const document = StoreDocument.draft07({
540
+ * $id: "https://example.com/config.schema.json",
541
+ * root: { type: "object" },
542
+ * });
543
+ * ```
544
+ */
545
+ static draft07(options: {
546
+ readonly $id: string;
547
+ readonly root: Record<string, unknown>;
548
+ readonly defs?: Record<string, unknown>;
549
+ }): StoreDocument;
445
550
  /**
446
551
  * Builds the document for an Effect Schema source. Pure and
447
552
  * synchronous — the primitive form; {@link StoreDocument.fromSchema} is
@@ -602,14 +707,89 @@ declare class SchemaFileWriteError extends SchemaFileWriteError_base {
602
707
  get message(): string;
603
708
  }
604
709
  /**
605
- * What {@link SchemaFileShape.write} did: `"written"` when the file's
606
- * content changed (or the file was created), `"unchanged"` when the
607
- * on-disk bytes already matched reported as a value so the caller
608
- * decides what to surface, never a log.
710
+ * What {@link SchemaFileShape.write} did to the filesystem: `"written"`
711
+ * when it wrote, `"unchanged"` when it left the file alone — reported as a
712
+ * value so the caller decides what to surface, never a log.
609
713
  *
610
714
  * @public
611
715
  */
612
716
  type WriteOutcome = "written" | "unchanged";
717
+ /**
718
+ * How the document being written relates to what was already on disk:
719
+ * {@link SchemaChange} plus `"created"` for a file that did not exist, so
720
+ * there was nothing to compare against.
721
+ *
722
+ * @public
723
+ */
724
+ type WriteChange = SchemaChange | "created";
725
+ /**
726
+ * The result of {@link SchemaFileShape.write}: what happened to the file,
727
+ * and what the difference MEANT.
728
+ *
729
+ * The two are independent on purpose. `change` answers the versioning
730
+ * question — `"annotations"` means only prose and editor affordances moved,
731
+ * so the document replaces its predecessor transparently and needs no new
732
+ * {@link SchemaVersioning} version, while `"contract"` means an assertion
733
+ * keyword moved and a consumer's document valid yesterday may be invalid
734
+ * today. `outcome` answers only whether bytes were written, which under
735
+ * `compare: "bytes"` can be `"written"` even when `change` is `"none"`.
736
+ *
737
+ * @public
738
+ */
739
+ interface WriteResult {
740
+ /**
741
+ * Whether the file was written. **This is the authoritative answer to
742
+ * "did the filesystem get touched"** — always, under either `compare`
743
+ * mode. Do not infer it from `change`: under `compare: "bytes"` a
744
+ * `change` of `"none"` still writes.
745
+ */
746
+ readonly outcome: WriteOutcome;
747
+ /** What differs between the previous content and the new document. */
748
+ readonly change: WriteChange;
749
+ }
750
+ /**
751
+ * The result of {@link SchemaFileShape.check}: the same two answers
752
+ * {@link WriteResult} carries, for a call that touched nothing.
753
+ *
754
+ * `wouldWrite` is `outcome`'s counterpart — it honors `compare`, so it
755
+ * answers "would `write` do anything with these same options", which
756
+ * `change` alone cannot under `compare: "bytes"`.
757
+ *
758
+ * @public
759
+ */
760
+ interface CheckResult {
761
+ /** Whether a `write` with the same options would touch the file. */
762
+ readonly wouldWrite: boolean;
763
+ /** What differs between the on-disk content and the new document. */
764
+ readonly change: WriteChange;
765
+ }
766
+ /**
767
+ * Options for {@link SchemaFileShape.write} and
768
+ * {@link SchemaFileShape.check}: the {@link CanonicalJsonOptions} the
769
+ * document serializes under, plus how `write` decides whether to touch the
770
+ * file.
771
+ *
772
+ * @public
773
+ */
774
+ interface SchemaWriteOptions extends CanonicalJsonOptions {
775
+ /**
776
+ * How `write` decides the file needs rewriting:
777
+ *
778
+ * - `"value"` (the default) — compare the parsed content. Immune to any
779
+ * other tool that reformats the file, which is the common case: a repo
780
+ * whose pre-commit hook runs Biome or Prettier over `*.json` would
781
+ * otherwise see every run rewrite the file forever, because the
782
+ * formatter's bytes never match `CanonicalJson`'s.
783
+ * - `"bytes"` — compare the exact text, so the file on disk is only ever
784
+ * `CanonicalJson`'s own output. Choose this when the emitted bytes are
785
+ * themselves the artifact and no other tool is allowed to touch them.
786
+ *
787
+ * `change` in the {@link WriteResult} is classified by content either
788
+ * way; this option decides only whether a byte-level difference is
789
+ * enough to rewrite.
790
+ */
791
+ readonly compare?: "bytes" | "value";
792
+ }
613
793
  /**
614
794
  * The shape of the {@link SchemaFile} service — the value produced by
615
795
  * {@link SchemaFile.make} and carried by its layer.
@@ -627,13 +807,26 @@ interface SchemaFileShape {
627
807
  /**
628
808
  * Serialize a document to canonical JSON and write it **only if the
629
809
  * on-disk content differs** (a missing file counts as different),
630
- * creating parent directories as needed. Answers the
631
- * {@link WriteOutcome} as a value. Fails with a `CanonicalJsonError`
632
- * (the document does not serialize), `SchemaFileReadError` (the
633
- * existing content could not be read for comparison) or
634
- * `SchemaFileWriteError` (the filesystem write failed).
810
+ * creating parent directories as needed. Answers a {@link WriteResult}
811
+ * as a value: what happened to the file, and what the difference meant.
812
+ * Fails with a `CanonicalJsonError` (the document does not serialize),
813
+ * `SchemaFileReadError` (the existing content could not be read for
814
+ * comparison) or `SchemaFileWriteError` (the filesystem write failed).
815
+ */
816
+ readonly write: (path: string, document: StoreDocument, options?: SchemaWriteOptions) => Effect.Effect<WriteResult, CanonicalJsonError | SchemaFileReadError | SchemaFileWriteError>;
817
+ /**
818
+ * The same comparison {@link SchemaFileShape.write} makes, **without
819
+ * touching the filesystem** — the drift-check half of the pair, for a
820
+ * CI job that must assert a committed schema is current rather than
821
+ * regenerate it.
822
+ *
823
+ * Answers both questions the writer answers: `change` classifies the
824
+ * content (immune to a formatter having reflowed the committed file),
825
+ * and `wouldWrite` honors `compare`, so it agrees with `write` under
826
+ * either mode. Checking drift is `change`; predicting the writer is
827
+ * `wouldWrite`.
635
828
  */
636
- readonly write: (path: string, document: StoreDocument, options?: CanonicalJsonOptions) => Effect.Effect<WriteOutcome, CanonicalJsonError | SchemaFileReadError | SchemaFileWriteError>;
829
+ readonly check: (path: string, document: StoreDocument, options?: SchemaWriteOptions) => Effect.Effect<CheckResult, CanonicalJsonError | SchemaFileReadError>;
637
830
  }
638
831
  declare const SchemaFile_base: Context.ServiceClass<SchemaFile, "@effected/schemastore/SchemaFile", SchemaFileShape>;
639
832
  /**
@@ -642,10 +835,14 @@ declare const SchemaFile_base: Context.ServiceClass<SchemaFile, "@effected/schem
642
835
  * services; provide `@effect/platform-node`'s `NodeFileSystem` / `NodePath`
643
836
  * (or a bun equivalent) at the application boundary.
644
837
  *
645
- * `write` is write-if-changed: serialization goes through the owned
646
- * `CanonicalJson` (equal documents serialize to equal bytes), so an
647
- * unchanged document never touches the file a generator committed to a
648
- * repo does not churn mtimes, and its CI drift check is `read` + compare.
838
+ * `write` is write-if-changed, and by default compares **content**: an
839
+ * unchanged document never touches the file even if another tool has
840
+ * reformatted it, so a generator committed to a repo whose pre-commit hook
841
+ * formats JSON does not churn on every run. It also reports what the
842
+ * difference meant — `"annotations"` (prose only, replaces its predecessor
843
+ * transparently) versus `"contract"` (an assertion moved, so a new
844
+ * `SchemaVersioning` version is warranted). `check` makes the same
845
+ * comparison without writing, which is what a CI drift job wants.
649
846
  *
650
847
  * @example
651
848
  * ```ts
@@ -694,9 +891,17 @@ interface SchemaTarget {
694
891
  readonly schema: Schema.Constraint;
695
892
  /** The canonical `$id` URL the generated document declares. */
696
893
  readonly $id: string;
697
- /** The catalog/file base name (`name.json` / `name-<version>.json`). */
698
- readonly name: string;
699
- /** The destination path the document is written to (phase-2 `SchemaFile`). */
894
+ /**
895
+ * The catalog/file base name (`name.json` / `name-<version>.json`).
896
+ *
897
+ * Only the catalog path consumes it — a target that merely emits a file
898
+ * to `path` needs no name, and inventing one to
899
+ * satisfy the constructor duplicates the basename with no invariant
900
+ * tying the two together. Required whenever `version` is present, since
901
+ * versioned catalog naming is defined in terms of it.
902
+ */
903
+ readonly name?: string;
904
+ /** The destination path the document is written to (`SchemaFile`). */
700
905
  readonly path: string;
701
906
  /** The version label, for versioned catalog mode. Omit for unversioned. */
702
907
  readonly version?: SchemaVersion;
@@ -709,15 +914,27 @@ interface SchemaTarget {
709
914
  declare class SchemaTarget {
710
915
  private constructor();
711
916
  /**
712
- * Builds a target. `$id`, `name` and `path` must be non-empty — an
713
- * empty identity is a wiring mistake and throws.
917
+ * Builds an unversioned target. `name` is optional only catalog
918
+ * naming reads it, so a target that merely emits a file needs none.
919
+ */
920
+ static make(options: {
921
+ readonly schema: Schema.Constraint;
922
+ readonly $id: string;
923
+ readonly name?: string;
924
+ readonly path: string;
925
+ }): SchemaTarget;
926
+ /**
927
+ * Builds a versioned target. `name` is **required** here: versioned
928
+ * catalog naming is `name-<version>.json`, so a version without a name
929
+ * cannot be resolved — the overload pair makes that unrepresentable
930
+ * rather than a runtime throw.
714
931
  */
715
932
  static make(options: {
716
933
  readonly schema: Schema.Constraint;
717
934
  readonly $id: string;
718
935
  readonly name: string;
719
936
  readonly path: string;
720
- readonly version?: SchemaVersion;
937
+ readonly version: SchemaVersion;
721
938
  }): SchemaTarget;
722
939
  }
723
940
  //#endregion
@@ -788,17 +1005,22 @@ interface SchemaValidatorShape {
788
1005
  }
789
1006
  declare const SchemaValidator_base: Context.ServiceClass<SchemaValidator, "@effected/schemastore/SchemaValidator", SchemaValidatorShape>;
790
1007
  /**
791
- * Contract for real-engine JSON Schema document validation the seam
792
- * through which SchemaStore's own gate (ajv strict mode) reaches this
793
- * package without ajv ever entering its dependency graph.
1008
+ * Real-engine JSON Schema document validation, closed by default over ajv —
1009
+ * the engine SchemaStore's own gate is defined in terms of.
1010
+ *
1011
+ * {@link SchemaValidator.layer} is the shipped implementation: provide it and
1012
+ * validation works, with no adapter to write. The service stays an interface
1013
+ * so a test can swap it ({@link SchemaValidator.layerTest}) or skip it
1014
+ * ({@link SchemaValidator.noop}), and so a consumer standardized on a
1015
+ * different engine can substitute one — but writing an adapter is no longer
1016
+ * the price of admission. `DocumentLint` remains the owned, engine-free
1017
+ * structural half of the validation story, and answers questions ajv does
1018
+ * not (SchemaStore's own hygiene conventions).
794
1019
  *
795
- * This is a contract-only service: {@link SchemaValidator.noop} is the sole
796
- * implementation this package ships, and it validates nothing. The consumer
797
- * closes the seam with a real engine at the application edge e.g. an ajv
798
- * adapter whose `validate` compiles the document with
799
- * `new Ajv({ strict: true, allErrors: true })` and answers compile failures
800
- * as findings. `DocumentLint` remains the owned, always-available
801
- * structural half of the validation story.
1020
+ * The shipped layer registers every declared {@link KeywordFamilies} keyword
1021
+ * present in the document before compiling, so ajv strict mode does not
1022
+ * reject the language-server families `DocumentLint` deliberately allows
1023
+ * one predicate governs both verdicts.
802
1024
  *
803
1025
  * @example
804
1026
  * ```ts
@@ -810,7 +1032,7 @@ declare const SchemaValidator_base: Context.ServiceClass<SchemaValidator, "@effe
810
1032
  * return yield* validator.validate({ type: "object" });
811
1033
  * });
812
1034
  *
813
- * Effect.runPromise(Effect.provide(program, SchemaValidator.noop));
1035
+ * Effect.runPromise(Effect.provide(program, SchemaValidator.layer));
814
1036
  * // => []
815
1037
  * ```
816
1038
  *
@@ -818,9 +1040,24 @@ declare const SchemaValidator_base: Context.ServiceClass<SchemaValidator, "@effe
818
1040
  */
819
1041
  declare class SchemaValidator extends SchemaValidator_base {
820
1042
  /**
821
- * No-op default: `validate` always succeeds with no findings, never
822
- * consulting an engine. A pure `Layer.succeed`, bound to a const so the
823
- * layer memoizes by reference.
1043
+ * The shipped ajv implementation the default a consumer provides.
1044
+ *
1045
+ * `validate` checks the document against the Draft-07 meta-schema and
1046
+ * then compiles it, reporting BOTH as {@link ValidationFinding} values:
1047
+ * meta-schema failures keep ajv's structured `instancePath` and
1048
+ * `keyword`, while a strict-mode rejection (which ajv raises by throwing
1049
+ * at compile time) becomes a root-pathed finding. The error channel
1050
+ * stays reserved for the engine failing as a mechanism.
1051
+ *
1052
+ * `strict` defaults to `true` — SchemaStore's gate. Each call builds its
1053
+ * own ajv instance, so documents sharing an `$id` never collide.
1054
+ */
1055
+ static readonly layer: Layer.Layer<SchemaValidator>;
1056
+ /**
1057
+ * No-op: `validate` always succeeds with no findings, never consulting an
1058
+ * engine. A pure `Layer.succeed`, bound to a const so the layer memoizes
1059
+ * by reference. Use it to switch validation off deliberately — for the
1060
+ * real engine, provide {@link SchemaValidator.layer}.
824
1061
  */
825
1062
  static readonly noop: Layer.Layer<SchemaValidator>;
826
1063
  /**
@@ -835,5 +1072,205 @@ declare class SchemaValidator extends SchemaValidator_base {
835
1072
  static readonly layerTest: (overrides?: Partial<SchemaValidatorShape>) => Layer.Layer<SchemaValidator>;
836
1073
  }
837
1074
  //#endregion
838
- export { AnnotationCarriers, CanonicalJson, type CanonicalJsonError, type CanonicalJsonOptions, CarrierDepthExceededError, CatalogEntry, CatalogLintFinding, type CatalogUrls, DRAFT_07_META_SCHEMA, DocumentLint, DocumentLintFinding, InvalidSchemaVersionError, JsonDepthExceededError, KeywordFamilies, NonJsonValueError, SchemaConversionError, SchemaFile, SchemaFileNotFoundError, SchemaFileReadError, type SchemaFileShape, SchemaFileWriteError, SchemaTarget, SchemaValidator, SchemaValidatorError, type SchemaValidatorOptions, type SchemaValidatorShape, SchemaVersion, SchemaVersioning, StoreDocument, type StoreDocumentOptions, ValidationFinding, type WriteOutcome };
1075
+ //#region src/SchemaPipeline.d.ts
1076
+ declare const PipelineFinding_base: Schema.Class<PipelineFinding, Schema.Struct<{
1077
+ /** Which gate produced it. */
1078
+ readonly source: Schema.Literals<readonly ["lint", "validator"]>;
1079
+ /** `"warning"` blocks under the default policy; `"advisory"` does not. */
1080
+ readonly severity: Schema.Literals<readonly ["warning", "advisory"]>;
1081
+ /** The lint check's name, or the engine keyword, when one is named. */
1082
+ readonly check: Schema.optionalKey<Schema.String>;
1083
+ /** JSON pointer into the flat document (`""` is the root). */
1084
+ readonly path: Schema.String;
1085
+ /** Human-readable explanation. */
1086
+ readonly message: Schema.String;
1087
+ }>, {}>;
1088
+ /**
1089
+ * One problem found while emitting a target, from either gate, normalized
1090
+ * so a single policy predicate can judge both.
1091
+ *
1092
+ * `DocumentLint` findings keep their own severity; engine findings are
1093
+ * `"warning"` — a document the engine rejects is not advisory.
1094
+ *
1095
+ * @public
1096
+ */
1097
+ declare class PipelineFinding extends PipelineFinding_base {
1098
+ /**
1099
+ * What to call this finding when rendering it: the check name when the
1100
+ * gate named one, the gate itself otherwise.
1101
+ *
1102
+ * Engine findings do not always carry a keyword, so without this every
1103
+ * consumer that logs findings writes the same `finding.check ?? …`
1104
+ * fallback.
1105
+ */
1106
+ get label(): string;
1107
+ }
1108
+ declare const SchemaGateError_base: Schema.Class<SchemaGateError, Schema.TaggedStruct<"SchemaGateError", {
1109
+ /** The `$id` of the target that failed the gate. */
1110
+ readonly $id: Schema.String;
1111
+ /** Every finding that blocked, in discovery order. */
1112
+ readonly findings: Schema.$Array<typeof PipelineFinding>;
1113
+ }>, import("effect/Cause").YieldableError>;
1114
+ /**
1115
+ * Indicates that at least one target's findings blocked under the active
1116
+ * gating policy. Carries every blocking finding, so a caller renders one
1117
+ * report instead of discovering problems one run at a time.
1118
+ *
1119
+ * @public
1120
+ */
1121
+ declare class SchemaGateError extends SchemaGateError_base {
1122
+ get message(): string;
1123
+ }
1124
+ /**
1125
+ * What the pipeline did with one target.
1126
+ *
1127
+ * @public
1128
+ */
1129
+ interface PipelineResult {
1130
+ /** The target's `$id`. */
1131
+ readonly $id: string;
1132
+ /** The path the document was written to (or compared against). */
1133
+ readonly path: string;
1134
+ /** Whether the file was written. Absent from {@link SchemaPipeline.check}'s results. */
1135
+ readonly outcome: WriteOutcome;
1136
+ /** What differs between the previous content and the new document. */
1137
+ readonly change: WriteChange;
1138
+ /**
1139
+ * Every finding, blocking or not — returned as a value, never logged.
1140
+ * The package does not choose your log wording; a run that reached a
1141
+ * result had no blocking findings under the active policy.
1142
+ */
1143
+ readonly findings: ReadonlyArray<PipelineFinding>;
1144
+ }
1145
+ /**
1146
+ * What {@link SchemaPipeline.check} found for one target — the same report
1147
+ * without the write.
1148
+ *
1149
+ * @public
1150
+ */
1151
+ interface PipelineCheckResult {
1152
+ /** The target's `$id`. */
1153
+ readonly $id: string;
1154
+ /** The path compared against. */
1155
+ readonly path: string;
1156
+ /** Whether a run would touch this file. */
1157
+ readonly wouldWrite: boolean;
1158
+ /**
1159
+ * Whether this target's findings would block a {@link SchemaPipeline.run}
1160
+ * under the active policy — so a document that could never be written is
1161
+ * never mistaken for clean drift.
1162
+ */
1163
+ readonly blocked: boolean;
1164
+ /** What differs between the on-disk content and the new document. */
1165
+ readonly change: WriteChange;
1166
+ /** Every finding, blocking or not. */
1167
+ readonly findings: ReadonlyArray<PipelineFinding>;
1168
+ }
1169
+ /**
1170
+ * Options for {@link SchemaPipeline.run} and {@link SchemaPipeline.check}.
1171
+ *
1172
+ * @public
1173
+ */
1174
+ interface SchemaPipelineOptions {
1175
+ /**
1176
+ * Which findings block. Defaults to
1177
+ * `(finding) => finding.severity === "warning"`.
1178
+ *
1179
+ * The default is a policy call, not a mechanism: `UnresolvedRef`,
1180
+ * `UnknownKeyword` and `DepthExceeded` each describe a document that is
1181
+ * broken for the editors it exists to serve, and `UnknownKeyword` is by
1182
+ * construction the ajv-strict rejection set — tolerating it means
1183
+ * shipping something the engine gate rejects. A consumer who disagrees
1184
+ * replaces this predicate rather than re-implementing the loop.
1185
+ *
1186
+ * **Which findings can actually reach you here.** A `SchemaTarget`
1187
+ * carries a `Schema`, so the pipeline's documents come from
1188
+ * `StoreDocument.fromSchema` — and the Draft-07 lowering drops every
1189
+ * keyword outside its copy-list, so an undeclared keyword never
1190
+ * survives to be linted. Through this entry point `UnknownKeyword` is
1191
+ * therefore effectively unreachable, and **the engine gate is what
1192
+ * blocks in practice**. The lint's warning checks earn their keep on
1193
+ * documents this pipeline did not build — a hand-assembled
1194
+ * `StoreDocument.draft07`, or one read back off disk — and on depth,
1195
+ * which a schema can genuinely exceed.
1196
+ */
1197
+ readonly blocking?: (finding: PipelineFinding) => boolean;
1198
+ /** Passed through to `SchemaValidator.validate`. */
1199
+ readonly validator?: SchemaValidatorOptions;
1200
+ /** Passed through to `SchemaFile.write` / `SchemaFile.check`. */
1201
+ readonly write?: SchemaWriteOptions;
1202
+ }
1203
+ /**
1204
+ * The emit pipeline over a target manifest: generate, lint, validate, gate,
1205
+ * write — the loop every consumer of this package was writing by hand.
1206
+ *
1207
+ * Requires `SchemaFile` and `SchemaValidator` in `R`; provide
1208
+ * `SchemaFile.layer` and `SchemaValidator.layer` (plus a platform
1209
+ * `FileSystem` / `Path`) at the edge. Findings come back as **values**, so
1210
+ * the package never chooses your log wording — but the gating decision,
1211
+ * which is the part that must not silently differ between consumers, has
1212
+ * one default and one override point.
1213
+ *
1214
+ * @example
1215
+ * ```ts
1216
+ * import { SchemaFile, SchemaPipeline, SchemaTarget, SchemaValidator } from "@effected/schemastore";
1217
+ * import { NodeServices } from "@effect/platform-node";
1218
+ * import { Effect, Layer, Schema } from "effect";
1219
+ *
1220
+ * const targets = [
1221
+ * SchemaTarget.make({
1222
+ * schema: Schema.Struct({ name: Schema.String }),
1223
+ * $id: "https://example.com/config.schema.json",
1224
+ * path: "schemas/config.schema.json",
1225
+ * }),
1226
+ * ];
1227
+ *
1228
+ * const program = SchemaPipeline.run(targets).pipe(
1229
+ * Effect.provide(Layer.mergeAll(SchemaFile.layer, SchemaValidator.layer)),
1230
+ * Effect.provide(NodeServices.layer),
1231
+ * );
1232
+ * ```
1233
+ *
1234
+ * @public
1235
+ */
1236
+ declare class SchemaPipeline {
1237
+ private constructor();
1238
+ /**
1239
+ * Run every target: build its document, gather both gates' findings,
1240
+ * fail with a {@link SchemaGateError} if any block, and write otherwise.
1241
+ *
1242
+ * Targets are processed in order and the run stops at the first gate
1243
+ * failure — a document that fails its gate is not written, and neither
1244
+ * are the targets after it.
1245
+ */
1246
+ static run(targets: ReadonlyArray<SchemaTarget>, options?: SchemaPipelineOptions): Effect.Effect<ReadonlyArray<PipelineResult>, SchemaGateError | SchemaConversionError | SchemaValidatorError | CanonicalJsonError | SchemaFileReadError | SchemaFileWriteError, SchemaFile | SchemaValidator>;
1247
+ /**
1248
+ * The same walk with **no writes** — the drift-check counterpart, for a
1249
+ * CI job asserting the committed schemas are current.
1250
+ *
1251
+ * Unlike {@link SchemaPipeline.run} this is **total over the targets**:
1252
+ * it never stops at a failing gate, and reports `blocked` per target
1253
+ * instead of failing. Reporting is the job here, and a repo with three
1254
+ * broken documents should learn that in one run rather than fixing them
1255
+ * one run at a time. A blocked target is still never mistaken for clean
1256
+ * drift — `blocked` says so explicitly.
1257
+ *
1258
+ * The error channel is left to the mechanisms that genuinely cannot
1259
+ * produce a report (generation, serialization, the engine, the read).
1260
+ */
1261
+ static check(targets: ReadonlyArray<SchemaTarget>, options?: SchemaPipelineOptions): Effect.Effect<ReadonlyArray<PipelineCheckResult>, SchemaConversionError | SchemaValidatorError | CanonicalJsonError | SchemaFileReadError, SchemaFile | SchemaValidator>;
1262
+ /**
1263
+ * {@link SchemaPipeline.run} for a single target, answering its one
1264
+ * result directly — so a caller with one target does not index into an
1265
+ * array and prove to the type system that element zero exists.
1266
+ */
1267
+ static runOne(target: SchemaTarget, options?: SchemaPipelineOptions): Effect.Effect<PipelineResult, SchemaGateError | SchemaConversionError | SchemaValidatorError | CanonicalJsonError | SchemaFileReadError | SchemaFileWriteError, SchemaFile | SchemaValidator>;
1268
+ /**
1269
+ * {@link SchemaPipeline.check} for a single target, answering its one
1270
+ * result directly.
1271
+ */
1272
+ static checkOne(target: SchemaTarget, options?: SchemaPipelineOptions): Effect.Effect<PipelineCheckResult, SchemaConversionError | SchemaValidatorError | CanonicalJsonError | SchemaFileReadError, SchemaFile | SchemaValidator>;
1273
+ }
1274
+ //#endregion
1275
+ export { AnnotationCarriers, CanonicalJson, type CanonicalJsonError, type CanonicalJsonOptions, CarrierDepthExceededError, CatalogEntry, CatalogLintFinding, type CatalogUrls, type CheckResult, DRAFT_07_META_SCHEMA, DocumentDiff, DocumentLint, DocumentLintFinding, InvalidSchemaVersionError, JsonDepthExceededError, KeywordFamilies, NonJsonValueError, type PipelineCheckResult, PipelineFinding, type PipelineResult, type SchemaChange, SchemaConversionError, SchemaFile, SchemaFileNotFoundError, SchemaFileReadError, type SchemaFileShape, SchemaFileWriteError, SchemaGateError, SchemaPipeline, type SchemaPipelineOptions, SchemaTarget, SchemaValidator, SchemaValidatorError, type SchemaValidatorOptions, type SchemaValidatorShape, SchemaVersion, SchemaVersioning, type SchemaWriteOptions, StoreDocument, type StoreDocumentOptions, ValidationFinding, type WriteChange, type WriteOutcome, type WriteResult };
839
1276
  //# sourceMappingURL=index.d.ts.map