@effected/schemastore 0.11.0 → 0.13.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/CatalogEntry.js +4 -2
- package/HostedSchema.js +230 -0
- package/README.md +67 -266
- package/SchemaPipeline.js +7 -4
- package/SchemaValidator.js +19 -88
- package/SchemaVersioning.js +17 -11
- package/SchemastoreConfig.js +123 -127
- package/StoreDocument.js +1 -0
- package/index.d.ts +222 -88
- package/index.js +3 -2
- package/package.json +2 -4
package/SchemaVersioning.js
CHANGED
|
@@ -194,23 +194,27 @@ var SchemaVersioning = class SchemaVersioning {
|
|
|
194
194
|
* Derives the schema file name for a catalog name: `name.json`
|
|
195
195
|
* unversioned, `name-<version>.json` versioned under the `"flat"`
|
|
196
196
|
* layout (the default and the only shape SchemaStore serves), or
|
|
197
|
-
* `<version>/name-<version>.json` under `"versioned"
|
|
197
|
+
* `<version>/name-<version>.json` under `"versioned"` — `<version>/name.json`
|
|
198
|
+
* when `appendVersion` is `false`, which only the versioned layout can
|
|
199
|
+
* carry (under `"flat"` two versions would share one file name, so that
|
|
200
|
+
* combination throws).
|
|
198
201
|
*
|
|
199
202
|
* The name must be a simple file base name (no separators, no
|
|
200
203
|
* whitespace); anything else is a wiring mistake and throws.
|
|
201
204
|
*/
|
|
202
|
-
static fileName(name, version, layout = "flat") {
|
|
205
|
+
static fileName(name, version, layout = "flat", appendVersion = true) {
|
|
203
206
|
assertSimpleName(name);
|
|
207
|
+
if (!appendVersion && layout !== "versioned") throw new Error(`appendVersion: false requires the "versioned" layout; under "${layout}" every version of "${name}" would share one file name`);
|
|
204
208
|
if (version === void 0) return `${name}.json`;
|
|
205
|
-
const file = `${name}-${version}.json`;
|
|
209
|
+
const file = appendVersion ? `${name}-${version}.json` : `${name}.json`;
|
|
206
210
|
return layout === "versioned" ? `${version}/${file}` : file;
|
|
207
211
|
}
|
|
208
212
|
/**
|
|
209
213
|
* The canonical URL a schema file is hosted at: `baseUrl` joined with
|
|
210
214
|
* {@link SchemaVersioning.fileName}.
|
|
211
215
|
*/
|
|
212
|
-
static schemaUrl(baseUrl, name, version, layout = "flat") {
|
|
213
|
-
return joinUrl(baseUrl, SchemaVersioning.fileName(name, version, layout));
|
|
216
|
+
static schemaUrl(baseUrl, name, version, layout = "flat", appendVersion = true) {
|
|
217
|
+
return joinUrl(baseUrl, SchemaVersioning.fileName(name, version, layout, appendVersion));
|
|
214
218
|
}
|
|
215
219
|
/**
|
|
216
220
|
* Assembles the `url`/`versions` half of a catalog entry.
|
|
@@ -228,9 +232,10 @@ var SchemaVersioning = class SchemaVersioning {
|
|
|
228
232
|
* verbatim — so a differently-spelled equivalent (`"1.2"` matching a
|
|
229
233
|
* `"1.2.0"` member) still points `url` at the same file the map does.
|
|
230
234
|
*
|
|
231
|
-
* `layout` (default `"flat"`)
|
|
232
|
-
*
|
|
233
|
-
* directory
|
|
235
|
+
* `layout` (default `"flat"`) and `appendVersion` (default `true`) are
|
|
236
|
+
* forwarded to every URL derivation, so `"versioned"` nests every map
|
|
237
|
+
* value and `url` under its own version directory, with or without the
|
|
238
|
+
* `-<version>` file suffix.
|
|
234
239
|
*
|
|
235
240
|
* Labels are inserted in ascending {@link SchemaVersioning.Order}; see
|
|
236
241
|
* {@link CatalogUrls.versions} for why a bare-major key's serialized
|
|
@@ -239,17 +244,18 @@ var SchemaVersioning = class SchemaVersioning {
|
|
|
239
244
|
static catalogUrls(options) {
|
|
240
245
|
const { baseUrl, name, versions } = options;
|
|
241
246
|
const layout = options.layout ?? "flat";
|
|
242
|
-
|
|
247
|
+
const appendVersion = options.appendVersion ?? true;
|
|
248
|
+
if (versions === void 0) return { url: SchemaVersioning.schemaUrl(baseUrl, name, void 0, layout, appendVersion) };
|
|
243
249
|
if (versions.length === 0) throw new Error(`catalogUrls received an empty versions array for "${name}": pass undefined for the unversioned mode`);
|
|
244
250
|
const ascending = [...versions].sort(SchemaVersioning.Order);
|
|
245
251
|
const map = {};
|
|
246
|
-
for (const version of ascending) map[version] = SchemaVersioning.schemaUrl(baseUrl, name, version, layout);
|
|
252
|
+
for (const version of ascending) map[version] = SchemaVersioning.schemaUrl(baseUrl, name, version, layout, appendVersion);
|
|
247
253
|
const newest = ascending[ascending.length - 1];
|
|
248
254
|
const requested = options.current ?? newest;
|
|
249
255
|
const current = ascending.find((v) => SchemaVersioning.Order(v, requested) === 0);
|
|
250
256
|
if (current === void 0) throw new Error(`catalogUrls: current "${requested}" is not one of the versions of "${name}"`);
|
|
251
257
|
return {
|
|
252
|
-
url: SchemaVersioning.schemaUrl(baseUrl, name, current, layout),
|
|
258
|
+
url: SchemaVersioning.schemaUrl(baseUrl, name, current, layout, appendVersion),
|
|
253
259
|
versions: map
|
|
254
260
|
};
|
|
255
261
|
}
|
package/SchemastoreConfig.js
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
import { SchemaVersioning } from "./SchemaVersioning.js";
|
|
2
2
|
import { CatalogEntry } from "./CatalogEntry.js";
|
|
3
3
|
import { DriftPolicy } from "./DriftPolicy.js";
|
|
4
|
+
import { HostedSchema } from "./HostedSchema.js";
|
|
4
5
|
import { SchemaTarget } from "./SchemaTarget.js";
|
|
5
|
-
import {
|
|
6
|
+
import { Predicate, Result, Schema } from "effect";
|
|
6
7
|
|
|
7
8
|
//#region src/SchemastoreConfig.ts
|
|
8
9
|
const ConfigBrand = Symbol.for("@effected/schemastore/SchemastoreConfig");
|
|
9
|
-
/** The host SchemaStore-hosted documents declare in `$id`. @public */
|
|
10
|
-
const SCHEMASTORE_ID_BASE = "https://json.schemastore.org";
|
|
11
|
-
/** The host SchemaStore's `catalog.json` points `url` at. @public */
|
|
12
|
-
const SCHEMASTORE_CATALOG_BASE = "https://www.schemastore.org";
|
|
13
|
-
const DRIFT_TOLERANCES = [
|
|
14
|
-
"strict",
|
|
15
|
-
"semantic",
|
|
16
|
-
"allow"
|
|
17
|
-
];
|
|
18
|
-
const ON_DRIFT = ["error", "warn"];
|
|
19
|
-
const LAYOUTS = ["flat", "versioned"];
|
|
20
10
|
const fail = (message) => {
|
|
21
11
|
throw new Error(`defineConfig: ${message}`);
|
|
22
12
|
};
|
|
@@ -25,121 +15,130 @@ const trimSlashes = (dir) => {
|
|
|
25
15
|
while (end > 1 && dir.charCodeAt(end - 1) === 47) end -= 1;
|
|
26
16
|
return dir.slice(0, end);
|
|
27
17
|
};
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
18
|
+
const members = (record) => Object.keys(record);
|
|
19
|
+
const DriftToleranceInput = Schema.Literals(members({
|
|
20
|
+
strict: null,
|
|
21
|
+
semantic: null,
|
|
22
|
+
allow: null
|
|
23
|
+
}));
|
|
24
|
+
const OnDriftInput = Schema.Literals(members({
|
|
25
|
+
error: null,
|
|
26
|
+
warn: null
|
|
27
|
+
}));
|
|
28
|
+
const LayoutInput = Schema.Literals(members({
|
|
29
|
+
flat: null,
|
|
30
|
+
versioned: null
|
|
31
|
+
}));
|
|
32
|
+
const EffectSchemaInput = Schema.declare((u) => Schema.isSchema(u), { expected: "an Effect Schema" });
|
|
33
|
+
const OptionsInput = Schema.declare((u) => Predicate.isObject(u), { expected: "an object" });
|
|
34
|
+
const CatalogBlockInput = Schema.Struct({
|
|
35
|
+
description: Schema.String,
|
|
36
|
+
fileMatch: Schema.NonEmptyArray(Schema.String)
|
|
37
|
+
});
|
|
38
|
+
const EntryInput = Schema.Struct({
|
|
39
|
+
schema: EffectSchemaInput,
|
|
40
|
+
hosted: Schema.optionalKey(Schema.instanceOf(HostedSchema, { expected: "a HostedSchema" })),
|
|
41
|
+
versions: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
42
|
+
current: Schema.optionalKey(Schema.String),
|
|
43
|
+
published: Schema.optionalKey(Schema.Boolean),
|
|
44
|
+
baseUrl: Schema.optionalKey(Schema.String),
|
|
45
|
+
layout: Schema.optionalKey(LayoutInput),
|
|
46
|
+
appendVersion: Schema.optionalKey(Schema.Boolean),
|
|
47
|
+
drift: Schema.optionalKey(DriftToleranceInput),
|
|
48
|
+
catalog: Schema.optionalKey(CatalogBlockInput),
|
|
49
|
+
jsonSchema: Schema.optionalKey(OptionsInput),
|
|
50
|
+
rootAnnotations: Schema.optionalKey(OptionsInput)
|
|
51
|
+
});
|
|
52
|
+
const ConfigInput = Schema.Struct({
|
|
53
|
+
outputDir: Schema.NonEmptyString,
|
|
54
|
+
baseUrl: Schema.optionalKey(Schema.String),
|
|
55
|
+
drift: Schema.optionalKey(DriftToleranceInput),
|
|
56
|
+
onDrift: Schema.optionalKey(OnDriftInput),
|
|
57
|
+
catalogPath: Schema.optionalKey(Schema.NonEmptyString),
|
|
58
|
+
schemas: Schema.Record(Schema.String, Schema.Unknown)
|
|
59
|
+
});
|
|
60
|
+
const DECODE_OPTIONS = {
|
|
61
|
+
onExcessProperty: "error",
|
|
62
|
+
errors: "all"
|
|
51
63
|
};
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
const withoutUndefined = (input) => Predicate.isObject(input) && !Array.isArray(input) ? Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0)) : input;
|
|
65
|
+
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, " ")}`));
|
|
66
|
+
const resolveIdentity = (name, entry, defaultBaseUrl) => {
|
|
67
|
+
if (entry.hosted !== void 0) {
|
|
68
|
+
if (entry.hosted.name !== name) return fail(`schema "${name}" is keyed differently from its hosted identity "${entry.hosted.name}"`);
|
|
69
|
+
const spelled = [
|
|
70
|
+
"baseUrl",
|
|
71
|
+
"versions",
|
|
72
|
+
"current",
|
|
73
|
+
"layout",
|
|
74
|
+
"appendVersion"
|
|
75
|
+
].filter((key) => entry[key] !== void 0);
|
|
76
|
+
if (spelled.length > 0) return fail(`schema "${name}" declares ${spelled.map((key) => `"${key}"`).join(", ")} beside hosted; the hosted identity owns them`);
|
|
77
|
+
return entry.hosted;
|
|
65
78
|
}
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
const isDriftTolerance = (value) => DRIFT_TOLERANCES.includes(value);
|
|
80
|
-
const resolveConfigDrift = (value) => {
|
|
81
|
-
if (value === void 0) return DriftPolicy.defaults.policy;
|
|
82
|
-
return isDriftTolerance(value) ? value : fail(`config has an invalid drift tolerance "${String(value)}"`);
|
|
83
|
-
};
|
|
84
|
-
const resolveSchemaDrift = (name, value, fallback) => {
|
|
85
|
-
if (value === void 0) return fallback;
|
|
86
|
-
return isDriftTolerance(value) ? value : fail(`schema "${name}" has an invalid drift tolerance "${String(value)}"`);
|
|
79
|
+
const baseUrl = entry.baseUrl ?? defaultBaseUrl;
|
|
80
|
+
if (baseUrl === void 0) return fail(`schema "${name}" has no baseUrl and the config declares no default`);
|
|
81
|
+
const decoded = Schema.decodeUnknownResult(HostedSchema)(withoutUndefined({
|
|
82
|
+
name,
|
|
83
|
+
baseUrl,
|
|
84
|
+
versions: entry.versions,
|
|
85
|
+
current: entry.current,
|
|
86
|
+
layout: entry.layout,
|
|
87
|
+
appendVersion: entry.appendVersion
|
|
88
|
+
}));
|
|
89
|
+
return Result.getOrThrowWith(decoded, (error) => /* @__PURE__ */ new Error(`defineConfig: ${error.message.replace(/\n\s*/g, " ")}`));
|
|
87
90
|
};
|
|
88
|
-
const resolveEntry = (name,
|
|
91
|
+
const resolveEntry = (name, input, defaults, outputDir) => {
|
|
89
92
|
if (!SchemaVersioning.isSimpleName(name)) return fail(`schema "${name}" must be keyed by a simple file base name (no separators, no whitespace)`);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const urlOf = (base) => (version) => SchemaVersioning.schemaUrl(base, name, version, hosting.layout);
|
|
101
|
-
const idOf = urlOf(hosting.idBase);
|
|
102
|
-
const catalogUrlOf = urlOf(hosting.catalogBase);
|
|
103
|
-
const current = versioned?.current;
|
|
93
|
+
const entry = decodeOrThrow(EntryInput, withoutUndefined(input), `schema "${name}" `);
|
|
94
|
+
const hosted = resolveIdentity(name, entry, defaults.baseUrl);
|
|
95
|
+
if (hosted.baseUrl === "schemastore" && entry.catalog === void 0) return fail(`schema "${name}" must declare a catalog block under baseUrl "schemastore"`);
|
|
96
|
+
const versions = hosted.resolvedVersions;
|
|
97
|
+
const current = hosted.resolvedCurrent;
|
|
98
|
+
const file = (version) => `${outputDir}/${hosted.fileNameFor(version)}`;
|
|
99
|
+
const generation = {
|
|
100
|
+
...entry.jsonSchema !== void 0 ? { jsonSchema: entry.jsonSchema } : {},
|
|
101
|
+
...entry.rootAnnotations !== void 0 ? { rootAnnotations: entry.rootAnnotations } : {}
|
|
102
|
+
};
|
|
104
103
|
const target = current === void 0 ? SchemaTarget.make({
|
|
105
104
|
schema: entry.schema,
|
|
106
|
-
$id:
|
|
105
|
+
$id: hosted.idFor(current),
|
|
107
106
|
name,
|
|
108
107
|
path: file(current),
|
|
109
108
|
published: entry.published ?? false,
|
|
110
|
-
...
|
|
111
|
-
...entry.rootAnnotations !== void 0 ? { rootAnnotations: entry.rootAnnotations } : {}
|
|
109
|
+
...generation
|
|
112
110
|
}) : SchemaTarget.make({
|
|
113
111
|
schema: entry.schema,
|
|
114
|
-
$id:
|
|
112
|
+
$id: hosted.idFor(current),
|
|
115
113
|
name,
|
|
116
114
|
path: file(current),
|
|
117
115
|
version: current,
|
|
118
116
|
published: entry.published ?? false,
|
|
119
|
-
...
|
|
120
|
-
...entry.rootAnnotations !== void 0 ? { rootAnnotations: entry.rootAnnotations } : {}
|
|
117
|
+
...generation
|
|
121
118
|
});
|
|
122
|
-
const frozen =
|
|
119
|
+
const frozen = versions.filter((v) => v !== current).map((version) => ({
|
|
123
120
|
version,
|
|
124
121
|
path: file(version),
|
|
125
|
-
|
|
122
|
+
$id: hosted.idFor(version),
|
|
123
|
+
url: hosted.urlFor(version)
|
|
126
124
|
}));
|
|
127
125
|
const catalog = entry.catalog === void 0 ? void 0 : CatalogEntry.assemble({
|
|
128
126
|
name,
|
|
129
127
|
description: entry.catalog.description,
|
|
130
128
|
fileMatch: entry.catalog.fileMatch,
|
|
131
|
-
baseUrl:
|
|
132
|
-
layout:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
baseUrl: hosted.catalogBase,
|
|
130
|
+
layout: hosted.resolvedLayout,
|
|
131
|
+
appendVersion: hosted.resolvedAppendVersion,
|
|
132
|
+
...current !== void 0 ? {
|
|
133
|
+
versions,
|
|
134
|
+
current
|
|
136
135
|
} : {}
|
|
137
136
|
});
|
|
138
137
|
return {
|
|
139
138
|
name,
|
|
140
139
|
target,
|
|
141
140
|
frozen,
|
|
142
|
-
drift:
|
|
141
|
+
drift: entry.drift ?? defaults.drift,
|
|
143
142
|
...catalog !== void 0 ? { catalog } : {}
|
|
144
143
|
};
|
|
145
144
|
};
|
|
@@ -180,44 +179,41 @@ const assertUniquePaths = (paths) => {
|
|
|
180
179
|
* for the catalog URL, forcing the `"flat"` layout; any other `baseUrl` is
|
|
181
180
|
* used as one base for both, defaulting to the `"versioned"` layout.
|
|
182
181
|
*
|
|
183
|
-
* Throws a plain `Error` (never a raw `TypeError`)
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* `baseUrl: "schemastore"
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
182
|
+
* Throws a plain `Error` (never a raw `TypeError`) on every malformed
|
|
183
|
+
* input. The shape is decoded once per level with a `Schema.Struct`
|
|
184
|
+
* (`errors: "all"`, so every issue on an entry is reported at once, and
|
|
185
|
+
* `onExcessProperty: "error"`, so a typo'd key is named rather than
|
|
186
|
+
* dropped); the message is `defineConfig: schema "<name>" ` followed by the
|
|
187
|
+
* decode issues (`Expected string at ["baseUrl"]`). After a shape-clean
|
|
188
|
+
* decode the cross-field rules run: the entry's identity — its
|
|
189
|
+
* {@link HostedSchema}, or one built from `baseUrl`/`versions`/`current`/
|
|
190
|
+
* `layout` and the config default — is validated by `HostedSchema` itself
|
|
191
|
+
* (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
|
|
198
|
+
* module's default export via {@link isSchemastoreConfig}.
|
|
200
199
|
*
|
|
201
200
|
* @public
|
|
202
201
|
*/
|
|
203
202
|
const defineConfig = (input) => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (!Predicate.isObject(input.schemas) || Object.keys(input.schemas).length === 0) return fail("at least one schema is required");
|
|
208
|
-
if (input.onDrift !== void 0 && !ON_DRIFT.includes(input.onDrift)) return fail(`invalid onDrift "${String(input.onDrift)}"`);
|
|
209
|
-
if (input.catalogPath !== void 0 && (typeof input.catalogPath !== "string" || input.catalogPath.length === 0)) return fail("catalogPath must be a non-empty string when given");
|
|
203
|
+
const config = decodeOrThrow(ConfigInput, withoutUndefined(input), "");
|
|
204
|
+
const outputDir = trimSlashes(config.outputDir);
|
|
205
|
+
if (Object.keys(config.schemas).length === 0) return fail("at least one schema is required");
|
|
210
206
|
const defaults = {
|
|
211
|
-
baseUrl:
|
|
212
|
-
drift:
|
|
207
|
+
baseUrl: config.baseUrl,
|
|
208
|
+
drift: config.drift ?? DriftPolicy.defaults.policy
|
|
213
209
|
};
|
|
214
|
-
const schemas = Object.entries(
|
|
215
|
-
const catalogPath =
|
|
210
|
+
const schemas = Object.entries(config.schemas).map(([name, entry]) => resolveEntry(name, entry, defaults, outputDir));
|
|
211
|
+
const catalogPath = config.catalogPath ?? `${outputDir}/catalog.json`;
|
|
216
212
|
assertUniquePaths([...schemas.flatMap((s) => [s.target.path, ...s.frozen.map((f) => f.path)]), catalogPath]);
|
|
217
213
|
return {
|
|
218
214
|
[ConfigBrand]: true,
|
|
219
215
|
outputDir,
|
|
220
|
-
onDrift:
|
|
216
|
+
onDrift: config.onDrift ?? DriftPolicy.defaults.onDrift,
|
|
221
217
|
catalogPath,
|
|
222
218
|
schemas
|
|
223
219
|
};
|
|
@@ -231,4 +227,4 @@ const defineConfig = (input) => {
|
|
|
231
227
|
const isSchemastoreConfig = (value) => typeof value === "object" && value !== null && value[ConfigBrand] === true;
|
|
232
228
|
|
|
233
229
|
//#endregion
|
|
234
|
-
export {
|
|
230
|
+
export { defineConfig, isSchemastoreConfig };
|
package/StoreDocument.js
CHANGED
|
@@ -207,6 +207,7 @@ var StoreDocument = class StoreDocument extends Schema.Class("StoreDocument")({
|
|
|
207
207
|
const userIncludes = options.jsonSchema?.includeAnnotationKey;
|
|
208
208
|
const undeclared = /* @__PURE__ */ new Set();
|
|
209
209
|
const document = Schema.toJsonSchemaDocument(source, {
|
|
210
|
+
onExcessProperty: "error",
|
|
210
211
|
...options.jsonSchema,
|
|
211
212
|
includeAnnotationKey: (key) => {
|
|
212
213
|
if (KeywordFamilies.isDeclared(key)) return true;
|