@darksheep/json-schema 0.0.0 โ†’ 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1](https://github.com/DarkSheepSoftware/node-packages/compare/json-schema-v0.1.0...json-schema-v0.1.1) (2025-10-09)
4
+
5
+
6
+ ### ๐ŸŒŸ Features
7
+
8
+ * Support node 24 ([#761](https://github.com/DarkSheepSoftware/node-packages/issues/761)) ([f13caae](https://github.com/DarkSheepSoftware/node-packages/commit/f13caaef978c7a657e4599f775e0a62d1738f475))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @darksheep/errors bumped to 1.2.0
16
+
17
+ ## 0.1.0 (2025-01-28)
18
+
19
+
20
+ ### ๐ŸŒŸ Features
21
+
22
+ * **json-schema:** Extract schema tool to a package ([292c5a0](https://github.com/DarkSheepSoftware/node-packages/commit/292c5a0c4cfd1b9e82bd179db76c47aa979480bb))
23
+ * Refactor json-schema and add tests. ([5f9669b](https://github.com/DarkSheepSoftware/node-packages/commit/5f9669baddc0d31d1faa5c26c319ec664c2c746b))
24
+
25
+
26
+ ### ๐Ÿงน Chores
27
+
28
+ * Install ajv-formats ([9ce1702](https://github.com/DarkSheepSoftware/node-packages/commit/9ce170223b8e9dd16cae4a019170d90999589d45))
package/README.md CHANGED
@@ -1,63 +1,65 @@
1
- # @darksheep/package.json
2
- Fetch the package.json of either; the executing program, or a given package by name.
3
- This is primarily useful when you're making a package, and you need to find the package.json for the service that is executing your package.
4
- When using pnpm you need to be cautious to set the root directory correctly.
5
-
6
- ## Examples
7
- ### Common JS
8
- ```js
9
- const { getPackageJson } = require('@darksheep/package.json');
10
- (async () => {
11
- const { name, version } = await getPackageJson();
12
-
13
- console.info({ name, version });
14
- })()
15
- ```
16
-
17
- ### Module JS
18
- ```js
19
- import { getPackageJson } from '@darksheep/package.json';
20
- const { name, version } = await getPackageJson();
1
+ # @darksheep/json-schema
2
+ Validate your unkown json types in style!
21
3
 
22
- console.info({ name, version });
23
- ```
4
+ ## Example
24
5
 
25
- ### Sync API
26
- This api is only useful for commonjs where top level await is not available.
27
- If you intend on using this inside a module file, please reconsider!
6
+ ```ts
7
+ import { createValidator } from '@darksheep/json-schema';
28
8
 
29
- ```js
30
- const { getPackageJson } = require('@darksheep/package.json/sync');
31
- const { name, version } = getPackageJson();
9
+ const validate = createValidator({
10
+ type: 'object',
11
+ required: [ 'name', 'updatedAt' ],
12
+ properties: {
13
+ name: { type: 'string' },
14
+ description: { type: 'string' },
15
+ // Defaults are supported!
16
+ stared: {
17
+ type: 'boolean',
18
+ default: false,
19
+ },
20
+ // Formats are supported!
21
+ updatedAt: {
22
+ type: 'string',
23
+ format: 'date-time',
24
+ },
25
+ },
26
+ });
32
27
 
33
- console.info({ name, version });
28
+ // typeof validate is equal to: (
29
+ // data: {
30
+ // description?: string;
31
+ // stared?: boolean;
32
+ // name: string;
33
+ // updatedAt: string;
34
+ // }
35
+ // ) => Promise<{
36
+ // description?: string;
37
+ // name: string;
38
+ // updatedAt: string;
39
+ // stared: boolean;
40
+ // }>
34
41
  ```
35
42
 
36
- ## Detailed Docs
37
- ### Async (`@darksheep/package.json`)
38
- ```js
39
- /** @typedef {import('@darksheep/package.json').PackageJson} PackageJson */
40
-
41
- /**
42
- * Attempt to find the package.json for {packageName}
43
- *
44
- * @param {string} [packageName] The name of the npm package (the main package if blank)
45
- * @param {string | URL} [root] an optional root url to start from
46
- * @returns {Promise<PackageJson | null>}
47
- */
48
- export function getPackageJson(packageName, root);
49
- ```
43
+ ### Example with untrusted input
44
+
45
+ ```ts
46
+ import { createValidator, defineSchema } from '@darksheep/json-schema';
47
+
48
+ const TransferRequestSchema = defineSchema({
49
+ type: 'object',
50
+ required: [ 'source', 'destination', 'amount' ],
51
+ unevaluatedProperties: false,
52
+ properties: {
53
+ source: { type: 'string' },
54
+ destination: { type: 'string' },
55
+ amount: { type: 'integer' },
56
+ }
57
+ })
50
58
 
51
- ### Sync (`@darksheep/package.json/sync`)
52
- ```js
53
- /** @typedef {import('@darksheep/package.json').PackageJson} PackageJson */
54
-
55
- /**
56
- * Attempt to find the package.json for {packageName} synchronously
57
- *
58
- * @param {string} [packageName] The name of the npm package (the main package if blank)
59
- * @param {string | URL} [root] an optional root file url to start from
60
- * @returns {PackageJson | null}
61
- */
62
- export function getPackageJson(packageName, root);
59
+ const validateTransferRequest = createValidator(
60
+ TransferRequestSchema,
61
+ { allowUnknown: true },
62
+ )
63
+ // The `typeof validateTransferRequest` is:
64
+ // (data: unknown) => Promise<{ source: string; destination: string; amount: number }>
63
65
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksheep/json-schema",
3
- "version": "0.0.0",
3
+ "version": "0.1.1",
4
4
  "description": "JSON schema validation (with types)",
5
5
  "license": "UNLICENCED",
6
6
  "type": "module",
@@ -17,15 +17,16 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "prepack": "yarn dlx -q -p typescript tsc",
20
- "test": "node --test src/**/*.test.js",
21
- "test:watch": "node --watch --test src/**/*.test.js"
20
+ "test": "node --test --experimental-strip-types src/**/*.test.{ts,js}",
21
+ "test:watch": "node --test --watch --experimental-strip-types src/**/*.test.{ts,js}"
22
22
  },
23
23
  "dependencies": {
24
24
  "@darksheep/errors": "",
25
25
  "ajv": "8.17.1",
26
+ "ajv-formats": "3.0.1",
26
27
  "json-schema-to-ts": "3.1.1"
27
28
  },
28
29
  "engines": {
29
- "node": "^20.13.1 || ^22.2.0"
30
+ "node": "^20.13.1 || >=22.2.0"
30
31
  }
31
32
  }
package/src/async.js ADDED
@@ -0,0 +1,150 @@
1
+ import { Ajv2019 as Ajv } from 'ajv/dist/2019.js';
2
+ import AddFormats from 'ajv-formats';
3
+ import { isSchemaError } from './errors.js';
4
+
5
+ /**
6
+ * @import {
7
+ * AsyncSchema,
8
+ * AsyncValidateFunction,
9
+ * } from 'ajv';
10
+ * @import {
11
+ * FromSchema,
12
+ * JSONSchema,
13
+ * TargetError,
14
+ * } from './types.js';
15
+ */
16
+
17
+ /**
18
+ * @typedef {(
19
+ * & { schema: JSONSchema }
20
+ * )} ToJsonOptions
21
+ */
22
+
23
+ const ajv = new Ajv({
24
+ allErrors: true,
25
+ verbose: false,
26
+ allowUnionTypes: false,
27
+ multipleOfPrecision: 5,
28
+ ownProperties: true,
29
+ strict: true,
30
+ useDefaults: true,
31
+ });
32
+
33
+ AddFormats.default(ajv);
34
+
35
+ /**
36
+ * @type {WeakMap<JSONSchema, AsyncValidateFunction>}
37
+ */
38
+ const validatorSchema = new WeakMap();
39
+
40
+ /**
41
+ * @param {JSONSchema} schema - The original JSONSchema.
42
+ * @param {{ meta?: boolean | undefined }} [options] - The options for ajv compile.
43
+ * @returns {AsyncValidateFunction}
44
+ */
45
+ export function getValidator(schema, options) {
46
+ const Compiled = validatorSchema.get(schema);
47
+ if (Compiled != null) {
48
+ return Compiled;
49
+ }
50
+
51
+ const validator = ajv.compile(
52
+ /** @type {AsyncSchema} */ ({
53
+ ...schema,
54
+ $async: true,
55
+ }),
56
+ options?.meta,
57
+ );
58
+ validatorSchema.set(schema, validator);
59
+
60
+ return validator;
61
+ }
62
+
63
+ /**
64
+ * @template {JSONSchema} const Schema - The raw schema type.
65
+ * @template {boolean} [AllowUnknown=false]
66
+ * @template {boolean} [ParseNot=false]
67
+ * @template {boolean} [ParseIfThenElse=false]
68
+ * @template [DataOptional=FromSchema<Schema, {
69
+ * keepDefaultedPropertiesOptional: true,
70
+ * parseIfThenElseKeywords: ParseIfThenElse,
71
+ * parseNotKeyword: ParseNot,
72
+ * }>].
73
+ * @template [DataDefaults=FromSchema<Schema, {
74
+ * keepDefaultedPropertiesOptional: false,
75
+ * parseIfThenElseKeywords: ParseIfThenElse,
76
+ * parseNotKeyword: ParseNot,
77
+ * }>].
78
+ * @param {Schema} schema - The request body json schema.
79
+ * @param {{
80
+ * meta?: boolean | undefined,
81
+ * allowUnknown?: AllowUnknown | undefined,
82
+ * parseIfThenElseKeywords?: ParseIfThenElse | undefined,
83
+ * parseNotKeyword?: ParseNot | undefined,
84
+ * }} [options] - The request body json schema.
85
+ * @param {TargetError | undefined} [ParentThrownError] - The error type.
86
+ * @returns {(
87
+ * data: AllowUnknown extends true ? unknown : DataOptional,
88
+ * TargetError?: TargetError | undefined
89
+ * ) => Promise<DataDefaults>}
90
+ */
91
+ export function createValidator(schema, options, ParentThrownError) {
92
+ /** @type {AsyncValidateFunction<DataDefaults>} */
93
+ const validator = getValidator(schema, options);
94
+
95
+ /**
96
+ * @param {unknown} data - The raw data.
97
+ * @param {TargetError | undefined} [ThrownError] - The error type.
98
+ * @returns {Promise<DataDefaults>}
99
+ */
100
+ return async (data, ThrownError = ParentThrownError) => {
101
+ try {
102
+ return await validator(data);
103
+ } catch (error) {
104
+ if (
105
+ ThrownError == null ||
106
+ isSchemaError(error) === false
107
+ ) {
108
+ throw error;
109
+ }
110
+
111
+ throw new ThrownError(
112
+ { cause: error, errors: error.errors },
113
+ );
114
+ }
115
+ };
116
+ }
117
+
118
+ /**
119
+ * @template {JSONSchema} const Schema - The raw schema type.
120
+ * @template {boolean} [ParseNot=false]
121
+ * @template {boolean} [ParseIfThenElse=false]
122
+ * @template [DataDefaults=FromSchema<Schema, {
123
+ * keepDefaultedPropertiesOptional: false,
124
+ * parseIfThenElseKeywords: ParseIfThenElse,
125
+ * parseNotKeyword: ParseNot,
126
+ * }>].
127
+ * @param {Schema} schema - The request body json schema.
128
+ * @param {unknown} data - The data to validate.
129
+ * @param {TargetError | undefined} [ThrownError] - The error type.
130
+ * @returns {Promise<DataDefaults>}
131
+ */
132
+ export async function validate(schema, data, ThrownError) {
133
+ /** @type {AsyncValidateFunction<DataDefaults>} */
134
+ const validator = getValidator(schema);
135
+
136
+ try {
137
+ return await validator(data);
138
+ } catch (error) {
139
+ if (
140
+ ThrownError == null ||
141
+ isSchemaError(error) === false
142
+ ) {
143
+ throw error;
144
+ }
145
+
146
+ throw new ThrownError(
147
+ { cause: error, errors: error.errors },
148
+ );
149
+ }
150
+ }
package/src/errors.js CHANGED
@@ -1,26 +1,31 @@
1
+ /**
2
+ * @import { DefinedError } from 'ajv'
3
+ * @import { ErrorOptions } from '@darksheep/errors'
4
+ */
5
+
6
+ import { ValidationError } from 'ajv';
1
7
  import { BadRequest, InternalServerError } from '@darksheep/errors';
2
8
 
3
9
  /**
4
10
  * @typedef {(
5
- * & import('@darksheep/errors').ErrorOptions
6
- * & { errors: import('ajv').DefinedError[] }
7
- * )} ErrorOptions
11
+ * & ErrorOptions
12
+ * & { errors: DefinedError[] }
13
+ * )} SchemaErrorOptions
8
14
  */
9
15
 
10
16
  /**
11
17
  * The server will not process the request body not matching the expected request schema.
12
18
  */
13
19
  export class RequestSchemaError extends BadRequest {
14
- /** @type {import('ajv').DefinedError[]} */
20
+ /** @type {DefinedError[]} */
15
21
  errors = [];
16
22
  name = 'RequestSchemaError';
17
23
 
18
24
  /**
19
- * @param {string} message - The custom message for the http error.
20
- * @param {ErrorOptions} options - The custom message for the http error.
25
+ * @param {SchemaErrorOptions} options - The custom message for the http error.
21
26
  */
22
- constructor(message, { errors, ...options }) {
23
- super(message, options);
27
+ constructor({ errors, ...options }) {
28
+ super('Request.body does not match Schema', options);
24
29
 
25
30
  this.errors = errors;
26
31
  }
@@ -30,17 +35,84 @@ export class RequestSchemaError extends BadRequest {
30
35
  * The server tried sending a response that did not match the expected response schema.
31
36
  */
32
37
  export class ResponseSchemaError extends InternalServerError {
33
- /** @type {import('ajv').DefinedError[]} */
38
+ /** @type {DefinedError[]} */
34
39
  errors = [];
35
40
  name = 'ResponseSchemaError';
36
41
 
37
42
  /**
38
- * @param {string} message - The custom message for the http error.
39
- * @param {ErrorOptions} options - The custom message for the http error.
43
+ * @param {SchemaErrorOptions} options - The custom message for the http error.
40
44
  */
41
- constructor(message, { errors, ...options }) {
42
- super(message, options);
45
+ constructor({ errors, ...options }) {
46
+ super('Response.body does not match Schema', options);
43
47
 
44
48
  this.errors = errors;
45
49
  }
46
50
  }
51
+
52
+ /** @type {Set<string>} */
53
+ const keywords = new Set(
54
+ /** @type {DefinedError['keyword'][]} */ ([
55
+ 'false schema',
56
+ 'anyOf',
57
+ 'not',
58
+ 'type',
59
+ 'additionalItems',
60
+ 'items',
61
+ 'contains',
62
+ 'additionalProperties',
63
+ 'dependencies',
64
+ 'if',
65
+ 'oneOf',
66
+ 'propertyNames',
67
+ 'maxItems',
68
+ 'minItems',
69
+ 'maxProperties',
70
+ 'minProperties',
71
+ 'minLength',
72
+ 'maxLength',
73
+ 'maximum',
74
+ 'minimum',
75
+ 'exclusiveMaximum',
76
+ 'exclusiveMinimum',
77
+ 'multipleOf',
78
+ 'pattern',
79
+ 'required',
80
+ 'uniqueItems',
81
+ 'const',
82
+ 'enum',
83
+ 'format',
84
+ 'unevaluatedProperties',
85
+ 'unevaluatedItems',
86
+ 'dependentRequired',
87
+ 'discriminator',
88
+ ]),
89
+ );
90
+
91
+ /**
92
+ * @param {unknown} input - The value to check.
93
+ * @returns {input is DefinedError}
94
+ */
95
+ export function isDefinedError(input) {
96
+ return (
97
+ input != null &&
98
+ typeof input === 'object' &&
99
+ 'keyword' in input &&
100
+ typeof input.keyword === 'string' &&
101
+ keywords.has(input.keyword) &&
102
+ 'instancePath' in input &&
103
+ typeof input.instancePath === 'string' &&
104
+ 'schemaPath' in input &&
105
+ typeof input.schemaPath === 'string'
106
+ );
107
+ }
108
+
109
+ /**
110
+ * @param {unknown} input - The value to check.
111
+ * @returns {input is import('./types.js').ValidationError}
112
+ */
113
+ export function isSchemaError(input) {
114
+ return (
115
+ input instanceof ValidationError &&
116
+ input.errors.every(isDefinedError)
117
+ );
118
+ }
package/src/index.js CHANGED
@@ -1,18 +1,7 @@
1
- /**
2
- * @typedef {import('ajv').DefinedError} DefinedError
3
- * @typedef {Exclude<import('json-schema-to-ts').JSONSchema, boolean>} JSONSchema
4
- * @typedef {import('json-schema-to-ts').FromSchemaOptions} FromSchemaOptions
5
- * @typedef {import('json-schema-to-ts').FromSchemaDefaultOptions} FromSchemaDefaultOptions
6
- */
1
+ export * from './types.js';
7
2
 
8
3
  /**
9
- * @template {JSONSchema} Schema
10
- * @template {FromSchemaOptions} [Options=FromSchemaDefaultOptions]
11
- * @typedef {import('json-schema-to-ts').FromSchema<Schema, Options>} FromSchema
12
- */
13
-
14
- /**
15
- * @template {JSONSchema} const Schema - The raw schema type.
4
+ * @template {import('./types.js').JSONSchema} const Schema - The raw schema type.
16
5
  * @param {Schema} schema - The raw JSON schema.
17
6
  * @returns {Schema}
18
7
  */
@@ -21,12 +10,18 @@ export function defineSchema(schema) {
21
10
  }
22
11
 
23
12
  export {
13
+ isDefinedError,
14
+ isSchemaError,
24
15
  RequestSchemaError,
25
16
  ResponseSchemaError,
26
17
  } from './errors.js';
27
18
 
28
19
  export {
29
- validateData,
30
- validateRequestBody,
31
- validateResponseBody,
32
- } from './validator.js';
20
+ createValidator,
21
+ validate,
22
+ } from './async.js';
23
+
24
+ export {
25
+ createAssertSchema,
26
+ assertSchema,
27
+ } from './sync.js';
package/src/sync.js ADDED
@@ -0,0 +1,112 @@
1
+ import { Ajv2019 as Ajv, ValidationError } from 'ajv/dist/2019.js';
2
+ import AddFormats from 'ajv-formats';
3
+ import { isDefinedError } from './errors.js';
4
+
5
+ /**
6
+ * @import {
7
+ * SchemaObject,
8
+ * ValidateFunction
9
+ * } from 'ajv'
10
+ * @import {
11
+ * FromSchema,
12
+ * JSONSchema,
13
+ * TargetError,
14
+ * } from './types.js'
15
+ */
16
+
17
+ /**
18
+ * @typedef {(
19
+ * & { schema: JSONSchema }
20
+ * )} ToJsonOptions
21
+ */
22
+
23
+ const ajv = new Ajv({
24
+ allErrors: true,
25
+ verbose: false,
26
+ allowUnionTypes: false,
27
+ multipleOfPrecision: 5,
28
+ ownProperties: true,
29
+ strict: true,
30
+ useDefaults: false,
31
+ });
32
+
33
+ AddFormats.default(ajv);
34
+
35
+ /**
36
+ * @type {WeakMap<JSONSchema, ValidateFunction>}
37
+ */
38
+ const enforcerSchema = new WeakMap();
39
+
40
+ /**
41
+ * @param {JSONSchema} schema - The original JSONSchema.
42
+ * @returns {ValidateFunction}
43
+ */
44
+ export function getValidator(schema) {
45
+ const Compiled = enforcerSchema.get(schema);
46
+ if (Compiled != null) {
47
+ return Compiled;
48
+ }
49
+
50
+ const enforcer = ajv.compile(
51
+ /** @type {SchemaObject} */ ({
52
+ ...schema,
53
+ $async: false,
54
+ }),
55
+ );
56
+ enforcerSchema.set(schema, enforcer);
57
+
58
+ return enforcer;
59
+ }
60
+
61
+ /**
62
+ * @template {JSONSchema} const Schema - The raw schema type.
63
+ * @template [Data=FromSchema<Schema>]
64
+ * @param {Schema} schema - The request body json schema.
65
+ * @returns {(data: unknown, TargetError?: TargetError | undefined) => asserts data is Data}
66
+ */
67
+ export function createAssertSchema(schema) {
68
+ /** @type {ValidateFunction<Data>} */
69
+ const validator = getValidator(schema);
70
+
71
+ /** @type {(data: unknown, TargetError?: TargetError | undefined) => asserts data is Data} */
72
+ return (data, TargetError) => {
73
+ if (validator(data)) {
74
+ return;
75
+ }
76
+
77
+ const errors = validator.errors
78
+ ?.filter(isDefinedError) ?? [];
79
+
80
+ if (TargetError == null) {
81
+ throw new ValidationError(errors);
82
+ }
83
+
84
+ throw new TargetError({ errors });
85
+ };
86
+ }
87
+
88
+ /**
89
+ * @template {JSONSchema} const Schema - The raw schema type.
90
+ * @template [Data=FromSchema<Schema>]
91
+ * @param {Schema} schema - The request body json schema.
92
+ * @param {unknown} data - The raw data.
93
+ * @param {TargetError | undefined} [TargetError] - The error type.
94
+ * @returns {asserts data is Data}
95
+ */
96
+ export function assertSchema(schema, data, TargetError) {
97
+ /** @type {ValidateFunction<Data>} */
98
+ const validator = getValidator(schema);
99
+
100
+ if (validator(data)) {
101
+ return;
102
+ }
103
+
104
+ const errors = validator.errors
105
+ ?.filter(isDefinedError) ?? [];
106
+
107
+ if (TargetError == null) {
108
+ throw new ValidationError(errors);
109
+ }
110
+
111
+ throw new TargetError({ errors });
112
+ }
package/src/types.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @import { DefinedError } from 'ajv'
3
+ */
4
+ export {};
5
+
6
+ /**
7
+ * @typedef {(
8
+ * & Omit<import('ajv').ValidationError, 'errors'>
9
+ * & { errors: DefinedError[] }
10
+ * )} ValidationError
11
+ */
12
+
13
+ /**
14
+ * @typedef {new (options: { cause?: Error, errors: DefinedError[] }) => Error} TargetError
15
+ */
16
+ /**
17
+ * @typedef {Exclude<import('json-schema-to-ts').JSONSchema, boolean>} JSONSchema
18
+ */
19
+
20
+ /**
21
+ * @typedef {import('json-schema-to-ts').FromSchemaOptions} FromSchemaOptions
22
+ */
23
+
24
+ /**
25
+ * @typedef {import('json-schema-to-ts').FromSchemaDefaultOptions} FromSchemaDefaultOptions
26
+ */
27
+
28
+ /**
29
+ * @template {JSONSchema} Schema - The raw schema type.
30
+ * @template {FromSchemaOptions} [Options=FromSchemaDefaultOptions] - The schema typing options.
31
+ * @typedef {import('json-schema-to-ts').FromSchema<Schema, Options>} FromSchema
32
+ */
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @param {JSONSchema} schema - The original JSONSchema.
3
+ * @param {{ meta?: boolean | undefined }} [options] - The options for ajv compile.
4
+ * @returns {AsyncValidateFunction}
5
+ */
6
+ export function getValidator(schema: JSONSchema, options?: {
7
+ meta?: boolean | undefined;
8
+ }): AsyncValidateFunction;
9
+ /**
10
+ * @template {JSONSchema} const Schema - The raw schema type.
11
+ * @template {boolean} [AllowUnknown=false]
12
+ * @template {boolean} [ParseNot=false]
13
+ * @template {boolean} [ParseIfThenElse=false]
14
+ * @template [DataOptional=FromSchema<Schema, {
15
+ * keepDefaultedPropertiesOptional: true,
16
+ * parseIfThenElseKeywords: ParseIfThenElse,
17
+ * parseNotKeyword: ParseNot,
18
+ * }>].
19
+ * @template [DataDefaults=FromSchema<Schema, {
20
+ * keepDefaultedPropertiesOptional: false,
21
+ * parseIfThenElseKeywords: ParseIfThenElse,
22
+ * parseNotKeyword: ParseNot,
23
+ * }>].
24
+ * @param {Schema} schema - The request body json schema.
25
+ * @param {{
26
+ * meta?: boolean | undefined,
27
+ * allowUnknown?: AllowUnknown | undefined,
28
+ * parseIfThenElseKeywords?: ParseIfThenElse | undefined,
29
+ * parseNotKeyword?: ParseNot | undefined,
30
+ * }} [options] - The request body json schema.
31
+ * @param {TargetError | undefined} [ParentThrownError] - The error type.
32
+ * @returns {(
33
+ * data: AllowUnknown extends true ? unknown : DataOptional,
34
+ * TargetError?: TargetError | undefined
35
+ * ) => Promise<DataDefaults>}
36
+ */
37
+ export function createValidator<const Schema extends JSONSchema, AllowUnknown extends boolean = false, ParseNot extends boolean = false, ParseIfThenElse extends boolean = false, DataOptional = FromSchema<Schema, {
38
+ keepDefaultedPropertiesOptional: true;
39
+ parseIfThenElseKeywords: ParseIfThenElse;
40
+ parseNotKeyword: ParseNot;
41
+ }>, DataDefaults = FromSchema<Schema, {
42
+ keepDefaultedPropertiesOptional: false;
43
+ parseIfThenElseKeywords: ParseIfThenElse;
44
+ parseNotKeyword: ParseNot;
45
+ }>>(schema: Schema, options?: {
46
+ meta?: boolean | undefined;
47
+ allowUnknown?: AllowUnknown | undefined;
48
+ parseIfThenElseKeywords?: ParseIfThenElse | undefined;
49
+ parseNotKeyword?: ParseNot | undefined;
50
+ }, ParentThrownError?: TargetError | undefined): (data: AllowUnknown extends true ? unknown : DataOptional, TargetError?: TargetError | undefined) => Promise<DataDefaults>;
51
+ /**
52
+ * @template {JSONSchema} const Schema - The raw schema type.
53
+ * @template {boolean} [ParseNot=false]
54
+ * @template {boolean} [ParseIfThenElse=false]
55
+ * @template [DataDefaults=FromSchema<Schema, {
56
+ * keepDefaultedPropertiesOptional: false,
57
+ * parseIfThenElseKeywords: ParseIfThenElse,
58
+ * parseNotKeyword: ParseNot,
59
+ * }>].
60
+ * @param {Schema} schema - The request body json schema.
61
+ * @param {unknown} data - The data to validate.
62
+ * @param {TargetError | undefined} [ThrownError] - The error type.
63
+ * @returns {Promise<DataDefaults>}
64
+ */
65
+ export function validate<const Schema extends JSONSchema, ParseNot extends boolean = false, ParseIfThenElse extends boolean = false, DataDefaults = FromSchema<Schema, {
66
+ keepDefaultedPropertiesOptional: false;
67
+ parseIfThenElseKeywords: ParseIfThenElse;
68
+ parseNotKeyword: ParseNot;
69
+ }>>(schema: Schema, data: unknown, ThrownError?: TargetError | undefined): Promise<DataDefaults>;
70
+ export type ToJsonOptions = ({
71
+ schema: JSONSchema;
72
+ });
73
+ import type { JSONSchema } from './types.js';
74
+ import type { AsyncValidateFunction } from 'ajv';
75
+ import type { FromSchema } from './types.js';
76
+ import type { TargetError } from './types.js';
package/types/errors.d.ts CHANGED
@@ -1,35 +1,45 @@
1
+ /**
2
+ * @param {unknown} input - The value to check.
3
+ * @returns {input is DefinedError}
4
+ */
5
+ export function isDefinedError(input: unknown): input is DefinedError;
6
+ /**
7
+ * @param {unknown} input - The value to check.
8
+ * @returns {input is import('./types.js').ValidationError}
9
+ */
10
+ export function isSchemaError(input: unknown): input is import("./types.js").ValidationError;
1
11
  /**
2
12
  * @typedef {(
3
- * & import('@darksheep/errors').ErrorOptions
4
- * & { errors: import('ajv').DefinedError[] }
5
- * )} ErrorOptions
13
+ * & ErrorOptions
14
+ * & { errors: DefinedError[] }
15
+ * )} SchemaErrorOptions
6
16
  */
7
17
  /**
8
18
  * The server will not process the request body not matching the expected request schema.
9
19
  */
10
20
  export class RequestSchemaError extends BadRequest {
11
21
  /**
12
- * @param {string} message - The custom message for the http error.
13
- * @param {ErrorOptions} options - The custom message for the http error.
22
+ * @param {SchemaErrorOptions} options - The custom message for the http error.
14
23
  */
15
- constructor(message: string, { errors, ...options }: ErrorOptions);
16
- /** @type {import('ajv').DefinedError[]} */
17
- errors: import("ajv").DefinedError[];
24
+ constructor({ errors, ...options }: SchemaErrorOptions);
25
+ /** @type {DefinedError[]} */
26
+ errors: DefinedError[];
18
27
  }
19
28
  /**
20
29
  * The server tried sending a response that did not match the expected response schema.
21
30
  */
22
31
  export class ResponseSchemaError extends InternalServerError {
23
32
  /**
24
- * @param {string} message - The custom message for the http error.
25
- * @param {ErrorOptions} options - The custom message for the http error.
33
+ * @param {SchemaErrorOptions} options - The custom message for the http error.
26
34
  */
27
- constructor(message: string, { errors, ...options }: ErrorOptions);
28
- /** @type {import('ajv').DefinedError[]} */
29
- errors: import("ajv").DefinedError[];
35
+ constructor({ errors, ...options }: SchemaErrorOptions);
36
+ /** @type {DefinedError[]} */
37
+ errors: DefinedError[];
30
38
  }
31
- export type ErrorOptions = (import("@darksheep/errors").ErrorOptions & {
32
- errors: import("ajv").DefinedError[];
39
+ export type SchemaErrorOptions = (ErrorOptions & {
40
+ errors: DefinedError[];
33
41
  });
42
+ import type { DefinedError } from 'ajv';
34
43
  import { BadRequest } from '@darksheep/errors';
35
44
  import { InternalServerError } from '@darksheep/errors';
45
+ import type { ErrorOptions } from '@darksheep/errors';
package/types/index.d.ts CHANGED
@@ -1,24 +1,10 @@
1
1
  /**
2
- * @typedef {import('ajv').DefinedError} DefinedError
3
- * @typedef {Exclude<import('json-schema-to-ts').JSONSchema, boolean>} JSONSchema
4
- * @typedef {import('json-schema-to-ts').FromSchemaOptions} FromSchemaOptions
5
- * @typedef {import('json-schema-to-ts').FromSchemaDefaultOptions} FromSchemaDefaultOptions
6
- */
7
- /**
8
- * @template {JSONSchema} Schema
9
- * @template {FromSchemaOptions} [Options=FromSchemaDefaultOptions]
10
- * @typedef {import('json-schema-to-ts').FromSchema<Schema, Options>} FromSchema
11
- */
12
- /**
13
- * @template {JSONSchema} const Schema - The raw schema type.
2
+ * @template {import('./types.js').JSONSchema} const Schema - The raw schema type.
14
3
  * @param {Schema} schema - The raw JSON schema.
15
4
  * @returns {Schema}
16
5
  */
17
- export function defineSchema<const Schema extends JSONSchema>(schema: Schema): Schema;
18
- export type DefinedError = import("ajv").DefinedError;
19
- export type JSONSchema = Exclude<import("json-schema-to-ts").JSONSchema, boolean>;
20
- export type FromSchemaOptions = import("json-schema-to-ts").FromSchemaOptions;
21
- export type FromSchemaDefaultOptions = import("json-schema-to-ts").FromSchemaDefaultOptions;
22
- export type FromSchema<Schema extends JSONSchema, Options extends FromSchemaOptions = import("json-schema-to-ts").FromSchemaDefaultOptions> = import("json-schema-to-ts").FromSchema<Schema, Options>;
23
- export { RequestSchemaError, ResponseSchemaError } from "./errors.js";
24
- export { validateData, validateRequestBody, validateResponseBody } from "./validator.js";
6
+ export function defineSchema<const Schema extends import("./types.js").JSONSchema>(schema: Schema): Schema;
7
+ export * from "./types.js";
8
+ export { isDefinedError, isSchemaError, RequestSchemaError, ResponseSchemaError } from "./errors.js";
9
+ export { createValidator, validate } from "./async.js";
10
+ export { createAssertSchema, assertSchema } from "./sync.js";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @param {JSONSchema} schema - The original JSONSchema.
3
+ * @returns {ValidateFunction}
4
+ */
5
+ export function getValidator(schema: JSONSchema): ValidateFunction;
6
+ /**
7
+ * @template {JSONSchema} const Schema - The raw schema type.
8
+ * @template [Data=FromSchema<Schema>]
9
+ * @param {Schema} schema - The request body json schema.
10
+ * @returns {(data: unknown, TargetError?: TargetError | undefined) => asserts data is Data}
11
+ */
12
+ export function createAssertSchema<const Schema extends JSONSchema, Data = FromSchema<Schema>>(schema: Schema): (data: unknown, TargetError?: TargetError | undefined) => asserts data is Data;
13
+ /**
14
+ * @template {JSONSchema} const Schema - The raw schema type.
15
+ * @template [Data=FromSchema<Schema>]
16
+ * @param {Schema} schema - The request body json schema.
17
+ * @param {unknown} data - The raw data.
18
+ * @param {TargetError | undefined} [TargetError] - The error type.
19
+ * @returns {asserts data is Data}
20
+ */
21
+ export function assertSchema<const Schema extends JSONSchema, Data = FromSchema<Schema>>(schema: Schema, data: unknown, TargetError?: TargetError | undefined): asserts data is Data;
22
+ export type ToJsonOptions = ({
23
+ schema: JSONSchema;
24
+ });
25
+ import type { JSONSchema } from './types.js';
26
+ import type { ValidateFunction } from 'ajv';
27
+ import type { FromSchema } from './types.js';
28
+ import type { TargetError } from './types.js';
@@ -0,0 +1,12 @@
1
+ export type ValidationError = (Omit<import("ajv").ValidationError, "errors"> & {
2
+ errors: DefinedError[];
3
+ });
4
+ export type TargetError = new (options: {
5
+ cause?: Error;
6
+ errors: DefinedError[];
7
+ }) => Error;
8
+ export type JSONSchema = Exclude<import("json-schema-to-ts").JSONSchema, boolean>;
9
+ export type FromSchemaOptions = import("json-schema-to-ts").FromSchemaOptions;
10
+ export type FromSchemaDefaultOptions = import("json-schema-to-ts").FromSchemaDefaultOptions;
11
+ export type FromSchema<Schema extends JSONSchema, Options extends FromSchemaOptions = import("json-schema-to-ts").FromSchemaDefaultOptions> = import("json-schema-to-ts").FromSchema<Schema, Options>;
12
+ import type { DefinedError } from 'ajv';
package/src/validator.js DELETED
@@ -1,122 +0,0 @@
1
- import { Ajv } from 'ajv';
2
- import { RequestSchemaError, ResponseSchemaError } from './errors.js';
3
-
4
- /**
5
- * @import { DefinedError, AsyncSchema, AsyncValidateFunction, ValidationError } from 'ajv'
6
- * @import { FromSchema, JSONSchema } from 'json-schema-to-ts'
7
- */
8
-
9
- /**
10
- * @typedef {(
11
- * & { schema: JSONSchema }
12
- * )} ToJsonOptions
13
- */
14
-
15
- const ajv = new Ajv({
16
- allErrors: true,
17
- verbose: false,
18
- allowUnionTypes: false,
19
- multipleOfPrecision: 5,
20
- ownProperties: true,
21
- strict: true,
22
- useDefaults: false,
23
- });
24
-
25
- /**
26
- * @type {WeakMap<Exclude<JSONSchema, boolean>, AsyncValidateFunction>}
27
- */
28
- const compiled = new WeakMap();
29
-
30
- /**
31
- * @param {unknown} input - The value to check.
32
- * @returns {input is ValidationError & { errors: DefinedError[] }}
33
- */
34
- function isAjvError(input) {
35
- return (
36
- input instanceof Error &&
37
- 'errors' in input &&
38
- Array.isArray(input.errors)
39
- );
40
- }
41
-
42
- /**
43
- * @param {Exclude<JSONSchema, boolean>} schema - The original JSONSchema.
44
- * @returns {AsyncValidateFunction}
45
- */
46
- function getValidator(schema) {
47
- const Compiled = compiled.get(schema);
48
- if (Compiled != null) {
49
- return Compiled;
50
- }
51
-
52
- const validator = ajv.compile(
53
- /** @type {AsyncSchema} */ ({
54
- ...schema,
55
- $async: true,
56
- }),
57
- );
58
- compiled.set(schema, validator);
59
-
60
- return validator;
61
- }
62
-
63
- /**
64
- * @template {Exclude<JSONSchema, boolean>} Schema
65
- * @template [Data=FromSchema<Schema>]
66
- * @param {Schema} schema - The request body json schema.
67
- * @param {unknown} data - The data to validate.
68
- * @returns {Promise<Data>}
69
- * @throws {ValidationError & { errors: DefinedError[] }}
70
- */
71
- export async function validateData(schema, data) {
72
- /** @type {AsyncValidateFunction<Data>} */
73
- const validator = getValidator(schema);
74
-
75
- return await validator(data);
76
- }
77
-
78
- /**
79
- * @template {Exclude<JSONSchema, boolean>} Schema
80
- * @template [Data=FromSchema<Schema>]
81
- * @param {Schema} schema - The request body json schema.
82
- * @param {unknown} data - The data to validate.
83
- * @returns {Promise<Data>}
84
- * @throws {RequestSchemaError}
85
- */
86
- export async function validateRequestBody(schema, data) {
87
- try {
88
- return await validateData(schema, data);
89
- } catch (error) {
90
- if (isAjvError(error)) {
91
- throw new RequestSchemaError(
92
- 'Request.body does not match Schema',
93
- { cause: error, errors: error.errors },
94
- );
95
- }
96
-
97
- throw error;
98
- }
99
- }
100
-
101
- /**
102
- * @template {Exclude<JSONSchema, boolean>} Schema
103
- * @template [Data=FromSchema<Schema>]
104
- * @param {Schema} schema - The response body json schema.
105
- * @param {unknown} data - The data to validate.
106
- * @returns {Promise<Data>}
107
- * @throws {ResponseSchemaError}
108
- */
109
- export async function validateResponseBody(schema, data) {
110
- try {
111
- return await validateData(schema, data);
112
- } catch (error) {
113
- if (isAjvError(error)) {
114
- throw new ResponseSchemaError(
115
- 'Response.body does not match Schema',
116
- { cause: error, errors: error.errors },
117
- );
118
- }
119
-
120
- throw error;
121
- }
122
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * @template {Exclude<JSONSchema, boolean>} Schema
3
- * @template [Data=FromSchema<Schema>]
4
- * @param {Schema} schema - The request body json schema.
5
- * @param {unknown} data - The data to validate.
6
- * @returns {Promise<Data>}
7
- * @throws {ValidationError & { errors: DefinedError[] }}
8
- */
9
- export function validateData<Schema extends Exclude<JSONSchema, boolean>, Data = FromSchema<Schema>>(schema: Schema, data: unknown): Promise<Data>;
10
- /**
11
- * @template {Exclude<JSONSchema, boolean>} Schema
12
- * @template [Data=FromSchema<Schema>]
13
- * @param {Schema} schema - The request body json schema.
14
- * @param {unknown} data - The data to validate.
15
- * @returns {Promise<Data>}
16
- * @throws {RequestSchemaError}
17
- */
18
- export function validateRequestBody<Schema extends Exclude<JSONSchema, boolean>, Data = FromSchema<Schema>>(schema: Schema, data: unknown): Promise<Data>;
19
- /**
20
- * @template {Exclude<JSONSchema, boolean>} Schema
21
- * @template [Data=FromSchema<Schema>]
22
- * @param {Schema} schema - The response body json schema.
23
- * @param {unknown} data - The data to validate.
24
- * @returns {Promise<Data>}
25
- * @throws {ResponseSchemaError}
26
- */
27
- export function validateResponseBody<Schema extends Exclude<JSONSchema, boolean>, Data = FromSchema<Schema>>(schema: Schema, data: unknown): Promise<Data>;
28
- export type ToJsonOptions = ({
29
- schema: JSONSchema;
30
- });
31
- import type { JSONSchema } from 'json-schema-to-ts';
32
- import type { FromSchema } from 'json-schema-to-ts';