@effected/schemastore 0.10.0 → 0.12.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
@@ -264,6 +264,14 @@ interface StoreDocumentOptions {
264
264
  * (`onExcessProperty`, `generateDescriptions`,
265
265
  * `includeAnnotationKey`).
266
266
  *
267
+ * `onExcessProperty` defaults to `"error"` here — every object is
268
+ * emitted closed (`additionalProperties: false`) because a published
269
+ * document is a contract — where core's own default has been `"ignore"`
270
+ * (open) since rc.113. Pass `{ onExcessProperty: "ignore" }` to reopen
271
+ * one document's objects. Omitting `jsonSchema` altogether reproduces
272
+ * byte-for-byte what the CLI writes for a target that declares none — a
273
+ * consumer test may call `fromSchemaResult(schema, { $id })` and compare.
274
+ *
267
275
  * The declared non-standard keyword families ({@link KeywordFamilies})
268
276
  * are **always admitted**, regardless of what a supplied
269
277
  * `includeAnnotationKey` answers — annotate a schema node
@@ -650,6 +658,16 @@ export declare const SchemaVersion: Schema.brand<Schema.String, "SchemaVersion">
650
658
  * @public
651
659
  */
652
660
  export type SchemaVersion = typeof SchemaVersion.Type;
661
+ /**
662
+ * Where a versioned document sits relative to its base: `flat` is
663
+ * `<name>-<version>.json` (the only shape SchemaStore serves), `versioned`
664
+ * nests it as `<version>/<name>-<version>.json` — or, with `appendVersion`
665
+ * off, `<version>/<name>.json`, the directory alone carrying the label. An
666
+ * unversioned document is `<name>.json` under either.
667
+ *
668
+ * @public
669
+ */
670
+ type SchemaLayout = "flat" | "versioned";
653
671
  /**
654
672
  * The `url`/`versions` half of a catalog entry, as assembled by
655
673
  * {@link SchemaVersioning.catalogUrls}.
@@ -756,28 +774,51 @@ export declare class SchemaVersioning {
756
774
  * the label and the ceiling rather than `SemVer.make`'s bare schema failure.
757
775
  */
758
776
  static next(current: SchemaVersion, change: WriteChange): SchemaVersion;
777
+ /**
778
+ * Whether a name is a simple file base name — non-empty, no path
779
+ * separators, no whitespace — the rule every schema name is held to
780
+ * ({@link SchemaVersioning.fileName} throws on anything else; `defineConfig`
781
+ * rejects a schema key the same way). One predicate so the two cannot drift.
782
+ */
783
+ static isSimpleName(name: string): boolean;
759
784
  /**
760
785
  * Derives the schema file name for a catalog name: `name.json`
761
- * unversioned, `name-<version>.json` versioned.
786
+ * unversioned, `name-<version>.json` versioned under the `"flat"`
787
+ * layout (the default and the only shape SchemaStore serves), or
788
+ * `<version>/name-<version>.json` under `"versioned"` — `<version>/name.json`
789
+ * when `appendVersion` is `false`, which only the versioned layout can
790
+ * carry (under `"flat"` two versions would share one file name, so that
791
+ * combination throws).
762
792
  *
763
793
  * The name must be a simple file base name (no separators, no
764
794
  * whitespace); anything else is a wiring mistake and throws.
765
795
  */
766
- static fileName(name: string, version?: SchemaVersion): string;
796
+ static fileName(name: string, version?: SchemaVersion, layout?: SchemaLayout, appendVersion?: boolean): string;
767
797
  /**
768
798
  * The canonical URL a schema file is hosted at: `baseUrl` joined with
769
799
  * {@link SchemaVersioning.fileName}.
770
800
  */
771
- static schemaUrl(baseUrl: string, name: string, version?: SchemaVersion): string;
801
+ static schemaUrl(baseUrl: string, name: string, version?: SchemaVersion, layout?: SchemaLayout, appendVersion?: boolean): string;
772
802
  /**
773
803
  * Assembles the `url`/`versions` half of a catalog entry.
774
804
  *
775
805
  * Omitting `versions` selects the unversioned mode (`url` only,
776
806
  * pointing at the plain `name.json`). Providing them selects the
777
807
  * versioned mode: the `versions` map carries every label, and `url`
778
- * points at the latest version's file. An **empty** `versions` array is
808
+ * points at `current` (default: the newest label under
809
+ * {@link SchemaVersioning.Order}). An **empty** `versions` array is
779
810
  * a contradiction (versioned mode with no versions) and throws — pass
780
- * `undefined` for the unversioned mode.
811
+ * `undefined` for the unversioned mode. `current`, when given, must
812
+ * compare equal under {@link SchemaVersioning.Order} to a member of
813
+ * `versions` or this throws; `url` is built from that member's own
814
+ * spelling (the `versions` map's key), not from the `current` argument
815
+ * verbatim — so a differently-spelled equivalent (`"1.2"` matching a
816
+ * `"1.2.0"` member) still points `url` at the same file the map does.
817
+ *
818
+ * `layout` (default `"flat"`) and `appendVersion` (default `true`) are
819
+ * forwarded to every URL derivation, so `"versioned"` nests every map
820
+ * value and `url` under its own version directory, with or without the
821
+ * `-<version>` file suffix.
781
822
  *
782
823
  * Labels are inserted in ascending {@link SchemaVersioning.Order}; see
783
824
  * {@link CatalogUrls.versions} for why a bare-major key's serialized
@@ -787,6 +828,9 @@ export declare class SchemaVersioning {
787
828
  readonly baseUrl: string;
788
829
  readonly name: string;
789
830
  readonly versions?: ReadonlyArray<SchemaVersion>;
831
+ readonly layout?: SchemaLayout;
832
+ readonly appendVersion?: boolean;
833
+ readonly current?: SchemaVersion;
790
834
  }): CatalogUrls;
791
835
  }
792
836
  //#endregion
@@ -836,7 +880,10 @@ export declare class CatalogEntry extends CatalogEntry_base {
836
880
  * Assembles an entry from a catalog identity plus
837
881
  * {@link SchemaVersioning.catalogUrls}' inputs: pass `versions` for the
838
882
  * versioned mode (the `versions` map and latest-pointing `url` are
839
- * derived), omit it for the unversioned mode. Throws an `Error` naming
883
+ * derived), omit it for the unversioned mode. `layout` (default
884
+ * `"flat"`), `appendVersion` (default `true`) and `current` (default: the
885
+ * newest label) are forwarded to {@link SchemaVersioning.catalogUrls}
886
+ * verbatim. Throws an `Error` naming
840
887
  * both spellings when two labels compare equal under
841
888
  * {@link SchemaVersioning.Order} (`1.2` and `1.2.0`): each would be its
842
889
  * own key and URL for one document.
@@ -848,6 +895,9 @@ export declare class CatalogEntry extends CatalogEntry_base {
848
895
  readonly baseUrl: string;
849
896
  readonly fileBaseName?: string;
850
897
  readonly versions?: ReadonlyArray<SchemaVersion>;
898
+ readonly layout?: SchemaLayout;
899
+ readonly appendVersion?: boolean;
900
+ readonly current?: SchemaVersion;
851
901
  }): CatalogEntry;
852
902
  /**
853
903
  * The fileMatch hygiene lint over this entry's patterns — pure shape
@@ -975,6 +1025,135 @@ export declare class DriftPolicy {
975
1025
  }, policy: DriftTolerance): DriftVerdict;
976
1026
  }
977
1027
  //#endregion
1028
+ //#region src/HostedSchema.d.ts
1029
+ /** The host SchemaStore-hosted documents declare in `$id`. @public */
1030
+ export declare const SCHEMASTORE_ID_BASE = "https://json.schemastore.org";
1031
+ /** The host SchemaStore's `catalog.json` points `url` at. @public */
1032
+ export declare const SCHEMASTORE_CATALOG_BASE = "https://www.schemastore.org";
1033
+ /**
1034
+ * The identity every hosted document derives from: `versions` and `current`
1035
+ * as `defineConfig` accepts them, minus the host, which each constructor
1036
+ * supplies.
1037
+ *
1038
+ * @public
1039
+ */
1040
+ interface HostedSchemaVersionsInput {
1041
+ /** The schema's file base name — the key it takes in `defineConfig`'s `schemas`. */
1042
+ readonly name: string;
1043
+ /** Every version label the schema advertises; omit for an unversioned schema. */
1044
+ readonly versions?: ReadonlyArray<string>;
1045
+ /** Which of `versions` is current. Defaults to the newest under {@link SchemaVersioning.Order}. */
1046
+ readonly current?: string;
1047
+ /**
1048
+ * Whether a versioned file name carries the `-<version>` suffix
1049
+ * (`<version>/<name>-<version>.json`, SchemaStore's own convention, the
1050
+ * default) or the version directory alone names it
1051
+ * (`<version>/<name>.json`). `false` requires the `"versioned"` layout
1052
+ * and is rejected under `"flat"`, including SchemaStore hosting.
1053
+ */
1054
+ readonly appendVersion?: boolean;
1055
+ }
1056
+ /** Input to {@link HostedSchema.github}. @public */
1057
+ interface GitHubHostedSchemaInput extends HostedSchemaVersionsInput {
1058
+ /** The repository, as `owner/repo`. */
1059
+ readonly repo: string;
1060
+ /** The branch (or tag) the files are served from. Defaults to `"main"`. */
1061
+ readonly branch?: string;
1062
+ /** The directory under the repository root the files live in, e.g. `"schemas"`. Defaults to the root. */
1063
+ readonly path?: string;
1064
+ /** How a versioned file nests under `path`. Defaults to `"versioned"`. */
1065
+ readonly layout?: SchemaLayout;
1066
+ }
1067
+ /** Input to {@link HostedSchema.custom}. @public */
1068
+ interface CustomHostedSchemaInput extends HostedSchemaVersionsInput {
1069
+ /** The `https://` directory URL the files are served under; a trailing slash is trimmed. */
1070
+ readonly baseUrl: string | URL;
1071
+ /** How a versioned file nests under `baseUrl`. Defaults to `"versioned"`. */
1072
+ readonly layout?: SchemaLayout;
1073
+ }
1074
+ declare const HostedSchema_base: Schema.Class<HostedSchema, Schema.Struct<{
1075
+ readonly name: Schema.String;
1076
+ readonly baseUrl: Schema.String;
1077
+ readonly versions: Schema.optionalKey<Schema.$Array<Schema.String>>;
1078
+ readonly current: Schema.optionalKey<Schema.String>;
1079
+ readonly layout: Schema.optionalKey<Schema.Literals<readonly ["flat", "versioned"]>>;
1080
+ readonly appendVersion: Schema.optionalKey<Schema.Boolean>;
1081
+ }>, {}>;
1082
+ /**
1083
+ * Where a JSON Schema document is hosted and which version of it is current
1084
+ * — the one value an application derives its `$schema` URL from and hands
1085
+ * to `defineConfig`, so the URL the code emits and the `$id` the CLI writes
1086
+ * cannot disagree.
1087
+ *
1088
+ * @remarks
1089
+ * Build one with {@link HostedSchema.github},
1090
+ * {@link HostedSchema.schemastore} or
1091
+ * {@link HostedSchema.custom}; each validates the identity and
1092
+ * throws a plain `Error` naming the reason. The derivation is
1093
+ * {@link SchemaVersioning.schemaUrl}'s: `<base>/<name>.json` unversioned,
1094
+ * `<base>/<name>-<version>.json` under the `"flat"` layout and
1095
+ * `<base>/<version>/<name>-<version>.json` under `"versioned"` (or
1096
+ * `<base>/<version>/<name>.json` with `appendVersion: false`, when the
1097
+ * version directory alone should name the file). The raw fields are what
1098
+ * `defineConfig` accepts by hand (`baseUrl`, `versions`, `current`,
1099
+ * `layout`, `appendVersion`); the getters are the resolved identity.
1100
+ *
1101
+ * @example
1102
+ * ```ts
1103
+ * import { HostedSchema } from "@effected/schemastore";
1104
+ * import { Schema } from "effect";
1105
+ *
1106
+ * export const OutputSchema = HostedSchema.github({
1107
+ * repo: "savvy-web/silk-release-action",
1108
+ * path: "schemas",
1109
+ * name: "silk-release-action.output",
1110
+ * versions: ["5.2"],
1111
+ * });
1112
+ *
1113
+ * // In the application: every payload names the schema it was written against.
1114
+ * const Output = Schema.Struct({ $schema: Schema.Literal(OutputSchema.$id) });
1115
+ *
1116
+ * // In schemastore.config.ts: the same value, so nothing is re-derived.
1117
+ * // schemas: { [OutputSchema.name]: { schema: Output, hosted: OutputSchema } }
1118
+ * ```
1119
+ *
1120
+ * @public
1121
+ */
1122
+ export declare class HostedSchema extends HostedSchema_base {
1123
+ /** A schema served raw from a GitHub repository. */
1124
+ static github(input: GitHubHostedSchemaInput): HostedSchema;
1125
+ /** A schema published to SchemaStore: `$id` on {@link SCHEMASTORE_ID_BASE}, catalog URL on {@link SCHEMASTORE_CATALOG_BASE}, flat layout. */
1126
+ static schemastore(input: HostedSchemaVersionsInput): HostedSchema;
1127
+ /** A schema served from any `https://` directory. */
1128
+ static custom(input: CustomHostedSchemaInput): HostedSchema;
1129
+ private get resolved();
1130
+ /** The base `$id` is derived from: {@link SCHEMASTORE_ID_BASE} under SchemaStore, else `baseUrl`. */
1131
+ get idBase(): string;
1132
+ /** The base the catalog URL is derived from: {@link SCHEMASTORE_CATALOG_BASE} under SchemaStore, else `baseUrl`. */
1133
+ get catalogBase(): string;
1134
+ /** The effective layout: `"flat"` under SchemaStore, else `layout` defaulting to `"versioned"`. */
1135
+ get resolvedLayout(): SchemaLayout;
1136
+ /** Whether versioned file names carry the `-<version>` suffix: `appendVersion` defaulting to `true`. */
1137
+ get resolvedAppendVersion(): boolean;
1138
+ /** Every advertised version, parsed; empty for an unversioned schema. */
1139
+ get resolvedVersions(): ReadonlyArray<SchemaVersion>;
1140
+ /** The current version — `current`, else the newest of `versions`; `undefined` for an unversioned schema. */
1141
+ get resolvedCurrent(): SchemaVersion | undefined;
1142
+ /** The `$id` of the current document — what an application writes as `$schema`. */
1143
+ get $id(): string;
1144
+ /** The catalog URL of the current document; equals {@link HostedSchema.$id} except under SchemaStore. */
1145
+ get url(): string;
1146
+ /** The current document's file name relative to the output directory. */
1147
+ get fileName(): string;
1148
+ /** The `$id` of the document at `version` (or the unversioned document). */
1149
+ idFor(version?: string): string;
1150
+ /** The catalog URL of the document at `version` (or the unversioned document). */
1151
+ urlFor(version?: string): string;
1152
+ /** The file name of the document at `version` (or the unversioned document), relative to the output directory. */
1153
+ fileNameFor(version?: string): string;
1154
+ private parseLabel;
1155
+ }
1156
+ //#endregion
978
1157
  //#region src/KeywordFamilies.d.ts
979
1158
  /**
980
1159
  * The one owner of the declared non-standard keyword families, in two
@@ -1119,8 +1298,9 @@ export interface SchemaTarget {
1119
1298
  * document's generation contract self-describing, so
1120
1299
  * {@link SchemaPipeline} reproduces a document deterministically
1121
1300
  * regardless of core's own default — for example, an `onExcessProperty`
1122
- * setting of `error` restores closed objects after rc.113 flipped that
1123
- * default open. See {@link StoreDocumentOptions.jsonSchema} for the
1301
+ * setting of `"ignore"` reopens a document's objects, which
1302
+ * {@link StoreDocument.fromSchema} closes by default. See
1303
+ * {@link StoreDocumentOptions.jsonSchema} for that default and the
1124
1304
  * `includeAnnotationKey` gate this option is also subject to.
1125
1305
  */
1126
1306
  readonly jsonSchema?: Schema.ToJsonSchemaOptions;
@@ -1237,31 +1417,22 @@ interface SchemaValidatorShape {
1237
1417
  }
1238
1418
  declare const SchemaValidator_base: Context.ServiceClass<SchemaValidator, "@effected/schemastore/SchemaValidator", SchemaValidatorShape>;
1239
1419
  /**
1240
- * Real-engine JSON Schema document validation, closed by default over ajv —
1241
- * the engine SchemaStore's own gate is defined in terms of.
1242
- *
1243
- * {@link SchemaValidator.layer} is the shipped implementation: provide it and
1244
- * validation works, with no adapter to write. The service stays an interface
1245
- * so a test can swap it ({@link SchemaValidator.layerTest}) or skip it
1246
- * ({@link SchemaValidator.noop}), and so a consumer standardized on a
1247
- * different engine can substitute one — but writing an adapter is no longer
1248
- * the price of admission. `DocumentLint` remains the owned, engine-free
1249
- * structural half of the validation story, and answers questions ajv does
1250
- * not (SchemaStore's own hygiene conventions).
1251
- *
1252
- * The shipped layer registers every declared {@link KeywordFamilies} keyword
1253
- * present in the document before compiling, so ajv strict mode does not
1254
- * reject the language-server families `DocumentLint` deliberately allows —
1255
- * one predicate governs both verdicts. It also registers the standard
1256
- * ajv-formats vocabulary (`date-time`, `date`, `time`, `duration`, `uri`,
1257
- * `uri-reference`, `uri-template`, `url`, `email`, `hostname`, `ipv4`,
1258
- * `ipv6`, `regex`, `uuid`, `json-pointer`, `relative-json-pointer`, …), so a
1259
- * published document can say "this string is an ISO-8601 instant" with a
1260
- * `format` instead of falling back to a `pattern` plus a runtime filter;
1261
- * an UNKNOWN format string remains a strict-mode rejection. The plugin's
1262
- * `formatMaximum` / `formatMinimum` limit keywords are deliberately NOT
1263
- * registered — `DocumentLint` answers those as unknown keywords, and the
1264
- * two verdicts must not drift.
1420
+ * The JSON Schema document validation contract — the engine SchemaStore's
1421
+ * own gate is defined in terms of, as a service the pipeline requires in
1422
+ * `R` and never owns.
1423
+ *
1424
+ * This package ships the contract and its doubles only: skip validation
1425
+ * with {@link SchemaValidator.noop}, stub it with
1426
+ * {@link SchemaValidator.layerTest}. The one real implementation is
1427
+ * `@effected/schemastore-cli`'s `AjvValidator.layer` — ajv strict mode over
1428
+ * the declared `KeywordFamilies` and the standard `ajv-formats`
1429
+ * vocabulary — which the `schemastore` command composes for you and which
1430
+ * that package also exports for a program that drives `SchemaPipeline`
1431
+ * itself. Keeping the engine there keeps `ajv` out of every application that
1432
+ * imports this package at runtime (to read a `HostedSchema`, say) and out of
1433
+ * its bundle. `DocumentLint` is the owned, engine-free structural half of the
1434
+ * validation story, and answers questions ajv does not (SchemaStore's own
1435
+ * hygiene conventions).
1265
1436
  *
1266
1437
  * @example
1267
1438
  * ```ts
@@ -1273,38 +1444,20 @@ declare const SchemaValidator_base: Context.ServiceClass<SchemaValidator, "@effe
1273
1444
  * return yield* validator.validate({ type: "object" });
1274
1445
  * });
1275
1446
  *
1276
- * Effect.runPromise(Effect.provide(program, SchemaValidator.layer));
1447
+ * // Provide the engine at the edge — the CLI does this for you:
1448
+ * // Effect.provide(program, AjvValidator.layer) (from @effected/schemastore-cli)
1449
+ * Effect.runPromise(Effect.provide(program, SchemaValidator.noop));
1277
1450
  * // => []
1278
1451
  * ```
1279
1452
  *
1280
1453
  * @public
1281
1454
  */
1282
1455
  export declare class SchemaValidator extends SchemaValidator_base {
1283
- /**
1284
- * The shipped ajv implementation — the default a consumer provides.
1285
- *
1286
- * `validate` checks the document against the Draft-07 meta-schema and
1287
- * then compiles it, reporting BOTH as {@link ValidationFinding} values:
1288
- * meta-schema failures keep ajv's structured `instancePath` and
1289
- * `keyword`, while a rejection ajv raises by *throwing* becomes a
1290
- * root-pathed finding — both a strict-mode compile failure and a
1291
- * declared keyword whose NAME ajv's own grammar
1292
- * (`/^[a-z_$][a-z0-9_$:-]*$/i`) refuses, such as an `x-ai-*` key
1293
- * carrying a dot or a space. The error channel stays reserved for the
1294
- * engine failing as a mechanism.
1295
- *
1296
- * `strict` defaults to `true` — SchemaStore's gate. Each call builds its
1297
- * own ajv instance, so documents sharing an `$id` never collide. The
1298
- * standard ajv-formats vocabulary is registered on every instance, so
1299
- * `format: "date-time"` (and the rest of the standard set) compiles under
1300
- * strict mode instead of being rejected as an unknown format.
1301
- */
1302
- static readonly layer: Layer.Layer<SchemaValidator>;
1303
1456
  /**
1304
1457
  * No-op: `validate` always succeeds with no findings, never consulting an
1305
1458
  * engine. A pure `Layer.succeed`, bound to a const so the layer memoizes
1306
1459
  * by reference. Use it to switch validation off deliberately — for the
1307
- * real engine, provide {@link SchemaValidator.layer}.
1460
+ * real engine, provide `AjvValidator.layer` from `@effected/schemastore-cli`.
1308
1461
  */
1309
1462
  static readonly noop: Layer.Layer<SchemaValidator>;
1310
1463
  /**
@@ -1529,15 +1682,18 @@ interface SchemaPipelineOptions {
1529
1682
  * write — the loop every consumer of this package was writing by hand.
1530
1683
  *
1531
1684
  * Requires `SchemaFile` and `SchemaValidator` in `R`; provide
1532
- * `SchemaFile.layer` and `SchemaValidator.layer` (plus a platform
1533
- * `FileSystem` / `Path`) at the edge. Findings come back as **values**, so
1685
+ * `SchemaFile.layer` and an engine — `AjvValidator.layer` from
1686
+ * `@effected/schemastore-cli`, which is what the `schemastore` command
1687
+ * composes, or `SchemaValidator.noop` to skip validation — plus a platform
1688
+ * `FileSystem` / `Path` at the edge. Findings come back as **values**, so
1534
1689
  * the package never chooses your log wording — but the gating decision,
1535
1690
  * which is the part that must not silently differ between consumers, has
1536
1691
  * one default and one override point.
1537
1692
  *
1538
1693
  * @example
1539
1694
  * ```ts
1540
- * import { SchemaFile, SchemaPipeline, SchemaTarget, SchemaValidator } from "@effected/schemastore";
1695
+ * import { SchemaFile, SchemaPipeline, SchemaTarget } from "@effected/schemastore";
1696
+ * import { AjvValidator } from "@effected/schemastore-cli";
1541
1697
  * import { NodeServices } from "@effect/platform-node";
1542
1698
  * import { Effect, Layer, Schema } from "effect";
1543
1699
  *
@@ -1550,7 +1706,7 @@ interface SchemaPipelineOptions {
1550
1706
  * ];
1551
1707
  *
1552
1708
  * const program = SchemaPipeline.run(targets).pipe(
1553
- * Effect.provide(Layer.mergeAll(SchemaFile.layer, SchemaValidator.layer)),
1709
+ * Effect.provide(Layer.mergeAll(SchemaFile.layer, AjvValidator.layer)),
1554
1710
  * Effect.provide(NodeServices.layer),
1555
1711
  * );
1556
1712
  * ```
@@ -1631,41 +1787,155 @@ export declare class SchemaPipeline {
1631
1787
  //#region src/SchemastoreConfig.d.ts
1632
1788
  declare const ConfigBrand: unique symbol;
1633
1789
  /**
1634
- * One catalog entry a `schemastore.config.ts` declares: the SchemaStore
1635
- * `catalog.json` fields plus where to write the assembled entry. The entry's
1636
- * `versions` and `url` are derived by {@link defineConfig} from every
1637
- * versioned schema of the same `name`.
1790
+ * The SchemaStore `catalog.json` fields a schema entry declares, minus
1791
+ * `url`/`versions` — those are derived from the entry's `baseUrl`, `layout`
1792
+ * and `versions`/`current` by {@link defineConfig}, so they cannot disagree
1793
+ * with the schema's own identity.
1638
1794
  *
1639
1795
  * @public
1640
1796
  */
1641
- interface CatalogConfig {
1642
- readonly name: string;
1797
+ interface CatalogInput {
1798
+ /** The catalog description. */
1643
1799
  readonly description: string;
1800
+ /** Glob patterns editors match files against; must be non-empty. */
1644
1801
  readonly fileMatch: ReadonlyArray<string>;
1645
- readonly baseUrl: string;
1646
- /** Where to write the assembled entry; relative paths are resolved by the loader against the config file's directory. */
1647
- readonly path: string;
1648
1802
  }
1649
1803
  /**
1650
- * What a `schemastore.config.ts` hands to {@link defineConfig}: the schema
1651
- * targets, an optional catalog block and an optional partial drift block.
1804
+ * One schema a `schemastore.config.ts` declares, keyed by its own file base
1805
+ * name in {@link SchemastoreConfigInput.schemas}. `$id`, the write `path` and
1806
+ * every catalog URL are derived from `outputDir`, `baseUrl` (this entry's, or
1807
+ * the config's default) and `layout` — never spelled out by hand.
1808
+ *
1809
+ * @public
1810
+ */
1811
+ interface SchemaEntryInput {
1812
+ /** The Effect Schema source the document is generated from. */
1813
+ readonly schema: Schema.Constraint;
1814
+ /**
1815
+ * The schema's hosted identity, when the application already holds one
1816
+ * (to derive its `$schema` URL from). Supplies `baseUrl`, `versions`,
1817
+ * `current` and `layout`, which must then not be spelled here, and its
1818
+ * `name` must equal this entry's key.
1819
+ */
1820
+ readonly hosted?: HostedSchema;
1821
+ /**
1822
+ * Every version label this schema advertises. Omit for an unversioned
1823
+ * schema (`<name>.json`). An empty array is rejected — omit the field
1824
+ * instead. Two labels that compare equal under
1825
+ * {@link SchemaVersioning.Order} (`"1.2"` and `"1.2.0"`) are rejected as
1826
+ * one version spelled twice.
1827
+ */
1828
+ readonly versions?: ReadonlyArray<string>;
1829
+ /**
1830
+ * Which of `versions` is the one generated at this entry's `path`/`$id`;
1831
+ * the rest become {@link ResolvedSchema.frozen} files the CLI verifies
1832
+ * but does not regenerate. Defaults to the newest label under
1833
+ * {@link SchemaVersioning.Order}. Requires `versions`, and must name one
1834
+ * of them.
1835
+ */
1836
+ readonly current?: string;
1837
+ /**
1838
+ * Whether a consumer already depends on this document at this label —
1839
+ * forwarded to {@link (SchemaTarget:class).(make:1)}. Defaults to `false`.
1840
+ */
1841
+ readonly published?: boolean;
1842
+ /**
1843
+ * Where this schema is hosted: the literal `"schemastore"` (the only
1844
+ * value that expands `$id` to {@link SCHEMASTORE_ID_BASE} and the catalog
1845
+ * URL to {@link SCHEMASTORE_CATALOG_BASE}, and forces the `"flat"`
1846
+ * layout) or an `https://` URL used as ONE base for both `$id` and the
1847
+ * catalog URL. Falls back to {@link SchemastoreConfigInput.baseUrl} when
1848
+ * omitted; an entry with neither is rejected.
1849
+ */
1850
+ readonly baseUrl?: string;
1851
+ /**
1852
+ * How a versioned document's path/URL nests relative to its base — see
1853
+ * {@link SchemaLayout}. Defaults to `"versioned"` for a custom `baseUrl`,
1854
+ * and is rejected outright under `baseUrl: "schemastore"`, which serves
1855
+ * only the flat layout.
1856
+ */
1857
+ readonly layout?: SchemaLayout;
1858
+ /**
1859
+ * Whether a versioned file name carries the `-<version>` suffix
1860
+ * (`<version>/<name>-<version>.json`, the default) or the version
1861
+ * directory alone names it (`<version>/<name>.json`). `false` requires
1862
+ * the `"versioned"` layout. Owned by `hosted` when that is given.
1863
+ */
1864
+ readonly appendVersion?: boolean;
1865
+ /** Overrides {@link SchemastoreConfigInput.drift} for this schema. */
1866
+ readonly drift?: DriftTolerance;
1867
+ /**
1868
+ * The catalog entry to assemble for this schema. Required under
1869
+ * `baseUrl: "schemastore"` (every SchemaStore-hosted document is
1870
+ * cataloged); optional under a custom host.
1871
+ */
1872
+ readonly catalog?: CatalogInput;
1873
+ /** Forwarded to {@link (SchemaTarget:class).(make:1)}'s `jsonSchema`. */
1874
+ readonly jsonSchema?: Schema.ToJsonSchemaOptions;
1875
+ /** Forwarded to {@link (SchemaTarget:class).(make:1)}'s `rootAnnotations`. */
1876
+ readonly rootAnnotations?: Readonly<Record<string, unknown>>;
1877
+ }
1878
+ /**
1879
+ * What a `schemastore.config.ts` hands to {@link defineConfig}: a directory
1880
+ * every derived path is written under, top-level defaults for `baseUrl` and
1881
+ * `drift`, and the keyed set of schemas to derive.
1652
1882
  *
1653
1883
  * @public
1654
1884
  */
1655
1885
  interface SchemastoreConfigInput {
1656
- readonly schemas: ReadonlyArray<SchemaTarget>;
1657
- readonly catalog?: ReadonlyArray<CatalogConfig>;
1658
- readonly drift?: Partial<DriftOptions>;
1886
+ /** The directory every derived `path` is written under; a trailing slash is trimmed. */
1887
+ readonly outputDir: string;
1888
+ /** The default {@link SchemaEntryInput.baseUrl} for an entry that declares none. */
1889
+ readonly baseUrl?: string;
1890
+ /** The default {@link SchemaEntryInput.drift} for an entry that declares none. Defaults to `"semantic"`. */
1891
+ readonly drift?: DriftTolerance;
1892
+ /** What a build does when it finds drift. Defaults to `"error"`. */
1893
+ readonly onDrift?: OnDrift;
1894
+ /** Where the assembled catalog is written. Defaults to `<outputDir>/catalog.json`. */
1895
+ readonly catalogPath?: string;
1896
+ /**
1897
+ * The schemas to derive, keyed by file base name — the key IS the
1898
+ * `name` every derived path and URL is built from, so it must be a
1899
+ * simple file base name (no separators, no whitespace).
1900
+ */
1901
+ readonly schemas: Readonly<Record<string, SchemaEntryInput>>;
1659
1902
  }
1660
1903
  /**
1661
- * A validated catalog declaration paired with the `CatalogEntry` assembled
1662
- * from it.
1904
+ * One version of a schema that is advertised (via `versions`) but not the
1905
+ * one currently generated — a file the CLI verifies before any write (it
1906
+ * exists, and its own `$id` is the derived one — the rest of its content is
1907
+ * never compared), never regenerates.
1663
1908
  *
1664
1909
  * @public
1665
1910
  */
1666
- interface CatalogTarget {
1667
- readonly config: CatalogConfig;
1668
- readonly entry: CatalogEntry;
1911
+ interface FrozenVersion {
1912
+ /** The frozen version label. */
1913
+ readonly version: SchemaVersion;
1914
+ /** The path the frozen file lives at. */
1915
+ readonly path: string;
1916
+ /** The `$id` the frozen document must declare; differs from `url` only under `baseUrl: "schemastore"`. */
1917
+ readonly $id: string;
1918
+ /** The catalog URL the frozen file is hosted at. */
1919
+ readonly url: string;
1920
+ }
1921
+ /**
1922
+ * One `defineConfig` schema entry, resolved: the {@link (SchemaTarget:interface)} to
1923
+ * generate, its frozen predecessor versions, its effective drift tolerance,
1924
+ * and its assembled catalog entry, if any.
1925
+ *
1926
+ * @public
1927
+ */
1928
+ interface ResolvedSchema {
1929
+ /** The schema's key in {@link SchemastoreConfigInput.schemas}. */
1930
+ readonly name: string;
1931
+ /** The target to generate at the current version (or the sole, unversioned target). */
1932
+ readonly target: SchemaTarget;
1933
+ /** Every OTHER advertised version, as a frozen file to verify. */
1934
+ readonly frozen: ReadonlyArray<FrozenVersion>;
1935
+ /** This entry's effective drift tolerance, after falling back to the config default. */
1936
+ readonly drift: DriftTolerance;
1937
+ /** The assembled catalog entry, when {@link SchemaEntryInput.catalog} was given. */
1938
+ readonly catalog?: CatalogEntry;
1669
1939
  }
1670
1940
  /**
1671
1941
  * The validated, defaults-filled config {@link defineConfig} answers and the
@@ -1675,23 +1945,47 @@ interface CatalogTarget {
1675
1945
  */
1676
1946
  interface SchemastoreConfig {
1677
1947
  readonly [ConfigBrand]: true;
1678
- readonly schemas: ReadonlyArray<SchemaTarget>;
1679
- readonly catalog: ReadonlyArray<CatalogTarget>;
1680
- readonly drift: DriftOptions;
1948
+ /** The directory every derived `path` is written under, trailing slash trimmed. */
1949
+ readonly outputDir: string;
1950
+ /** What a build does when it finds drift. */
1951
+ readonly onDrift: OnDrift;
1952
+ /** Where the assembled catalog is written. */
1953
+ readonly catalogPath: string;
1954
+ /** Every schema, resolved. */
1955
+ readonly schemas: ReadonlyArray<ResolvedSchema>;
1681
1956
  }
1682
1957
  /**
1683
1958
  * Validate and assemble a `schemastore.config.ts` value.
1684
1959
  *
1685
1960
  * @remarks
1686
- * Pure: no IO, no Effect. Identity-with-validation over the input, filling
1687
- * drift defaults, deriving each catalog entry's `versions` from EVERY
1688
- * versioned schema of that name (published or not — the entry is what gets
1689
- * submitted to become published), and branding the result so a loader can
1690
- * recognise a config module's default export. Throws a plain `Error` on a
1691
- * bad input; the CLI wraps it into its typed config-load error. Rejects an
1692
- * output `path` declared twice across schemas and catalog entries, compared
1693
- * after a lexical normalisation (`./`, `..`, trailing `/`); the CLI's loader
1694
- * re-checks on the resolved absolute paths.
1961
+ * Pure: no IO, no Effect. `$id`, the write `path` and every catalog URL are
1962
+ * derived from ONE layout (`outputDir`, `baseUrl` and `layout`) so they
1963
+ * cannot disagree with each other. `versions` names every label a schema
1964
+ * advertises; `current` (default: the newest under
1965
+ * {@link SchemaVersioning.Order}) is the one generated at `target`, and every
1966
+ * other label becomes a {@link FrozenVersion} the CLI verifies but does not
1967
+ * regenerate. `baseUrl: "schemastore"` expands to
1968
+ * {@link SCHEMASTORE_ID_BASE} for `$id` and {@link SCHEMASTORE_CATALOG_BASE}
1969
+ * for the catalog URL, forcing the `"flat"` layout; any other `baseUrl` is
1970
+ * used as one base for both, defaulting to the `"versioned"` layout.
1971
+ *
1972
+ * Throws a plain `Error` (never a raw `TypeError`) on every malformed
1973
+ * input. The shape is decoded once per level with a `Schema.Struct`
1974
+ * (`errors: "all"`, so every issue on an entry is reported at once, and
1975
+ * `onExcessProperty: "error"`, so a typo'd key is named rather than
1976
+ * dropped); the message is `defineConfig: schema "<name>" ` followed by the
1977
+ * decode issues (`Expected string at ["baseUrl"]`). After a shape-clean
1978
+ * decode the cross-field rules run: the entry's identity — its
1979
+ * {@link HostedSchema}, or one built from `baseUrl`/`versions`/`current`/
1980
+ * `layout` and the config default — is validated by `HostedSchema` itself
1981
+ * (a `hosted` entry must be keyed by `hosted.name` and must not spell those
1982
+ * four fields beside it); a schema key must be a simple file base name; a
1983
+ * `catalog` is required under `baseUrl: "schemastore"`; an empty `schemas`
1984
+ * record is rejected; and an output path (a target, a frozen file, or the
1985
+ * catalog path) declared twice is rejected after a lexical normalisation
1986
+ * (`./`, `..`, trailing `/`) — the CLI's loader re-checks on the resolved
1987
+ * absolute paths. Branding the result lets a loader recognise a config
1988
+ * module's default export via {@link isSchemastoreConfig}.
1695
1989
  *
1696
1990
  * @public
1697
1991
  */
@@ -1704,5 +1998,5 @@ export declare const defineConfig: (input: SchemastoreConfigInput) => Schemastor
1704
1998
  */
1705
1999
  export declare const isSchemastoreConfig: (value: unknown) => value is SchemastoreConfig;
1706
2000
  //#endregion
1707
- export type { CanonicalJsonError, CanonicalJsonOptions, CatalogConfig, CatalogTarget, CatalogUrls, CheckResult, ContractChangePolicy, DriftOptions, DriftTolerance, DriftVerdict, OnDrift, PipelineCheckResult, PipelineResult, SchemaChange, SchemaFileShape, SchemaPipelineOptions, SchemaValidatorOptions, SchemaValidatorShape, SchemaWriteOptions, SchemastoreConfig, SchemastoreConfigInput, StoreDocumentOptions, WriteChange, WriteOutcome, WriteResult };
2001
+ export type { CanonicalJsonError, CanonicalJsonOptions, CatalogInput, CatalogUrls, CheckResult, ContractChangePolicy, CustomHostedSchemaInput, DriftOptions, DriftTolerance, DriftVerdict, FrozenVersion, GitHubHostedSchemaInput, HostedSchemaVersionsInput, OnDrift, PipelineCheckResult, PipelineResult, ResolvedSchema, SchemaChange, SchemaEntryInput, SchemaFileShape, SchemaLayout, SchemaPipelineOptions, SchemaValidatorOptions, SchemaValidatorShape, SchemaWriteOptions, SchemastoreConfig, SchemastoreConfigInput, StoreDocumentOptions, WriteChange, WriteOutcome, WriteResult };
1708
2002
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -5,11 +5,12 @@ import { KeywordFamilies } from "./KeywordFamilies.js";
5
5
  import { DocumentDiff } from "./DocumentDiff.js";
6
6
  import { DocumentLint, DocumentLintFinding } from "./DocumentLint.js";
7
7
  import { DriftPolicy } from "./DriftPolicy.js";
8
+ import { HostedSchema, SCHEMASTORE_CATALOG_BASE, SCHEMASTORE_ID_BASE } from "./HostedSchema.js";
8
9
  import { SchemaFile, SchemaFileNotFoundError, SchemaFileReadError, SchemaFileWriteError } from "./SchemaFile.js";
9
10
  import { SchemaValidator, SchemaValidatorError, ValidationFinding } from "./SchemaValidator.js";
10
11
  import { DRAFT_07_META_SCHEMA, SchemaConversionError, StoreDocument, UndeclaredAnnotationKeyError } from "./StoreDocument.js";
11
12
  import { ContractChangeTarget, PipelineFinding, SchemaContractChangeError, SchemaGateError, SchemaPipeline } from "./SchemaPipeline.js";
12
- import { defineConfig, isSchemastoreConfig } from "./SchemastoreConfig.js";
13
13
  import { SchemaTarget } from "./SchemaTarget.js";
14
+ import { defineConfig, isSchemastoreConfig } from "./SchemastoreConfig.js";
14
15
 
15
- export { CanonicalJson, CatalogEntry, CatalogLintFinding, ContractChangeTarget, DRAFT_07_META_SCHEMA, DocumentDiff, DocumentLint, DocumentLintFinding, DriftPolicy, InvalidSchemaVersionError, JsonDepthExceededError, KeywordFamilies, NonJsonValueError, PipelineFinding, SchemaContractChangeError, SchemaConversionError, SchemaFile, SchemaFileNotFoundError, SchemaFileReadError, SchemaFileWriteError, SchemaGateError, SchemaPipeline, SchemaTarget, SchemaValidator, SchemaValidatorError, SchemaVersion, SchemaVersioning, StoreDocument, UndeclaredAnnotationKeyError, ValidationFinding, defineConfig, isSchemastoreConfig };
16
+ export { CanonicalJson, CatalogEntry, CatalogLintFinding, ContractChangeTarget, DRAFT_07_META_SCHEMA, DocumentDiff, DocumentLint, DocumentLintFinding, DriftPolicy, HostedSchema, InvalidSchemaVersionError, JsonDepthExceededError, KeywordFamilies, NonJsonValueError, PipelineFinding, SCHEMASTORE_CATALOG_BASE, SCHEMASTORE_ID_BASE, SchemaContractChangeError, SchemaConversionError, SchemaFile, SchemaFileNotFoundError, SchemaFileReadError, SchemaFileWriteError, SchemaGateError, SchemaPipeline, SchemaTarget, SchemaValidator, SchemaValidatorError, SchemaVersion, SchemaVersioning, StoreDocument, UndeclaredAnnotationKeyError, ValidationFinding, defineConfig, isSchemastoreConfig };