@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/CatalogEntry.js +8 -2
- package/HostedSchema.js +230 -0
- package/README.md +67 -266
- package/SchemaPipeline.js +7 -4
- package/SchemaValidator.js +19 -88
- package/SchemaVersioning.js +44 -11
- package/SchemastoreConfig.js +178 -69
- package/StoreDocument.js +1 -0
- package/index.d.ts +385 -91
- package/index.js +3 -2
- package/package.json +2 -4
package/SchemaVersioning.js
CHANGED
|
@@ -73,7 +73,7 @@ const bumpNext = (current, parsed, components) => {
|
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
const assertSimpleName = (name) => {
|
|
76
|
-
if (
|
|
76
|
+
if (!SchemaVersioning.isSimpleName(name)) throw new Error(`Schema name must be a non-empty simple file base name, got "${name}"`);
|
|
77
77
|
};
|
|
78
78
|
const joinUrl = (baseUrl, file) => {
|
|
79
79
|
let end = baseUrl.length;
|
|
@@ -182,22 +182,39 @@ var SchemaVersioning = class SchemaVersioning {
|
|
|
182
182
|
return reparsed.success;
|
|
183
183
|
}
|
|
184
184
|
/**
|
|
185
|
+
* Whether a name is a simple file base name — non-empty, no path
|
|
186
|
+
* separators, no whitespace — the rule every schema name is held to
|
|
187
|
+
* ({@link SchemaVersioning.fileName} throws on anything else; `defineConfig`
|
|
188
|
+
* rejects a schema key the same way). One predicate so the two cannot drift.
|
|
189
|
+
*/
|
|
190
|
+
static isSimpleName(name) {
|
|
191
|
+
return name.length > 0 && !/[/\\\s]/.test(name);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
185
194
|
* Derives the schema file name for a catalog name: `name.json`
|
|
186
|
-
* unversioned, `name-<version>.json` versioned
|
|
195
|
+
* unversioned, `name-<version>.json` versioned under the `"flat"`
|
|
196
|
+
* layout (the default and the only shape SchemaStore serves), or
|
|
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).
|
|
187
201
|
*
|
|
188
202
|
* The name must be a simple file base name (no separators, no
|
|
189
203
|
* whitespace); anything else is a wiring mistake and throws.
|
|
190
204
|
*/
|
|
191
|
-
static fileName(name, version) {
|
|
205
|
+
static fileName(name, version, layout = "flat", appendVersion = true) {
|
|
192
206
|
assertSimpleName(name);
|
|
193
|
-
|
|
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`);
|
|
208
|
+
if (version === void 0) return `${name}.json`;
|
|
209
|
+
const file = appendVersion ? `${name}-${version}.json` : `${name}.json`;
|
|
210
|
+
return layout === "versioned" ? `${version}/${file}` : file;
|
|
194
211
|
}
|
|
195
212
|
/**
|
|
196
213
|
* The canonical URL a schema file is hosted at: `baseUrl` joined with
|
|
197
214
|
* {@link SchemaVersioning.fileName}.
|
|
198
215
|
*/
|
|
199
|
-
static schemaUrl(baseUrl, name, version) {
|
|
200
|
-
return joinUrl(baseUrl, SchemaVersioning.fileName(name, version));
|
|
216
|
+
static schemaUrl(baseUrl, name, version, layout = "flat", appendVersion = true) {
|
|
217
|
+
return joinUrl(baseUrl, SchemaVersioning.fileName(name, version, layout, appendVersion));
|
|
201
218
|
}
|
|
202
219
|
/**
|
|
203
220
|
* Assembles the `url`/`versions` half of a catalog entry.
|
|
@@ -205,9 +222,20 @@ var SchemaVersioning = class SchemaVersioning {
|
|
|
205
222
|
* Omitting `versions` selects the unversioned mode (`url` only,
|
|
206
223
|
* pointing at the plain `name.json`). Providing them selects the
|
|
207
224
|
* versioned mode: the `versions` map carries every label, and `url`
|
|
208
|
-
* points at
|
|
225
|
+
* points at `current` (default: the newest label under
|
|
226
|
+
* {@link SchemaVersioning.Order}). An **empty** `versions` array is
|
|
209
227
|
* a contradiction (versioned mode with no versions) and throws — pass
|
|
210
|
-
* `undefined` for the unversioned mode.
|
|
228
|
+
* `undefined` for the unversioned mode. `current`, when given, must
|
|
229
|
+
* compare equal under {@link SchemaVersioning.Order} to a member of
|
|
230
|
+
* `versions` or this throws; `url` is built from that member's own
|
|
231
|
+
* spelling (the `versions` map's key), not from the `current` argument
|
|
232
|
+
* verbatim — so a differently-spelled equivalent (`"1.2"` matching a
|
|
233
|
+
* `"1.2.0"` member) still points `url` at the same file the map does.
|
|
234
|
+
*
|
|
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.
|
|
211
239
|
*
|
|
212
240
|
* Labels are inserted in ascending {@link SchemaVersioning.Order}; see
|
|
213
241
|
* {@link CatalogUrls.versions} for why a bare-major key's serialized
|
|
@@ -215,14 +243,19 @@ var SchemaVersioning = class SchemaVersioning {
|
|
|
215
243
|
*/
|
|
216
244
|
static catalogUrls(options) {
|
|
217
245
|
const { baseUrl, name, versions } = options;
|
|
218
|
-
|
|
246
|
+
const layout = options.layout ?? "flat";
|
|
247
|
+
const appendVersion = options.appendVersion ?? true;
|
|
248
|
+
if (versions === void 0) return { url: SchemaVersioning.schemaUrl(baseUrl, name, void 0, layout, appendVersion) };
|
|
219
249
|
if (versions.length === 0) throw new Error(`catalogUrls received an empty versions array for "${name}": pass undefined for the unversioned mode`);
|
|
220
250
|
const ascending = [...versions].sort(SchemaVersioning.Order);
|
|
221
251
|
const map = {};
|
|
222
|
-
for (const version of ascending) map[version] = SchemaVersioning.schemaUrl(baseUrl, name, version);
|
|
252
|
+
for (const version of ascending) map[version] = SchemaVersioning.schemaUrl(baseUrl, name, version, layout, appendVersion);
|
|
223
253
|
const newest = ascending[ascending.length - 1];
|
|
254
|
+
const requested = options.current ?? newest;
|
|
255
|
+
const current = ascending.find((v) => SchemaVersioning.Order(v, requested) === 0);
|
|
256
|
+
if (current === void 0) throw new Error(`catalogUrls: current "${requested}" is not one of the versions of "${name}"`);
|
|
224
257
|
return {
|
|
225
|
-
url: SchemaVersioning.schemaUrl(baseUrl, name,
|
|
258
|
+
url: SchemaVersioning.schemaUrl(baseUrl, name, current, layout, appendVersion),
|
|
226
259
|
versions: map
|
|
227
260
|
};
|
|
228
261
|
}
|
package/SchemastoreConfig.js
CHANGED
|
@@ -1,42 +1,146 @@
|
|
|
1
1
|
import { SchemaVersioning } from "./SchemaVersioning.js";
|
|
2
2
|
import { CatalogEntry } from "./CatalogEntry.js";
|
|
3
3
|
import { DriftPolicy } from "./DriftPolicy.js";
|
|
4
|
-
import {
|
|
4
|
+
import { HostedSchema } from "./HostedSchema.js";
|
|
5
|
+
import { SchemaTarget } from "./SchemaTarget.js";
|
|
6
|
+
import { Predicate, Result, Schema } from "effect";
|
|
5
7
|
|
|
6
8
|
//#region src/SchemastoreConfig.ts
|
|
7
9
|
const ConfigBrand = Symbol.for("@effected/schemastore/SchemastoreConfig");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
const
|
|
17
|
-
|
|
10
|
+
const fail = (message) => {
|
|
11
|
+
throw new Error(`defineConfig: ${message}`);
|
|
12
|
+
};
|
|
13
|
+
const trimSlashes = (dir) => {
|
|
14
|
+
let end = dir.length;
|
|
15
|
+
while (end > 1 && dir.charCodeAt(end - 1) === 47) end -= 1;
|
|
16
|
+
return dir.slice(0, end);
|
|
17
|
+
};
|
|
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({
|
|
18
35
|
description: Schema.String,
|
|
19
|
-
fileMatch: Schema.
|
|
20
|
-
|
|
21
|
-
|
|
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)
|
|
22
51
|
});
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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"
|
|
27
63
|
};
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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;
|
|
38
78
|
}
|
|
39
|
-
|
|
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, " ")}`));
|
|
90
|
+
};
|
|
91
|
+
const resolveEntry = (name, input, defaults, outputDir) => {
|
|
92
|
+
if (!SchemaVersioning.isSimpleName(name)) return fail(`schema "${name}" must be keyed by a simple file base name (no separators, no whitespace)`);
|
|
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
|
+
};
|
|
103
|
+
const target = current === void 0 ? SchemaTarget.make({
|
|
104
|
+
schema: entry.schema,
|
|
105
|
+
$id: hosted.idFor(current),
|
|
106
|
+
name,
|
|
107
|
+
path: file(current),
|
|
108
|
+
published: entry.published ?? false,
|
|
109
|
+
...generation
|
|
110
|
+
}) : SchemaTarget.make({
|
|
111
|
+
schema: entry.schema,
|
|
112
|
+
$id: hosted.idFor(current),
|
|
113
|
+
name,
|
|
114
|
+
path: file(current),
|
|
115
|
+
version: current,
|
|
116
|
+
published: entry.published ?? false,
|
|
117
|
+
...generation
|
|
118
|
+
});
|
|
119
|
+
const frozen = versions.filter((v) => v !== current).map((version) => ({
|
|
120
|
+
version,
|
|
121
|
+
path: file(version),
|
|
122
|
+
$id: hosted.idFor(version),
|
|
123
|
+
url: hosted.urlFor(version)
|
|
124
|
+
}));
|
|
125
|
+
const catalog = entry.catalog === void 0 ? void 0 : CatalogEntry.assemble({
|
|
126
|
+
name,
|
|
127
|
+
description: entry.catalog.description,
|
|
128
|
+
fileMatch: entry.catalog.fileMatch,
|
|
129
|
+
baseUrl: hosted.catalogBase,
|
|
130
|
+
layout: hosted.resolvedLayout,
|
|
131
|
+
appendVersion: hosted.resolvedAppendVersion,
|
|
132
|
+
...current !== void 0 ? {
|
|
133
|
+
versions,
|
|
134
|
+
current
|
|
135
|
+
} : {}
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
name,
|
|
139
|
+
target,
|
|
140
|
+
frozen,
|
|
141
|
+
drift: entry.drift ?? defaults.drift,
|
|
142
|
+
...catalog !== void 0 ? { catalog } : {}
|
|
143
|
+
};
|
|
40
144
|
};
|
|
41
145
|
const normalizePath = (raw) => {
|
|
42
146
|
const absolute = raw.startsWith("/");
|
|
@@ -52,11 +156,11 @@ const normalizePath = (raw) => {
|
|
|
52
156
|
}
|
|
53
157
|
return `${absolute ? "/" : ""}${out.join("/")}`;
|
|
54
158
|
};
|
|
55
|
-
const assertUniquePaths = (
|
|
159
|
+
const assertUniquePaths = (paths) => {
|
|
56
160
|
const seen = /* @__PURE__ */ new Set();
|
|
57
|
-
for (const p of
|
|
161
|
+
for (const p of paths) {
|
|
58
162
|
const normalized = normalizePath(p);
|
|
59
|
-
if (seen.has(normalized))
|
|
163
|
+
if (seen.has(normalized)) fail(`output path "${p}" is declared twice`);
|
|
60
164
|
seen.add(normalized);
|
|
61
165
|
}
|
|
62
166
|
};
|
|
@@ -64,49 +168,54 @@ const assertUniquePaths = (schemas, catalog) => {
|
|
|
64
168
|
* Validate and assemble a `schemastore.config.ts` value.
|
|
65
169
|
*
|
|
66
170
|
* @remarks
|
|
67
|
-
* Pure: no IO, no Effect.
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
171
|
+
* Pure: no IO, no Effect. `$id`, the write `path` and every catalog URL are
|
|
172
|
+
* derived from ONE layout (`outputDir`, `baseUrl` and `layout`) so they
|
|
173
|
+
* cannot disagree with each other. `versions` names every label a schema
|
|
174
|
+
* advertises; `current` (default: the newest under
|
|
175
|
+
* {@link SchemaVersioning.Order}) is the one generated at `target`, and every
|
|
176
|
+
* other label becomes a {@link FrozenVersion} the CLI verifies but does not
|
|
177
|
+
* regenerate. `baseUrl: "schemastore"` expands to
|
|
178
|
+
* {@link SCHEMASTORE_ID_BASE} for `$id` and {@link SCHEMASTORE_CATALOG_BASE}
|
|
179
|
+
* for the catalog URL, forcing the `"flat"` layout; any other `baseUrl` is
|
|
180
|
+
* used as one base for both, defaulting to the `"versioned"` layout.
|
|
181
|
+
*
|
|
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}.
|
|
76
199
|
*
|
|
77
200
|
* @public
|
|
78
201
|
*/
|
|
79
202
|
const defineConfig = (input) => {
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
config,
|
|
92
|
-
entry: CatalogEntry.assemble({
|
|
93
|
-
name: config.name,
|
|
94
|
-
description: config.description,
|
|
95
|
-
fileMatch: config.fileMatch,
|
|
96
|
-
baseUrl: config.baseUrl,
|
|
97
|
-
versions: found
|
|
98
|
-
})
|
|
99
|
-
};
|
|
100
|
-
});
|
|
101
|
-
assertUniquePaths(input.schemas, catalog.map((c) => c.config));
|
|
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");
|
|
206
|
+
const defaults = {
|
|
207
|
+
baseUrl: config.baseUrl,
|
|
208
|
+
drift: config.drift ?? DriftPolicy.defaults.policy
|
|
209
|
+
};
|
|
210
|
+
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]);
|
|
102
213
|
return {
|
|
103
214
|
[ConfigBrand]: true,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
onDrift: drift.onDrift ?? DriftPolicy.defaults.onDrift
|
|
109
|
-
}
|
|
215
|
+
outputDir,
|
|
216
|
+
onDrift: config.onDrift ?? DriftPolicy.defaults.onDrift,
|
|
217
|
+
catalogPath,
|
|
218
|
+
schemas
|
|
110
219
|
};
|
|
111
220
|
};
|
|
112
221
|
/**
|
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;
|