@effected/schemastore 0.1.1 → 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/DocumentDiff.js +178 -0
- package/README.md +86 -38
- package/SchemaFile.js +53 -15
- package/SchemaPipeline.js +200 -0
- package/SchemaTarget.js +8 -8
- package/SchemaValidator.js +73 -14
- package/SchemaVersioning.js +34 -43
- package/StoreDocument.js +30 -0
- package/index.d.ts +501 -64
- package/index.js +4 -2
- package/package.json +4 -3
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
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
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)
|
|
210
|
-
*
|
|
211
|
-
*
|
|
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`
|
|
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
|
|
225
|
-
*
|
|
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
|
|
244
|
-
*
|
|
245
|
-
*
|
|
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}
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* bare-major label (`"2"`)
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
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"`
|
|
606
|
-
*
|
|
607
|
-
*
|
|
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
|
|
631
|
-
*
|
|
632
|
-
* (the document does not serialize),
|
|
633
|
-
* existing content could not be read for
|
|
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
|
|
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
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
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
|
-
/**
|
|
698
|
-
|
|
699
|
-
|
|
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
|
|
713
|
-
*
|
|
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
|
|
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
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
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
|
-
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
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
|
-
|
|
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
|