@effected/schemastore 0.19.0 → 0.21.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
@@ -131,7 +131,7 @@ const program = SchemaPipeline.run(targets).pipe(
131
131
  ## What the library owns
132
132
 
133
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.
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
+ - `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, an optional `catalog.name` display name (defaulting to the key, unique within the config) that renames the catalog entry and nothing else, frozen labels resolved, a branded result the CLI recognises.
135
135
  - `StoreDocument` — assembly: `fromSchema` / `fromSchemaResult`, the `draft07` constructor for hand-built documents, the flat `toJson()` publication shape, `serializeResult()`, `DRAFT_07_META_SCHEMA`.
136
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.
137
137
  - `SchemaVersioning` / `SchemaVersion` — one-to-three-component version labels, `Order`, `latest`, `isPinned`, `next`, and the `fileName` / `schemaUrl` / `catalogUrls` derivations.
@@ -32,6 +32,7 @@ const LayoutInput = Schema.Literals(members({
32
32
  const EffectSchemaInput = Schema.declare((u) => Schema.isSchema(u), { expected: "an Effect Schema" });
33
33
  const OptionsInput = Schema.declare((u) => Predicate.isObject(u), { expected: "an object" });
34
34
  const CatalogBlockInput = Schema.Struct({
35
+ name: Schema.optionalKey(Schema.NonEmptyString),
35
36
  description: Schema.String,
36
37
  fileMatch: Schema.NonEmptyArray(Schema.String)
37
38
  });
@@ -63,6 +64,10 @@ const DECODE_OPTIONS = {
63
64
  errors: "all"
64
65
  };
65
66
  const withoutUndefined = (input) => Predicate.isObject(input) && !Array.isArray(input) ? Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0)) : input;
67
+ const withoutUndefinedCatalog = (entry) => Predicate.isObject(entry) && !Array.isArray(entry) && "catalog" in entry ? {
68
+ ...entry,
69
+ catalog: withoutUndefined(entry.catalog)
70
+ } : entry;
66
71
  const decodeOrThrow = (schema, input, prefix) => Result.getOrThrowWith(Schema.decodeUnknownResult(schema)(input, DECODE_OPTIONS), (error) => /* @__PURE__ */ new Error(`defineConfig: ${prefix}${error.message.replace(/\n\s*/g, " ")}`));
67
72
  const resolveIdentity = (name, entry, defaultBaseUrl) => {
68
73
  if (entry.hosted !== void 0) {
@@ -91,7 +96,7 @@ const resolveIdentity = (name, entry, defaultBaseUrl) => {
91
96
  };
92
97
  const resolveEntry = (name, input, defaults, outputDir) => {
93
98
  if (!SchemaVersioning.isSimpleName(name)) return fail(`schema "${name}" must be keyed by a simple file base name (no separators, no whitespace)`);
94
- const entry = decodeOrThrow(EntryInput, withoutUndefined(input), `schema "${name}" `);
99
+ const entry = decodeOrThrow(EntryInput, withoutUndefinedCatalog(withoutUndefined(input)), `schema "${name}" `);
95
100
  const hosted = resolveIdentity(name, entry, defaults.baseUrl);
96
101
  if (hosted.baseUrl === "schemastore" && entry.catalog === void 0) return fail(`schema "${name}" must declare a catalog block under baseUrl "schemastore"`);
97
102
  const versions = hosted.resolvedVersions;
@@ -124,7 +129,8 @@ const resolveEntry = (name, input, defaults, outputDir) => {
124
129
  url: hosted.urlFor(version)
125
130
  }));
126
131
  const catalog = entry.catalog === void 0 ? void 0 : CatalogEntry.assemble({
127
- name,
132
+ name: entry.catalog.name ?? name,
133
+ fileBaseName: name,
128
134
  description: entry.catalog.description,
129
135
  fileMatch: entry.catalog.fileMatch,
130
136
  baseUrl: hosted.catalogBase,
@@ -163,6 +169,15 @@ const assertCatalogDirHoldsOnlySlices = (catalogDir, outputDir, documents) => {
163
169
  if (dir === normalizePath(outputDir)) fail(`catalogDir "${catalogDir}" must not be outputDir: every *.json file in it is read as a catalog slice`);
164
170
  for (const document of documents) if (normalizePath(`${document}/..`) === dir) fail(`output path "${document}" sits in catalogDir "${catalogDir}", which holds only catalog slices`);
165
171
  };
172
+ const assertUniqueCatalogNames = (schemas) => {
173
+ const owners = /* @__PURE__ */ new Map();
174
+ for (const schema of schemas) {
175
+ if (schema.catalog === void 0) continue;
176
+ const owner = owners.get(schema.catalog.name);
177
+ if (owner !== void 0) fail(`schemas "${owner}" and "${schema.name}" both name their catalog entry "${schema.catalog.name}"`);
178
+ owners.set(schema.catalog.name, schema.name);
179
+ }
180
+ };
166
181
  const assertUniquePaths = (paths) => {
167
182
  const seen = /* @__PURE__ */ new Set();
168
183
  for (const p of paths) {
@@ -199,7 +214,9 @@ const assertUniquePaths = (paths) => {
199
214
  * four fields beside it); the config `name` is required (a missing one
200
215
  * fails `defineConfig: name is required — …`, naming what it is for), and
201
216
  * 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
217
+ * `baseUrl: "schemastore"`; two cataloged schemas whose catalog
218
+ * entries resolve to one `name` (a `catalog.name`, else the key) are
219
+ * rejected, naming both keys; an empty `schemas` record is rejected; a
203
220
  * `catalogDir` that is `outputDir`, that is the merged catalog's own path,
204
221
  * or that a derived document sits directly in, is rejected (every `*.json`
205
222
  * file there is read as a catalog slice); and an output path (a target, a frozen file, this config's
@@ -238,6 +255,7 @@ const defineConfig = (input) => {
238
255
  drift: config.drift ?? DriftPolicy.defaults.policy
239
256
  };
240
257
  const schemas = Object.entries(config.schemas).map(([name, entry]) => resolveEntry(name, entry, defaults, outputDir));
258
+ assertUniqueCatalogNames(schemas);
241
259
  const catalogDir = config.catalogDir !== void 0 ? trimSlashes(config.catalogDir) : `${outputDir}/catalogs`;
242
260
  const documents = schemas.flatMap((s) => [s.target.path, ...s.frozen.map((f) => f.path)]);
243
261
  assertCatalogDirHoldsOnlySlices(catalogDir, outputDir, documents);
package/index.d.ts CHANGED
@@ -1939,6 +1939,16 @@ declare const ConfigBrand: unique symbol;
1939
1939
  * @public
1940
1940
  */
1941
1941
  interface CatalogInput {
1942
+ /**
1943
+ * The entry's display name in the catalog — what an editor or SchemaStore
1944
+ * shows. Defaults to the schema's key. Only the catalog entry's `name`
1945
+ * changes: the key still names the file, `$id` and every catalog URL, so
1946
+ * a versioned `<version>/<name>.json` layout keeps its short file names.
1947
+ * Must be non-empty, and unique among the config's cataloged schemas
1948
+ * (compared after defaulting, so it must not equal another cataloged
1949
+ * schema's key either).
1950
+ */
1951
+ readonly name?: string;
1942
1952
  /** The catalog description. */
1943
1953
  readonly description: string;
1944
1954
  /** Glob patterns editors match files against; must be non-empty. */
@@ -2150,7 +2160,9 @@ interface SchemastoreConfig {
2150
2160
  * four fields beside it); the config `name` is required (a missing one
2151
2161
  * fails `defineConfig: name is required — …`, naming what it is for), and
2152
2162
  * it and every schema key must be simple file base names; a `catalog` is required under
2153
- * `baseUrl: "schemastore"`; an empty `schemas` record is rejected; a
2163
+ * `baseUrl: "schemastore"`; two cataloged schemas whose catalog
2164
+ * entries resolve to one `name` (a `catalog.name`, else the key) are
2165
+ * rejected, naming both keys; an empty `schemas` record is rejected; a
2154
2166
  * `catalogDir` that is `outputDir`, that is the merged catalog's own path,
2155
2167
  * or that a derived document sits directly in, is rejected (every `*.json`
2156
2168
  * file there is read as a catalog slice); and an output path (a target, a frozen file, this config's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/schemastore",
3
- "version": "0.19.0",
3
+ "version": "0.21.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": [