@effected/schemastore 0.16.1 → 0.17.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/README.md CHANGED
@@ -61,6 +61,7 @@ import { defineConfig } from "@effected/schemastore";
61
61
  import { OutputSchemaIdentity, ReleaseOutput } from "./src/schema/output.js";
62
62
 
63
63
  export default defineConfig({
64
+ name: "silk-release-action",
64
65
  outputDir: "schemas",
65
66
  schemas: {
66
67
  [OutputSchemaIdentity.name]: { schema: ReleaseOutput, hosted: OutputSchemaIdentity },
@@ -130,7 +131,7 @@ const program = SchemaPipeline.run(targets).pipe(
130
131
  ## What the library owns
131
132
 
132
133
  - `HostedSchema` — a schema's hosted identity (`github`, `schemastore`, `custom`), deriving `$id`, the catalog URL and the file name for the current or any advertised version.
133
- - `defineConfig` — the `schemastore.config.ts` contract: validated with one `Schema.Struct` per level (a typo'd key is named, every issue on an entry reported at once), every path and URL derived from the entry key and its identity, frozen labels resolved, a branded result the CLI recognises.
134
+ - `defineConfig` — the `schemastore.config.ts` contract: validated with one `Schema.Struct` per level (a typo'd key is named, every issue on an entry reported at once), a required config `name` (the base name of its catalog slice), every path and URL derived from the entry key and its identity, frozen labels resolved, a branded result the CLI recognises.
134
135
  - `StoreDocument` — assembly: `fromSchema` / `fromSchemaResult`, the `draft07` constructor for hand-built documents, the flat `toJson()` publication shape, `serializeResult()`, `DRAFT_07_META_SCHEMA`.
135
136
  - `KeywordFamilies` — the one registry of declared non-standard keyword families (the vscode five, `x-taplo`, `x-tombi-`, `x-intellij-`, and the house `x-ai-` machine-annotation namespace). Anything outside it fails `fromSchema` with `UndeclaredAnnotationKeyError`; nothing is silently dropped.
136
137
  - `SchemaVersioning` / `SchemaVersion` — one-to-three-component version labels, `Order`, `latest`, `isPinned`, `next`, and the `fileName` / `schemaUrl` / `catalogUrls` derivations.
@@ -50,11 +50,12 @@ const EntryInput = Schema.Struct({
50
50
  rootAnnotations: Schema.optionalKey(OptionsInput)
51
51
  });
52
52
  const ConfigInput = Schema.Struct({
53
+ name: Schema.String,
53
54
  outputDir: Schema.NonEmptyString,
54
55
  baseUrl: Schema.optionalKey(Schema.String),
55
56
  drift: Schema.optionalKey(DriftToleranceInput),
56
57
  onDrift: Schema.optionalKey(OnDriftInput),
57
- catalogPath: Schema.optionalKey(Schema.NonEmptyString),
58
+ catalogDir: Schema.optionalKey(Schema.NonEmptyString),
58
59
  schemas: Schema.Record(Schema.String, Schema.Unknown)
59
60
  });
60
61
  const DECODE_OPTIONS = {
@@ -156,6 +157,12 @@ const normalizePath = (raw) => {
156
157
  }
157
158
  return `${absolute ? "/" : ""}${out.join("/")}`;
158
159
  };
160
+ const assertCatalogDirHoldsOnlySlices = (catalogDir, outputDir, documents) => {
161
+ const dir = normalizePath(catalogDir);
162
+ if (dir === normalizePath(`${catalogDir}/../catalog.json`)) fail(`catalogDir "${catalogDir}" must not be the merged catalog's path (catalog.json in its parent)`);
163
+ if (dir === normalizePath(outputDir)) fail(`catalogDir "${catalogDir}" must not be outputDir: every *.json file in it is read as a catalog slice`);
164
+ for (const document of documents) if (normalizePath(`${document}/..`) === dir) fail(`output path "${document}" sits in catalogDir "${catalogDir}", which holds only catalog slices`);
165
+ };
159
166
  const assertUniquePaths = (paths) => {
160
167
  const seen = /* @__PURE__ */ new Set();
161
168
  for (const p of paths) {
@@ -189,18 +196,26 @@ const assertUniquePaths = (paths) => {
189
196
  * {@link HostedSchema}, or one built from `baseUrl`/`versions`/`current`/
190
197
  * `layout` and the config default — is validated by `HostedSchema` itself
191
198
  * (a `hosted` entry must be keyed by `hosted.name` and must not spell those
192
- * four fields beside it); a schema key must be a simple file base name; a
193
- * `catalog` is required under `baseUrl: "schemastore"`; an empty `schemas`
194
- * record is rejected; and an output path (a target, a frozen file, or the
195
- * catalog path) declared twice is rejected after a lexical normalisation
196
- * (`./`, `..`, trailing `/`) — the CLI's loader re-checks on the resolved
197
- * absolute paths. Branding the result lets a loader recognise a config
199
+ * four fields beside it); the config `name` is required (a missing one
200
+ * fails `defineConfig: name is required — …`, naming what it is for), and
201
+ * it and every schema key must be simple file base names; a `catalog` is required under
202
+ * `baseUrl: "schemastore"`; an empty `schemas` record is rejected; a
203
+ * `catalogDir` that is `outputDir`, that is the merged catalog's own path,
204
+ * or that a derived document sits directly in, is rejected (every `*.json`
205
+ * file there is read as a catalog slice); and an output path (a target, a frozen file, this config's
206
+ * catalog slice `<catalogDir>/<name>.json`, or the merged catalog
207
+ * `catalog.json` in `catalogDir`'s parent) declared twice is rejected after
208
+ * a lexical normalisation (`./`, `..`, trailing `/`) — the CLI's loader
209
+ * re-checks on the resolved absolute paths. Branding the result lets a loader recognise a config
198
210
  * module's default export via {@link isSchemastoreConfig}.
199
211
  *
200
212
  * @public
201
213
  */
202
214
  const defineConfig = (input) => {
203
- const config = decodeOrThrow(ConfigInput, withoutUndefined(input), "");
215
+ const raw = withoutUndefined(input);
216
+ if (Predicate.isObject(raw) && !Array.isArray(raw) && !Object.hasOwn(raw, "name")) return fail("name is required — the base name of this config's catalog slice (<catalogDir>/<name>.json)");
217
+ const config = decodeOrThrow(ConfigInput, raw, "");
218
+ if (!SchemaVersioning.isSimpleName(config.name)) return fail(`name "${config.name}" must be a simple file base name (no separators, no whitespace)`);
204
219
  const outputDir = trimSlashes(config.outputDir);
205
220
  if (Object.keys(config.schemas).length === 0) return fail("at least one schema is required");
206
221
  const defaults = {
@@ -208,13 +223,20 @@ const defineConfig = (input) => {
208
223
  drift: config.drift ?? DriftPolicy.defaults.policy
209
224
  };
210
225
  const schemas = Object.entries(config.schemas).map(([name, entry]) => resolveEntry(name, entry, defaults, outputDir));
211
- const catalogPath = config.catalogPath ?? `${outputDir}/catalog.json`;
212
- assertUniquePaths([...schemas.flatMap((s) => [s.target.path, ...s.frozen.map((f) => f.path)]), catalogPath]);
226
+ const catalogDir = config.catalogDir !== void 0 ? trimSlashes(config.catalogDir) : `${outputDir}/catalogs`;
227
+ const documents = schemas.flatMap((s) => [s.target.path, ...s.frozen.map((f) => f.path)]);
228
+ assertCatalogDirHoldsOnlySlices(catalogDir, outputDir, documents);
229
+ assertUniquePaths([
230
+ ...documents,
231
+ `${catalogDir}/${config.name}.json`,
232
+ `${catalogDir}/../catalog.json`
233
+ ]);
213
234
  return {
214
235
  [ConfigBrand]: true,
236
+ name: config.name,
215
237
  outputDir,
216
238
  onDrift: config.onDrift ?? DriftPolicy.defaults.onDrift,
217
- catalogPath,
239
+ catalogDir,
218
240
  schemas
219
241
  };
220
242
  };
package/index.d.ts CHANGED
@@ -1890,6 +1890,15 @@ interface SchemaEntryInput {
1890
1890
  * @public
1891
1891
  */
1892
1892
  interface SchemastoreConfigInput {
1893
+ /**
1894
+ * This config's identity: the base name of the catalog slice it owns
1895
+ * (`<catalogDir>/<name>.json`). Configs sharing a `catalogDir` must each
1896
+ * carry a distinct one — distinct case-insensitively, since on a
1897
+ * case-insensitive volume `docs` and `Docs` name one file and overwrite
1898
+ * each other; it must be a simple file base name (no separators, no
1899
+ * whitespace).
1900
+ */
1901
+ readonly name: string;
1893
1902
  /** The directory every derived `path` is written under; a trailing slash is trimmed. */
1894
1903
  readonly outputDir: string;
1895
1904
  /** The default {@link SchemaEntryInput.baseUrl} for an entry that declares none. */
@@ -1898,8 +1907,20 @@ interface SchemastoreConfigInput {
1898
1907
  readonly drift?: DriftTolerance;
1899
1908
  /** What a build does when it finds drift. Defaults to `"error"`. */
1900
1909
  readonly onDrift?: OnDrift;
1901
- /** Where the assembled catalog is written. Defaults to `<outputDir>/catalog.json`. */
1902
- readonly catalogPath?: string;
1910
+ /**
1911
+ * The directory of catalog slices, one per config: this config writes its
1912
+ * own entries to `<catalogDir>/<name>.json`, and the CLI maintains the
1913
+ * merged `catalog.json` — every slice in the directory, united — in the
1914
+ * directory's parent. Defaults to `<outputDir>/catalogs`, so
1915
+ * the merged catalog lands at `<outputDir>/catalog.json`. Every `*.json`
1916
+ * file in it is read as a slice, so it holds nothing else, must not be
1917
+ * `outputDir` itself, and must not be the merged catalog's own path.
1918
+ * Every config that shares a merged catalog must share the same
1919
+ * `catalogDir`, under a `name` unique case-insensitively among them: two sibling directories (`schemas/catalogs`,
1920
+ * `schemas/more`) both merge into `schemas/catalog.json` from different
1921
+ * slice sets and overwrite each other — no single config can detect it.
1922
+ */
1923
+ readonly catalogDir?: string;
1903
1924
  /**
1904
1925
  * The schemas to derive, keyed by file base name — the key IS the
1905
1926
  * `name` every derived path and URL is built from, so it must be a
@@ -1952,12 +1973,14 @@ interface ResolvedSchema {
1952
1973
  */
1953
1974
  interface SchemastoreConfig {
1954
1975
  readonly [ConfigBrand]: true;
1976
+ /** This config's identity: the base name of its catalog slice. */
1977
+ readonly name: string;
1955
1978
  /** The directory every derived `path` is written under, trailing slash trimmed. */
1956
1979
  readonly outputDir: string;
1957
1980
  /** What a build does when it finds drift. */
1958
1981
  readonly onDrift: OnDrift;
1959
- /** Where the assembled catalog is written. */
1960
- readonly catalogPath: string;
1982
+ /** The directory of catalog slices; this config's own is `<catalogDir>/<name>.json`. */
1983
+ readonly catalogDir: string;
1961
1984
  /** Every schema, resolved. */
1962
1985
  readonly schemas: ReadonlyArray<ResolvedSchema>;
1963
1986
  }
@@ -1986,12 +2009,17 @@ interface SchemastoreConfig {
1986
2009
  * {@link HostedSchema}, or one built from `baseUrl`/`versions`/`current`/
1987
2010
  * `layout` and the config default — is validated by `HostedSchema` itself
1988
2011
  * (a `hosted` entry must be keyed by `hosted.name` and must not spell those
1989
- * four fields beside it); a schema key must be a simple file base name; a
1990
- * `catalog` is required under `baseUrl: "schemastore"`; an empty `schemas`
1991
- * record is rejected; and an output path (a target, a frozen file, or the
1992
- * catalog path) declared twice is rejected after a lexical normalisation
1993
- * (`./`, `..`, trailing `/`) — the CLI's loader re-checks on the resolved
1994
- * absolute paths. Branding the result lets a loader recognise a config
2012
+ * four fields beside it); the config `name` is required (a missing one
2013
+ * fails `defineConfig: name is required — …`, naming what it is for), and
2014
+ * it and every schema key must be simple file base names; a `catalog` is required under
2015
+ * `baseUrl: "schemastore"`; an empty `schemas` record is rejected; a
2016
+ * `catalogDir` that is `outputDir`, that is the merged catalog's own path,
2017
+ * or that a derived document sits directly in, is rejected (every `*.json`
2018
+ * file there is read as a catalog slice); and an output path (a target, a frozen file, this config's
2019
+ * catalog slice `<catalogDir>/<name>.json`, or the merged catalog
2020
+ * `catalog.json` in `catalogDir`'s parent) declared twice is rejected after
2021
+ * a lexical normalisation (`./`, `..`, trailing `/`) — the CLI's loader
2022
+ * re-checks on the resolved absolute paths. Branding the result lets a loader recognise a config
1995
2023
  * module's default export via {@link isSchemastoreConfig}.
1996
2024
  *
1997
2025
  * @public
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/schemastore",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "private": false,
5
5
  "description": "Build, validate, version and publish SchemaStore-shaped Draft-07 JSON Schema documents from Effect Schema sources: document assembly, ajv strict-mode validation, structural lints, catalog entries, canonical JSON and a content-comparing emit pipeline.",
6
6
  "keywords": [