@darksheep/json-schema 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,63 @@
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();
21
+
22
+ console.info({ name, version });
23
+ ```
24
+
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!
28
+
29
+ ```js
30
+ const { getPackageJson } = require('@darksheep/package.json/sync');
31
+ const { name, version } = getPackageJson();
32
+
33
+ console.info({ name, version });
34
+ ```
35
+
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
+ ```
50
+
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);
63
+ ```
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@darksheep/json-schema",
3
+ "version": "0.0.0",
4
+ "description": "JSON schema validation (with types)",
5
+ "license": "UNLICENCED",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./types/index.d.ts",
10
+ "default": "./src/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "src",
15
+ "types",
16
+ "!*.test.*"
17
+ ],
18
+ "scripts": {
19
+ "prepack": "yarn dlx -q -p typescript tsc",
20
+ "test": "node --test src/**/*.test.js",
21
+ "test:watch": "node --watch --test src/**/*.test.js"
22
+ },
23
+ "dependencies": {
24
+ "@darksheep/errors": "",
25
+ "ajv": "8.17.1",
26
+ "json-schema-to-ts": "3.1.1"
27
+ },
28
+ "engines": {
29
+ "node": "^20.13.1 || ^22.2.0"
30
+ }
31
+ }
package/src/errors.js ADDED
@@ -0,0 +1,46 @@
1
+ import { BadRequest, InternalServerError } from '@darksheep/errors';
2
+
3
+ /**
4
+ * @typedef {(
5
+ * & import('@darksheep/errors').ErrorOptions
6
+ * & { errors: import('ajv').DefinedError[] }
7
+ * )} ErrorOptions
8
+ */
9
+
10
+ /**
11
+ * The server will not process the request body not matching the expected request schema.
12
+ */
13
+ export class RequestSchemaError extends BadRequest {
14
+ /** @type {import('ajv').DefinedError[]} */
15
+ errors = [];
16
+ name = 'RequestSchemaError';
17
+
18
+ /**
19
+ * @param {string} message - The custom message for the http error.
20
+ * @param {ErrorOptions} options - The custom message for the http error.
21
+ */
22
+ constructor(message, { errors, ...options }) {
23
+ super(message, options);
24
+
25
+ this.errors = errors;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * The server tried sending a response that did not match the expected response schema.
31
+ */
32
+ export class ResponseSchemaError extends InternalServerError {
33
+ /** @type {import('ajv').DefinedError[]} */
34
+ errors = [];
35
+ name = 'ResponseSchemaError';
36
+
37
+ /**
38
+ * @param {string} message - The custom message for the http error.
39
+ * @param {ErrorOptions} options - The custom message for the http error.
40
+ */
41
+ constructor(message, { errors, ...options }) {
42
+ super(message, options);
43
+
44
+ this.errors = errors;
45
+ }
46
+ }
package/src/index.js ADDED
@@ -0,0 +1,32 @@
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
+ /**
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.
16
+ * @param {Schema} schema - The raw JSON schema.
17
+ * @returns {Schema}
18
+ */
19
+ export function defineSchema(schema) {
20
+ return schema;
21
+ }
22
+
23
+ export {
24
+ RequestSchemaError,
25
+ ResponseSchemaError,
26
+ } from './errors.js';
27
+
28
+ export {
29
+ validateData,
30
+ validateRequestBody,
31
+ validateResponseBody,
32
+ } from './validator.js';
@@ -0,0 +1,122 @@
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
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @typedef {(
3
+ * & import('@darksheep/errors').ErrorOptions
4
+ * & { errors: import('ajv').DefinedError[] }
5
+ * )} ErrorOptions
6
+ */
7
+ /**
8
+ * The server will not process the request body not matching the expected request schema.
9
+ */
10
+ export class RequestSchemaError extends BadRequest {
11
+ /**
12
+ * @param {string} message - The custom message for the http error.
13
+ * @param {ErrorOptions} options - The custom message for the http error.
14
+ */
15
+ constructor(message: string, { errors, ...options }: ErrorOptions);
16
+ /** @type {import('ajv').DefinedError[]} */
17
+ errors: import("ajv").DefinedError[];
18
+ }
19
+ /**
20
+ * The server tried sending a response that did not match the expected response schema.
21
+ */
22
+ export class ResponseSchemaError extends InternalServerError {
23
+ /**
24
+ * @param {string} message - The custom message for the http error.
25
+ * @param {ErrorOptions} options - The custom message for the http error.
26
+ */
27
+ constructor(message: string, { errors, ...options }: ErrorOptions);
28
+ /** @type {import('ajv').DefinedError[]} */
29
+ errors: import("ajv").DefinedError[];
30
+ }
31
+ export type ErrorOptions = (import("@darksheep/errors").ErrorOptions & {
32
+ errors: import("ajv").DefinedError[];
33
+ });
34
+ import { BadRequest } from '@darksheep/errors';
35
+ import { InternalServerError } from '@darksheep/errors';
@@ -0,0 +1,24 @@
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.
14
+ * @param {Schema} schema - The raw JSON schema.
15
+ * @returns {Schema}
16
+ */
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";
@@ -0,0 +1,32 @@
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';