@kindgi/schema 0.1.1 → 0.1.3

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
@@ -15,6 +15,8 @@ Turn the JSON Schemas that live in `@kindgi/specs/*.schema.json` into runtime-us
15
15
  - `getSchema(id)` — retrieve the raw parsed schema JSON.
16
16
  - `ids()` — list every registered `$id`.
17
17
  - **`compileInlineSchema(schema)`** — compile one anonymous JSON Schema (no `$id`, no cross-schema `$ref`) into a `CompiledInlineSchema` whose `validate<T>(data)` returns `Result<T, ValidationErrorLike>`.
18
+ - **`compileJsonSchema(schema, options?)`** — compile one JSON Schema into an Ajv `ValidateFunction`, in the dialect it declares in `$schema` (draft-06, draft-07, 2019-09 or 2020-12; 2020-12 when it declares none), with formats checked. `dialects` limits the ones it may declare. `strict` (default `true`) is Ajv's strict mode, a lint for the schemas you write; turn it off for a schema from elsewhere, such as an MCP server's. `useDefaults` fills in each property's `default`. Throws for another dialect, or a schema that doesn't compile.
19
+ - **`jsonSchemaDialect(schema, allowed?)`** — the dialect a schema declares, as `'draft-06' | 'draft-07' | '2019-09' | '2020-12'`; throws, naming it, for one Kindgi doesn't validate (or one not in `allowed`).
18
20
  - **`versionOf(schema)`** — extract `{ major, semver }` from a schema's `$id` and `$comment`.
19
21
  - **Zod authoring helpers** — `AnySchema` (a JSON Schema object or a Zod v4 schema), `isZodSchema`, `isJsonSchemaObject`, `schemaKindOf`, and `toJSONSchema` / `toJSONSchemaSync` (with `loadZodConverter` / `loadZodConverterSync`), which convert Zod to JSON Schema through Zod's native `z.toJSONSchema()`. `zod` is an optional peer dependency: it is imported only when a Zod schema is passed, and a missing or failed conversion is an `invalid-zod-conversion` error, not a throw.
20
22
  - **`canonicalize(value)`** — deterministic JSON serialization (sorted keys, `undefined` omitted, no whitespace), a subset of RFC 8785 used for signing.
@@ -50,4 +52,4 @@ if (validated.kind === 'ok') {
50
52
  - **No Zod on the wire, and no JSON Schema → Zod conversion.** Zod is an authoring convenience: schemas are converted one way, Zod → JSON Schema, and every schema that is registered or exchanged is JSON Schema.
51
53
  - **No schema migration helpers.** Every schema is at its first major version, so there is nothing to migrate between.
52
54
  - **No TypeScript type generation.** Callers define TypeScript types that match schema shapes by hand, or author with Zod and derive them with `z.infer`.
53
- - **No custom Ajv extensions.** Uses stock Ajv Draft 2020-12 + `ajv-formats`, with no custom keywords.
55
+ - **No custom Ajv extensions.** Uses stock Ajv + `ajv-formats`, with no custom keywords. Kindgi's own schemas are Draft 2020-12; `compileJsonSchema` compiles a schema from elsewhere with Ajv's class for its declared dialect.
package/SPEC.md CHANGED
@@ -107,7 +107,7 @@ With Zod v4 as the workspace-wide version, `@kindgi/schema` exposes three helper
107
107
 
108
108
  - **No wire-level Zod.** Every artifact on the wire is a JSON Schema. Language-agnostic MCP + OpenAPI + Python/Go/Rust consumers must never see Zod.
109
109
  - **No TS type generation from JSON Schemas.** Callers maintain matching TS types by hand, or author with Zod and derive them via `z.infer<...>`.
110
- - **No custom Ajv keywords or extensions.** Stock Ajv Draft 2020-12 + `ajv-formats` only.
110
+ - **No custom Ajv keywords or extensions.** Stock Ajv + `ajv-formats` only: Draft 2020-12 for Kindgi's own schemas, and `compileJsonSchema` compiles a schema from elsewhere (an MCP server's) with Ajv's class for the dialect it declares.
111
111
  - **No schema migration between majors.** Not needed until any schema has a v2.
112
112
  - **No caching of loaded registries across calls.** Callers manage their own registry lifecycle.
113
113
 
@@ -0,0 +1,24 @@
1
+ import type { ValidateFunction } from 'ajv';
2
+ /** The JSON Schema dialects a schema may declare. */
3
+ export type JsonSchemaDialect = 'draft-06' | 'draft-07' | '2019-09' | '2020-12';
4
+ /**
5
+ * The dialect `schema` declares in `$schema` (2020-12 when it declares
6
+ * none). Throws for a dialect that isn't one of `allowed` (by default,
7
+ * every one Kindgi validates), naming it and them.
8
+ */
9
+ export declare function jsonSchemaDialect(schema: Readonly<Record<string, unknown>>, allowed?: readonly JsonSchemaDialect[]): JsonSchemaDialect;
10
+ export interface CompileJsonSchemaOptions {
11
+ /** The dialects the schema may declare. Default: every one Kindgi validates. */
12
+ readonly dialects?: readonly JsonSchemaDialect[];
13
+ /** Ajv's strict mode. Default `true`; `false` for a schema from elsewhere (see above). */
14
+ readonly strict?: boolean;
15
+ /** Fill in each property's `default` in the data validated (Ajv's `useDefaults`). */
16
+ readonly useDefaults?: boolean;
17
+ }
18
+ /**
19
+ * A validator for `schema`, compiled in its declared dialect. Throws when
20
+ * the dialect isn't supported (or allowed) or the schema doesn't compile,
21
+ * as `ajv.compile` does.
22
+ */
23
+ export declare function compileJsonSchema(schema: Readonly<Record<string, unknown>>, options?: CompileJsonSchemaOptions): ValidateFunction;
24
+ //# sourceMappingURL=dialect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAC;AAe5C,qDAAqD;AACrD,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAWhF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,GAAE,SAAS,iBAAiB,EAAiB,GACnD,iBAAiB,CAenB;AAOD,MAAM,WAAW,wBAAwB;IACvC,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACjD,0FAA0F;IAC1F,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,GAAE,wBAA6B,GACrC,gBAAgB,CAmBlB"}
@@ -0,0 +1,82 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ /**
4
+ * Compile a JSON Schema in the dialect it declares (`$schema`). Kindgi's
5
+ * own schemas are Draft 2020-12, the default when a schema declares none.
6
+ * A schema from elsewhere (an MCP server: the TypeScript MCP SDK emits
7
+ * draft-07) means its keywords as its dialect defines them: draft-07's
8
+ * array-form `items` is a tuple, 2020-12's is not. So each dialect gets
9
+ * Ajv's class for it, not 2020-12's with another meta-schema added.
10
+ *
11
+ * `strict` is Ajv's strict mode, an authoring lint (unknown keywords and
12
+ * formats, loose tuples, union types): right for the schemas a pack's
13
+ * author writes, wrong for a schema someone else's server sends, which is
14
+ * valid JSON Schema without being written to that standard.
15
+ */
16
+ import { createRequire } from 'node:module';
17
+ import { Ajv } from 'ajv';
18
+ import * as addFormatsModule from 'ajv-formats';
19
+ import { Ajv2019 } from 'ajv/dist/2019.js';
20
+ import { Ajv2020 } from 'ajv/dist/2020.js';
21
+ const addFormatsRaw = addFormatsModule;
22
+ const addFormats = typeof addFormatsRaw === 'function'
23
+ ? addFormatsRaw
24
+ : addFormatsRaw.default;
25
+ const require = createRequire(import.meta.url);
26
+ const DIALECTS = new Map([
27
+ ['http://json-schema.org/draft-06/schema', 'draft-06'],
28
+ ['http://json-schema.org/draft-07/schema', 'draft-07'],
29
+ ['https://json-schema.org/draft/2019-09/schema', '2019-09'],
30
+ ['https://json-schema.org/draft/2020-12/schema', '2020-12'],
31
+ ]);
32
+ const ALL_DIALECTS = [...DIALECTS.values()];
33
+ /**
34
+ * The dialect `schema` declares in `$schema` (2020-12 when it declares
35
+ * none). Throws for a dialect that isn't one of `allowed` (by default,
36
+ * every one Kindgi validates), naming it and them.
37
+ */
38
+ export function jsonSchemaDialect(schema, allowed = ALL_DIALECTS) {
39
+ const declared = schema.$schema;
40
+ const dialect = declared === undefined
41
+ ? '2020-12'
42
+ : typeof declared === 'string'
43
+ ? DIALECTS.get(declared.replace(/#$/, ''))
44
+ : undefined;
45
+ if (dialect === undefined || !allowed.includes(dialect)) {
46
+ const where = allowed.length < ALL_DIALECTS.length ? ' here' : '';
47
+ throw new Error(`The schema's $schema ${JSON.stringify(declared)} isn't a JSON Schema dialect Kindgi validates${where}: ${listOf(allowed)}.`);
48
+ }
49
+ return dialect;
50
+ }
51
+ /** `a`, `a or b`, `a, b or c`. */
52
+ function listOf(items) {
53
+ return items.length < 2 ? items.join('') : `${items.slice(0, -1).join(', ')} or ${items.at(-1)}`;
54
+ }
55
+ /**
56
+ * A validator for `schema`, compiled in its declared dialect. Throws when
57
+ * the dialect isn't supported (or allowed) or the schema doesn't compile,
58
+ * as `ajv.compile` does.
59
+ */
60
+ export function compileJsonSchema(schema, options = {}) {
61
+ const dialect = jsonSchemaDialect(schema, options.dialects);
62
+ const ajvOptions = {
63
+ strict: options.strict ?? true,
64
+ allErrors: true,
65
+ allowUnionTypes: false,
66
+ ...(options.useDefaults === true && { useDefaults: true }),
67
+ };
68
+ let ajv;
69
+ if (dialect === '2020-12')
70
+ ajv = new Ajv2020(ajvOptions);
71
+ else if (dialect === '2019-09')
72
+ ajv = new Ajv2019(ajvOptions);
73
+ else {
74
+ ajv = new Ajv(ajvOptions);
75
+ if (dialect === 'draft-06') {
76
+ ajv.addMetaSchema(require('ajv/dist/refs/json-schema-draft-06.json'));
77
+ }
78
+ }
79
+ addFormats(ajv);
80
+ return ajv.compile(schema);
81
+ }
82
+ //# sourceMappingURL=dialect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect.js","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { canonicalize } from './canonical.js';
2
2
  export type { InvalidZodConversionError, SchemaCompileError, SchemaError, SchemaLoadError, SchemaNotFoundError, SchemaParseError, ValidationError, } from './errors.js';
3
3
  export { compileInlineSchema, createSpecRegistry, loadSpecRegistry } from './registry.js';
4
+ export { compileJsonSchema, jsonSchemaDialect } from './dialect.js';
5
+ export type { CompileJsonSchemaOptions, JsonSchemaDialect } from './dialect.js';
4
6
  export type { CompiledInlineSchema, SpecRegistry, ValidationErrorLike } from './registry.js';
5
7
  export { versionOf } from './version.js';
6
8
  export type { SchemaVersion } from './version.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EACV,yBAAyB,EACzB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC1F,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,UAAU,EACV,YAAY,EACZ,aAAa,GACd,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EACV,yBAAyB,EACzB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACpE,YAAY,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChF,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,UAAU,EACV,YAAY,EACZ,aAAa,GACd,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // Copyright (C) 2026 Kindgi Inc.
3
3
  export { canonicalize } from './canonical.js';
4
4
  export { compileInlineSchema, createSpecRegistry, loadSpecRegistry } from './registry.js';
5
+ export { compileJsonSchema, jsonSchemaDialect } from './dialect.js';
5
6
  export { versionOf } from './version.js';
6
7
  export { isJsonSchemaObject, isZodSchema, loadZodConverter, loadZodConverterSync, parseWithSchema, schemaKindOf, toJSONSchema, toJSONSchemaSync, } from './zod.js';
7
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAU9C,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE1F,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAU9C,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,UAAU,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kindgi/schema",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "JSON Schema utilities for Kindgi. Loads specs, provides Ajv-based validation, extracts schema versions. Consumers validate incoming data against the wire contracts in @kindgi/specs.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -18,7 +18,8 @@
18
18
  "exports": {
19
19
  ".": {
20
20
  "types": "./dist/index.d.ts",
21
- "import": "./dist/index.js"
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
22
23
  }
23
24
  },
24
25
  "files": [
@@ -29,7 +30,7 @@
29
30
  "type-manifest.json"
30
31
  ],
31
32
  "dependencies": {
32
- "@kindgi/types": "0.1.1",
33
+ "@kindgi/types": "0.1.3",
33
34
  "ajv": "^8.17.1",
34
35
  "ajv-formats": "^3.0.1"
35
36
  },
@@ -42,14 +43,14 @@
42
43
  }
43
44
  },
44
45
  "devDependencies": {
45
- "@kindgi/specs": "0.1.1",
46
+ "@kindgi/specs": "0.1.3",
46
47
  "@types/node": "^22.10.5",
47
48
  "typescript": "^5.7.3",
48
49
  "vitest": "^2.1.8",
49
50
  "zod": "^4.6.5"
50
51
  },
51
52
  "engines": {
52
- "node": ">=22.0.0"
53
+ "node": ">=22.12.0"
53
54
  },
54
55
  "publishConfig": {
55
56
  "access": "public",
package/src/dialect.ts ADDED
@@ -0,0 +1,114 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ /**
5
+ * Compile a JSON Schema in the dialect it declares (`$schema`). Kindgi's
6
+ * own schemas are Draft 2020-12, the default when a schema declares none.
7
+ * A schema from elsewhere (an MCP server: the TypeScript MCP SDK emits
8
+ * draft-07) means its keywords as its dialect defines them: draft-07's
9
+ * array-form `items` is a tuple, 2020-12's is not. So each dialect gets
10
+ * Ajv's class for it, not 2020-12's with another meta-schema added.
11
+ *
12
+ * `strict` is Ajv's strict mode, an authoring lint (unknown keywords and
13
+ * formats, loose tuples, union types): right for the schemas a pack's
14
+ * author writes, wrong for a schema someone else's server sends, which is
15
+ * valid JSON Schema without being written to that standard.
16
+ */
17
+
18
+ import { createRequire } from 'node:module';
19
+
20
+ import { Ajv } from 'ajv';
21
+ import type { ValidateFunction } from 'ajv';
22
+ import * as addFormatsModule from 'ajv-formats';
23
+ import { Ajv2019 } from 'ajv/dist/2019.js';
24
+ import { Ajv2020 } from 'ajv/dist/2020.js';
25
+
26
+ type AnyAjv = Ajv | Ajv2019 | Ajv2020;
27
+ type AddFormatsFn = (ajv: AnyAjv, opts?: unknown) => unknown;
28
+ const addFormatsRaw = addFormatsModule as unknown;
29
+ const addFormats: AddFormatsFn =
30
+ typeof addFormatsRaw === 'function'
31
+ ? (addFormatsRaw as AddFormatsFn)
32
+ : (addFormatsRaw as { default: AddFormatsFn }).default;
33
+
34
+ const require = createRequire(import.meta.url);
35
+
36
+ /** The JSON Schema dialects a schema may declare. */
37
+ export type JsonSchemaDialect = 'draft-06' | 'draft-07' | '2019-09' | '2020-12';
38
+
39
+ const DIALECTS: ReadonlyMap<string, JsonSchemaDialect> = new Map<string, JsonSchemaDialect>([
40
+ ['http://json-schema.org/draft-06/schema', 'draft-06'],
41
+ ['http://json-schema.org/draft-07/schema', 'draft-07'],
42
+ ['https://json-schema.org/draft/2019-09/schema', '2019-09'],
43
+ ['https://json-schema.org/draft/2020-12/schema', '2020-12'],
44
+ ]);
45
+
46
+ const ALL_DIALECTS: readonly JsonSchemaDialect[] = [...DIALECTS.values()];
47
+
48
+ /**
49
+ * The dialect `schema` declares in `$schema` (2020-12 when it declares
50
+ * none). Throws for a dialect that isn't one of `allowed` (by default,
51
+ * every one Kindgi validates), naming it and them.
52
+ */
53
+ export function jsonSchemaDialect(
54
+ schema: Readonly<Record<string, unknown>>,
55
+ allowed: readonly JsonSchemaDialect[] = ALL_DIALECTS,
56
+ ): JsonSchemaDialect {
57
+ const declared = schema.$schema;
58
+ const dialect =
59
+ declared === undefined
60
+ ? '2020-12'
61
+ : typeof declared === 'string'
62
+ ? DIALECTS.get(declared.replace(/#$/, ''))
63
+ : undefined;
64
+ if (dialect === undefined || !allowed.includes(dialect)) {
65
+ const where = allowed.length < ALL_DIALECTS.length ? ' here' : '';
66
+ throw new Error(
67
+ `The schema's $schema ${JSON.stringify(declared)} isn't a JSON Schema dialect Kindgi validates${where}: ${listOf(allowed)}.`,
68
+ );
69
+ }
70
+ return dialect;
71
+ }
72
+
73
+ /** `a`, `a or b`, `a, b or c`. */
74
+ function listOf(items: readonly string[]): string {
75
+ return items.length < 2 ? items.join('') : `${items.slice(0, -1).join(', ')} or ${items.at(-1)}`;
76
+ }
77
+
78
+ export interface CompileJsonSchemaOptions {
79
+ /** The dialects the schema may declare. Default: every one Kindgi validates. */
80
+ readonly dialects?: readonly JsonSchemaDialect[];
81
+ /** Ajv's strict mode. Default `true`; `false` for a schema from elsewhere (see above). */
82
+ readonly strict?: boolean;
83
+ /** Fill in each property's `default` in the data validated (Ajv's `useDefaults`). */
84
+ readonly useDefaults?: boolean;
85
+ }
86
+
87
+ /**
88
+ * A validator for `schema`, compiled in its declared dialect. Throws when
89
+ * the dialect isn't supported (or allowed) or the schema doesn't compile,
90
+ * as `ajv.compile` does.
91
+ */
92
+ export function compileJsonSchema(
93
+ schema: Readonly<Record<string, unknown>>,
94
+ options: CompileJsonSchemaOptions = {},
95
+ ): ValidateFunction {
96
+ const dialect = jsonSchemaDialect(schema, options.dialects);
97
+ const ajvOptions = {
98
+ strict: options.strict ?? true,
99
+ allErrors: true,
100
+ allowUnionTypes: false,
101
+ ...(options.useDefaults === true && { useDefaults: true }),
102
+ };
103
+ let ajv: AnyAjv;
104
+ if (dialect === '2020-12') ajv = new Ajv2020(ajvOptions);
105
+ else if (dialect === '2019-09') ajv = new Ajv2019(ajvOptions);
106
+ else {
107
+ ajv = new Ajv(ajvOptions);
108
+ if (dialect === 'draft-06') {
109
+ ajv.addMetaSchema(require('ajv/dist/refs/json-schema-draft-06.json'));
110
+ }
111
+ }
112
+ addFormats(ajv);
113
+ return ajv.compile(schema as object);
114
+ }
package/src/index.ts CHANGED
@@ -12,6 +12,8 @@ export type {
12
12
  ValidationError,
13
13
  } from './errors.js';
14
14
  export { compileInlineSchema, createSpecRegistry, loadSpecRegistry } from './registry.js';
15
+ export { compileJsonSchema, jsonSchemaDialect } from './dialect.js';
16
+ export type { CompileJsonSchemaOptions, JsonSchemaDialect } from './dialect.js';
15
17
  export type { CompiledInlineSchema, SpecRegistry, ValidationErrorLike } from './registry.js';
16
18
  export { versionOf } from './version.js';
17
19
  export type { SchemaVersion } from './version.js';