yedra 0.20.11 → 0.20.13

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.
Files changed (81) hide show
  1. package/dist/routing/rest.d.ts +2 -0
  2. package/dist/routing/rest.js +2 -0
  3. package/dist/routing/websocket.d.ts +1 -0
  4. package/dist/routing/websocket.js +1 -0
  5. package/dist/src/index.d.ts +9 -0
  6. package/dist/src/index.js +7 -0
  7. package/dist/src/lib.d.ts +10 -0
  8. package/dist/src/lib.js +16 -0
  9. package/dist/src/routing/app.d.ts +165 -0
  10. package/dist/src/routing/app.js +537 -0
  11. package/dist/src/routing/env.d.ts +4 -0
  12. package/dist/src/routing/env.js +13 -0
  13. package/dist/src/routing/errors.d.ts +50 -0
  14. package/dist/src/routing/errors.js +64 -0
  15. package/dist/src/routing/log.d.ts +22 -0
  16. package/dist/src/routing/log.js +30 -0
  17. package/dist/src/routing/path.d.ts +44 -0
  18. package/dist/src/routing/path.js +110 -0
  19. package/dist/src/routing/rest.d.ts +103 -0
  20. package/dist/src/routing/rest.js +181 -0
  21. package/dist/src/routing/websocket.d.ts +60 -0
  22. package/dist/src/routing/websocket.js +132 -0
  23. package/dist/src/schema-lib.d.ts +17 -0
  24. package/dist/src/schema-lib.js +20 -0
  25. package/dist/src/schema.d.ts +2 -0
  26. package/dist/src/schema.js +4 -0
  27. package/dist/src/util/counter.d.ts +7 -0
  28. package/dist/src/util/counter.js +24 -0
  29. package/dist/src/util/docs.d.ts +9 -0
  30. package/dist/src/util/docs.js +57 -0
  31. package/dist/src/util/security.d.ts +14 -0
  32. package/dist/src/util/security.js +6 -0
  33. package/dist/src/util/stream.d.ts +2 -0
  34. package/dist/src/util/stream.js +7 -0
  35. package/dist/src/validation/body.d.ts +46 -0
  36. package/dist/src/validation/body.js +15 -0
  37. package/dist/src/validation/boolean.d.ts +10 -0
  38. package/dist/src/validation/boolean.js +27 -0
  39. package/dist/src/validation/date.d.ts +11 -0
  40. package/dist/src/validation/date.js +29 -0
  41. package/dist/src/validation/doc.d.ts +10 -0
  42. package/dist/src/validation/doc.js +22 -0
  43. package/dist/src/validation/either.d.ts +15 -0
  44. package/dist/src/validation/either.js +38 -0
  45. package/dist/src/validation/enum.d.ts +19 -0
  46. package/dist/src/validation/enum.js +44 -0
  47. package/dist/src/validation/error.d.ts +21 -0
  48. package/dist/src/validation/error.js +32 -0
  49. package/dist/src/validation/integer.d.ts +23 -0
  50. package/dist/src/validation/integer.js +64 -0
  51. package/dist/src/validation/json.d.ts +12 -0
  52. package/dist/src/validation/json.js +33 -0
  53. package/dist/src/validation/lazy.d.ts +48 -0
  54. package/dist/src/validation/lazy.js +78 -0
  55. package/dist/src/validation/modifiable.d.ts +105 -0
  56. package/dist/src/validation/modifiable.js +223 -0
  57. package/dist/src/validation/none.d.ts +10 -0
  58. package/dist/src/validation/none.js +14 -0
  59. package/dist/src/validation/null.d.ts +10 -0
  60. package/dist/src/validation/null.js +21 -0
  61. package/dist/src/validation/number.d.ts +23 -0
  62. package/dist/src/validation/number.js +58 -0
  63. package/dist/src/validation/object.d.ts +38 -0
  64. package/dist/src/validation/object.js +87 -0
  65. package/dist/src/validation/raw.d.ts +13 -0
  66. package/dist/src/validation/raw.js +21 -0
  67. package/dist/src/validation/record.d.ts +16 -0
  68. package/dist/src/validation/record.js +51 -0
  69. package/dist/src/validation/schema.d.ts +35 -0
  70. package/dist/src/validation/schema.js +76 -0
  71. package/dist/src/validation/stream.d.ts +13 -0
  72. package/dist/src/validation/stream.js +26 -0
  73. package/dist/src/validation/string.d.ts +29 -0
  74. package/dist/src/validation/string.js +51 -0
  75. package/dist/src/validation/union.d.ts +16 -0
  76. package/dist/src/validation/union.js +36 -0
  77. package/dist/src/validation/unknown.d.ts +10 -0
  78. package/dist/src/validation/unknown.js +13 -0
  79. package/dist/src/validation/uuid.d.ts +11 -0
  80. package/dist/src/validation/uuid.js +23 -0
  81. package/package.json +3 -4
@@ -0,0 +1,20 @@
1
+ // Browser-safe schema-only exports.
2
+ // This module excludes all Node.js-specific code (HTTP server,
3
+ // WebSocket, streams, etc.) so it can be safely bundled for the browser.
4
+ export { BodyType } from './validation/body.js';
5
+ export { boolean } from './validation/boolean.js';
6
+ export { date } from './validation/date.js';
7
+ export { _enum as enum } from './validation/enum.js';
8
+ export { ValidationError } from './validation/error.js';
9
+ export { integer } from './validation/integer.js';
10
+ export { lazy, LazySchema } from './validation/lazy.js';
11
+ export { array } from './validation/modifiable.js';
12
+ export { _null as null } from './validation/null.js';
13
+ export { number } from './validation/number.js';
14
+ export { laxObject, object } from './validation/object.js';
15
+ export { record } from './validation/record.js';
16
+ export { Schema } from './validation/schema.js';
17
+ export { string } from './validation/string.js';
18
+ export { union } from './validation/union.js';
19
+ export { unknown } from './validation/unknown.js';
20
+ export { uuid } from './validation/uuid.js';
@@ -0,0 +1,2 @@
1
+ import * as y from './schema-lib.js';
2
+ export { y };
@@ -0,0 +1,4 @@
1
+ // Browser-safe entry point for frontend use.
2
+ // Usage: import { y } from 'yedra/schema'
3
+ import * as y from './schema-lib.js';
4
+ export { y };
@@ -0,0 +1,7 @@
1
+ export declare class Counter {
2
+ private count;
3
+ private resolve;
4
+ wait(): Promise<void>;
5
+ increment(): void;
6
+ decrement(): void;
7
+ }
@@ -0,0 +1,24 @@
1
+ export class Counter {
2
+ constructor() {
3
+ this.count = 0;
4
+ }
5
+ wait() {
6
+ return new Promise((resolve) => {
7
+ if (this.count === 0) {
8
+ resolve();
9
+ }
10
+ else {
11
+ this.resolve = resolve;
12
+ }
13
+ });
14
+ }
15
+ increment() {
16
+ this.count += 1;
17
+ }
18
+ decrement() {
19
+ this.count -= 1;
20
+ if (this.count === 0 && this.resolve !== undefined) {
21
+ this.resolve();
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,9 @@
1
+ import type { Schema } from '../validation/schema.js';
2
+ import type { SecurityScheme } from './security.js';
3
+ /**
4
+ * Generate OpenAPI documentation for parameters.
5
+ * @param params - Record of parameters.
6
+ * @param position - The position of the parameter, e.g. `path`, `query`, `header`. This is passed directly to OpenAPI.
7
+ * @returns A list of parameter documentations in OpenAPI format.
8
+ */
9
+ export declare const paramDocs: <Params extends Record<string, Schema<unknown>>>(params: Params, position: "path" | "query" | "header", security: SecurityScheme[]) => object[];
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Generate OpenAPI documentation for parameters.
3
+ * @param params - Record of parameters.
4
+ * @param position - The position of the parameter, e.g. `path`, `query`, `header`. This is passed directly to OpenAPI.
5
+ * @returns A list of parameter documentations in OpenAPI format.
6
+ */
7
+ export const paramDocs = (params, position, security) => {
8
+ const result = [];
9
+ for (const name in params) {
10
+ if (isAuthToken(name, position, security)) {
11
+ // don't include auth tokens in the documentation, they're already
12
+ // handled by the separate authentication feature of OpenAPI
13
+ continue;
14
+ }
15
+ const docs = params[name].documentation();
16
+ result.push({
17
+ name,
18
+ in: position,
19
+ description: 'description' in docs ? docs.description : undefined,
20
+ required: !params[name].isOptional(),
21
+ schema: docs,
22
+ });
23
+ }
24
+ return result;
25
+ };
26
+ /**
27
+ * Check whether this parameter is an auth token, and should therefore not be
28
+ * included directly in the generated documentation.
29
+ * @param paramName - The name of the parameter to check.
30
+ * @param position - The position of the parameter.
31
+ * @param security - The security schemes that are enabled for this endpoint.
32
+ * @param securitySchemes - The security scheme definitions.
33
+ * @returns Whether the parameter is an auth token.
34
+ */
35
+ const isAuthToken = (paramName, position, securitySchemes) => {
36
+ if (position === 'path') {
37
+ // path parameters cannot be auth tokens
38
+ return false;
39
+ }
40
+ for (const scheme of securitySchemes) {
41
+ if (scheme.scheme.type === 'http') {
42
+ if (paramName.toLowerCase() === 'authorization' &&
43
+ position === 'header') {
44
+ // http auth token has to be in authorization header
45
+ return true;
46
+ }
47
+ }
48
+ else {
49
+ if (scheme.scheme.in === position &&
50
+ paramName.toLowerCase() === scheme.scheme.name.toLowerCase()) {
51
+ // API key has to have the correct name and be in the correct position
52
+ return true;
53
+ }
54
+ }
55
+ }
56
+ return false;
57
+ };
@@ -0,0 +1,14 @@
1
+ export declare class SecurityScheme {
2
+ name: string;
3
+ scheme: SecuritySchemeData;
4
+ constructor(name: string, scheme: SecuritySchemeData);
5
+ }
6
+ type SecuritySchemeData = {
7
+ type: 'http';
8
+ scheme: 'basic' | 'bearer';
9
+ } | {
10
+ type: 'apiKey';
11
+ in: 'header' | 'query' | 'cookie';
12
+ name: string;
13
+ };
14
+ export {};
@@ -0,0 +1,6 @@
1
+ export class SecurityScheme {
2
+ constructor(name, scheme) {
3
+ this.name = name;
4
+ this.scheme = scheme;
5
+ }
6
+ }
@@ -0,0 +1,2 @@
1
+ import type { Readable } from 'node:stream';
2
+ export declare const readableToBuffer: (stream: Readable) => Promise<Buffer<ArrayBuffer>>;
@@ -0,0 +1,7 @@
1
+ export const readableToBuffer = async (stream) => {
2
+ const chunks = [];
3
+ for await (const chunk of stream) {
4
+ chunks.push(chunk);
5
+ }
6
+ return Buffer.concat(chunks);
7
+ };
@@ -0,0 +1,46 @@
1
+ import type { Readable } from 'node:stream';
2
+ /**
3
+ * The base class for all body types.
4
+ * @typeParam Provides - The type that is provided by this body type.
5
+ * When a parse is successful, this is the type of object you get back.
6
+ * @typeParam Accepts - The type that is accepted by this body type.
7
+ * When you want a parse to be successful, this is the type of object
8
+ * you have to pass in. This is always weaker than `Provides`, since you
9
+ * can always pass in a value of `Provides` and get a success.
10
+ */
11
+ export declare abstract class BodyType<Provides, Accepts> {
12
+ _provides: Provides;
13
+ _accepts: Accepts;
14
+ constructor();
15
+ /**
16
+ * Deserialize a raw stream.
17
+ * @param stream - The raw stream.
18
+ * @param contentType - The content type.
19
+ */
20
+ abstract deserialize(stream: Readable, contentType: string): Promise<Provides>;
21
+ /**
22
+ * Generate OpenAPI docs for this body.
23
+ */
24
+ abstract bodyDocs(): object;
25
+ }
26
+ /**
27
+ * Get the type that is provided by a schema. When you parse
28
+ * something with a schema, this is the type you get back.
29
+ */
30
+ export type TypeofProvides<T extends BodyType<unknown, unknown>> = T['_provides'];
31
+ /**
32
+ * Get the type that is accepted by a schema. When you parse
33
+ * something with a schema, this is the type you have to pass
34
+ * in to get a successful result.
35
+ */
36
+ export type TypeofAccepts<T extends BodyType<unknown, unknown>> = T['_accepts'];
37
+ /**
38
+ * Get the type that is parsed by a schema. This is the same
39
+ * as `TypeofProvides`.
40
+ *
41
+ * ```typescript
42
+ * const schema = y.string();
43
+ * type SchemaType = y.Typeof<typeof schema>; // string
44
+ * ```
45
+ */
46
+ export type Typeof<T extends BodyType<unknown, unknown>> = TypeofProvides<T>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The base class for all body types.
3
+ * @typeParam Provides - The type that is provided by this body type.
4
+ * When a parse is successful, this is the type of object you get back.
5
+ * @typeParam Accepts - The type that is accepted by this body type.
6
+ * When you want a parse to be successful, this is the type of object
7
+ * you have to pass in. This is always weaker than `Provides`, since you
8
+ * can always pass in a value of `Provides` and get a success.
9
+ */
10
+ export class BodyType {
11
+ constructor() {
12
+ this._provides = undefined;
13
+ this._accepts = undefined;
14
+ }
15
+ }
@@ -0,0 +1,10 @@
1
+ import { ModifiableSchema } from './modifiable.js';
2
+ declare class BooleanSchema extends ModifiableSchema<boolean> {
3
+ parse(obj: unknown): boolean;
4
+ documentation(): object;
5
+ }
6
+ /**
7
+ * A schema matching any boolean.
8
+ */
9
+ export declare const boolean: () => BooleanSchema;
10
+ export {};
@@ -0,0 +1,27 @@
1
+ import { Issue, ValidationError } from './error.js';
2
+ import { ModifiableSchema } from './modifiable.js';
3
+ class BooleanSchema extends ModifiableSchema {
4
+ parse(obj) {
5
+ if (obj === 'true') {
6
+ return true;
7
+ }
8
+ if (obj === 'false') {
9
+ return false;
10
+ }
11
+ if (typeof obj !== 'boolean') {
12
+ throw new ValidationError([
13
+ new Issue([], `Expected boolean but got ${typeof obj}`),
14
+ ]);
15
+ }
16
+ return obj;
17
+ }
18
+ documentation() {
19
+ return {
20
+ type: 'boolean',
21
+ };
22
+ }
23
+ }
24
+ /**
25
+ * A schema matching any boolean.
26
+ */
27
+ export const boolean = () => new BooleanSchema();
@@ -0,0 +1,11 @@
1
+ import { ModifiableSchema } from './modifiable.js';
2
+ declare class DateSchema extends ModifiableSchema<Date> {
3
+ parse(obj: unknown): Date;
4
+ documentation(): object;
5
+ }
6
+ /**
7
+ * A schema matching date objects, or strings and numbers that can be
8
+ * interpreted as dates.
9
+ */
10
+ export declare const date: () => DateSchema;
11
+ export {};
@@ -0,0 +1,29 @@
1
+ import { Issue, ValidationError } from './error.js';
2
+ import { ModifiableSchema } from './modifiable.js';
3
+ class DateSchema extends ModifiableSchema {
4
+ parse(obj) {
5
+ if (obj instanceof Date) {
6
+ return obj;
7
+ }
8
+ if (typeof obj === 'string' || typeof obj === 'number') {
9
+ const date = new Date(obj);
10
+ if (date.toString() !== 'Invalid Date') {
11
+ return date;
12
+ }
13
+ }
14
+ throw new ValidationError([
15
+ new Issue([], `Expected date but got ${typeof obj}`),
16
+ ]);
17
+ }
18
+ documentation() {
19
+ return {
20
+ type: 'string',
21
+ format: 'date-time',
22
+ };
23
+ }
24
+ }
25
+ /**
26
+ * A schema matching date objects, or strings and numbers that can be
27
+ * interpreted as dates.
28
+ */
29
+ export const date = () => new DateSchema();
@@ -0,0 +1,10 @@
1
+ import { Schema } from './schema.js';
2
+ export declare class DocSchema<T> extends Schema<T> {
3
+ private readonly _schema;
4
+ private readonly _description;
5
+ private readonly _example?;
6
+ constructor(schema: Schema<T>, description: string, example?: T);
7
+ parse(obj: unknown): T;
8
+ documentation(): object;
9
+ isOptional(): boolean;
10
+ }
@@ -0,0 +1,22 @@
1
+ import { Schema } from './schema.js';
2
+ export class DocSchema extends Schema {
3
+ constructor(schema, description, example) {
4
+ super();
5
+ this._schema = schema;
6
+ this._description = description;
7
+ this._example = example;
8
+ }
9
+ parse(obj) {
10
+ return this._schema.parse(obj);
11
+ }
12
+ documentation() {
13
+ return {
14
+ description: this._description,
15
+ ...(this._example !== undefined && { example: this._example }),
16
+ ...this._schema.documentation(),
17
+ };
18
+ }
19
+ isOptional() {
20
+ return this._schema.isOptional();
21
+ }
22
+ }
@@ -0,0 +1,15 @@
1
+ import type { Readable } from 'node:stream';
2
+ import { BodyType, type Typeof, type TypeofAccepts, type TypeofProvides } from './body.js';
3
+ declare class EitherBody<T extends [...BodyType<unknown, unknown>[]]> extends BodyType<TypeofProvides<T[number]>, TypeofAccepts<T[number]>> {
4
+ private options;
5
+ constructor(options: T);
6
+ deserialize(stream: Readable, contentType: string): Promise<Typeof<T[number]>>;
7
+ bodyDocs(): object;
8
+ }
9
+ /**
10
+ * A body that matches any of the provided options.
11
+ * @param options
12
+ * @returns
13
+ */
14
+ export declare const either: <T extends [...BodyType<unknown, unknown>[]]>(...options: T) => EitherBody<T>;
15
+ export {};
@@ -0,0 +1,38 @@
1
+ import { BodyType, } from './body.js';
2
+ import { ValidationError } from './error.js';
3
+ class EitherBody extends BodyType {
4
+ constructor(options) {
5
+ super();
6
+ this.options = options;
7
+ }
8
+ deserialize(stream, contentType) {
9
+ const issues = [];
10
+ for (const option of this.options) {
11
+ try {
12
+ return option.deserialize(stream, contentType);
13
+ }
14
+ catch (error) {
15
+ if (error instanceof ValidationError) {
16
+ issues.push(...error.issues);
17
+ }
18
+ else {
19
+ throw error;
20
+ }
21
+ }
22
+ }
23
+ throw new ValidationError(issues);
24
+ }
25
+ bodyDocs() {
26
+ let docs = {};
27
+ for (const option of this.options) {
28
+ docs = { ...docs, ...option.bodyDocs() };
29
+ }
30
+ return docs;
31
+ }
32
+ }
33
+ /**
34
+ * A body that matches any of the provided options.
35
+ * @param options
36
+ * @returns
37
+ */
38
+ export const either = (...options) => new EitherBody(options);
@@ -0,0 +1,19 @@
1
+ import { ModifiableSchema } from './modifiable.js';
2
+ declare class EnumSchema<T extends [...(string | number)[]]> extends ModifiableSchema<T[number]> {
3
+ private readonly options;
4
+ private readonly normalized;
5
+ constructor(options: T);
6
+ parse(obj: unknown): T[number];
7
+ documentation(): object;
8
+ }
9
+ /**
10
+ * A schema that matches exactly the values provided. The options provided can
11
+ * be either strings or numbers.
12
+ *
13
+ * ```typescript
14
+ * const options = y.enum(3, 4, 'hello');
15
+ * type OptionsType = y.Typeof<typeof options>; // 3 | 4 | 'hello'
16
+ * ```
17
+ */
18
+ export declare const _enum: <T extends [...(string | number)[]]>(...options: T) => EnumSchema<T>;
19
+ export {};
@@ -0,0 +1,44 @@
1
+ import { Issue, ValidationError } from './error.js';
2
+ import { ModifiableSchema } from './modifiable.js';
3
+ class EnumSchema extends ModifiableSchema {
4
+ constructor(options) {
5
+ super();
6
+ this.options = options;
7
+ this.normalized = options.map((option) => option.toString());
8
+ }
9
+ parse(obj) {
10
+ if (typeof obj !== 'string' && typeof obj !== 'number') {
11
+ // enum objects can only be strings or numbers
12
+ throw new ValidationError([
13
+ new Issue([], `Expected one of ${this.options.join(', ')} but got ${typeof obj}`),
14
+ ]);
15
+ }
16
+ // compare only the stringified (normalized) values
17
+ const normalizedObj = obj.toString();
18
+ const index = this.normalized.indexOf(normalizedObj);
19
+ if (index === -1) {
20
+ // invalid value
21
+ throw new ValidationError([
22
+ new Issue([], `Expected one of ${this.options.join(', ')} but got ${obj}`),
23
+ ]);
24
+ }
25
+ // return the un-normalized value
26
+ return this.options[index];
27
+ }
28
+ documentation() {
29
+ return {
30
+ type: 'string',
31
+ enum: this.options,
32
+ };
33
+ }
34
+ }
35
+ /**
36
+ * A schema that matches exactly the values provided. The options provided can
37
+ * be either strings or numbers.
38
+ *
39
+ * ```typescript
40
+ * const options = y.enum(3, 4, 'hello');
41
+ * type OptionsType = y.Typeof<typeof options>; // 3 | 4 | 'hello'
42
+ * ```
43
+ */
44
+ export const _enum = (...options) => new EnumSchema(options);
@@ -0,0 +1,21 @@
1
+ export declare class Issue {
2
+ readonly path: string[];
3
+ readonly message: string;
4
+ constructor(path: string[], message: string);
5
+ }
6
+ export declare class ValidationError extends Error {
7
+ readonly issues: Issue[];
8
+ constructor(issues: Issue[]);
9
+ /**
10
+ * Format the validation error as a string.
11
+ * @returns The formatted error.
12
+ */
13
+ format(): string;
14
+ /**
15
+ * Adds a prefix to the validation error paths.
16
+ * @param prefix - The prefix path.
17
+ * @returns The prefixed error.
18
+ */
19
+ withPrefix(prefix: string): Issue[];
20
+ private static formatIssues;
21
+ }
@@ -0,0 +1,32 @@
1
+ export class Issue {
2
+ constructor(path, message) {
3
+ this.path = path;
4
+ this.message = message;
5
+ }
6
+ }
7
+ export class ValidationError extends Error {
8
+ constructor(issues) {
9
+ super(ValidationError.formatIssues(issues));
10
+ this.issues = issues;
11
+ }
12
+ /**
13
+ * Format the validation error as a string.
14
+ * @returns The formatted error.
15
+ */
16
+ format() {
17
+ return ValidationError.formatIssues(this.issues);
18
+ }
19
+ /**
20
+ * Adds a prefix to the validation error paths.
21
+ * @param prefix - The prefix path.
22
+ * @returns The prefixed error.
23
+ */
24
+ withPrefix(prefix) {
25
+ return this.issues.map((issue) => new Issue([prefix, ...issue.path], issue.message));
26
+ }
27
+ static formatIssues(issues) {
28
+ return issues
29
+ .map((issue) => `Error at \`${issue.path.join('.')}\`: ${issue.message}.`)
30
+ .join(' ');
31
+ }
32
+ }
@@ -0,0 +1,23 @@
1
+ import { ModifiableSchema } from './modifiable.js';
2
+ declare class IntegerSchema extends ModifiableSchema<number> {
3
+ private readonly minValue?;
4
+ private readonly maxValue?;
5
+ constructor(min?: number, max?: number);
6
+ /**
7
+ * Set the minimum value the number is allowed to be.
8
+ * @param value - The minimum value.
9
+ */
10
+ min(value: number): IntegerSchema;
11
+ /**
12
+ * Set the maximum value the number is allowed to be.
13
+ * @param value - The maximum value.
14
+ */
15
+ max(value: number): IntegerSchema;
16
+ parse(obj: unknown): number;
17
+ documentation(): object;
18
+ }
19
+ /**
20
+ * A schema that matches an integer.
21
+ */
22
+ export declare const integer: () => IntegerSchema;
23
+ export {};
@@ -0,0 +1,64 @@
1
+ import { Issue, ValidationError } from './error.js';
2
+ import { ModifiableSchema } from './modifiable.js';
3
+ class IntegerSchema extends ModifiableSchema {
4
+ constructor(min, max) {
5
+ super();
6
+ this.minValue = min;
7
+ this.maxValue = max;
8
+ }
9
+ /**
10
+ * Set the minimum value the number is allowed to be.
11
+ * @param value - The minimum value.
12
+ */
13
+ min(value) {
14
+ if (!Number.isInteger(value)) {
15
+ throw new Error('minimum value has to be an integer');
16
+ }
17
+ return new IntegerSchema(value, this.maxValue);
18
+ }
19
+ /**
20
+ * Set the maximum value the number is allowed to be.
21
+ * @param value - The maximum value.
22
+ */
23
+ max(value) {
24
+ if (!Number.isInteger(value)) {
25
+ throw new Error('maximum value has to be an integer');
26
+ }
27
+ return new IntegerSchema(this.minValue, value);
28
+ }
29
+ parse(obj) {
30
+ if (typeof obj !== 'number' && typeof obj !== 'string') {
31
+ throw new ValidationError([
32
+ new Issue([], `Expected number but got ${typeof obj}`),
33
+ ]);
34
+ }
35
+ const num = typeof obj === 'number' ? obj : Number.parseFloat(obj);
36
+ if (Number.isNaN(num) || !Number.isInteger(num)) {
37
+ throw new ValidationError([
38
+ new Issue([], `Expected integer but got ${typeof obj}`),
39
+ ]);
40
+ }
41
+ if (this.minValue !== undefined && num < this.minValue) {
42
+ throw new ValidationError([
43
+ new Issue([], `Must be at least ${this.minValue}, but was ${num}`),
44
+ ]);
45
+ }
46
+ if (this.maxValue !== undefined && num > this.maxValue) {
47
+ throw new ValidationError([
48
+ new Issue([], `Must be at most ${this.maxValue}, but was ${num}`),
49
+ ]);
50
+ }
51
+ return num;
52
+ }
53
+ documentation() {
54
+ return {
55
+ type: 'integer',
56
+ ...(this.minValue !== undefined && { minimum: this.minValue }),
57
+ ...(this.maxValue !== undefined && { maximum: this.maxValue }),
58
+ };
59
+ }
60
+ }
61
+ /**
62
+ * A schema that matches an integer.
63
+ */
64
+ export const integer = () => new IntegerSchema();
@@ -0,0 +1,12 @@
1
+ import type { Readable } from 'node:stream';
2
+ import { BodyType } from './body.js';
3
+ import type { Schema } from './schema.js';
4
+ declare class JsonBody<T> extends BodyType<T, T> {
5
+ private contentType;
6
+ private schema;
7
+ constructor(contentType: string, schema: Schema<T>);
8
+ deserialize(stream: Readable, contentType: string): Promise<T>;
9
+ bodyDocs(): object;
10
+ }
11
+ export declare const json: <T>(schema: Schema<T>, contentType: string) => JsonBody<T>;
12
+ export {};
@@ -0,0 +1,33 @@
1
+ import { readableToBuffer } from '../util/stream.js';
2
+ import { BodyType } from './body.js';
3
+ import { Issue, ValidationError } from './error.js';
4
+ class JsonBody extends BodyType {
5
+ constructor(contentType, schema) {
6
+ super();
7
+ this.contentType = contentType;
8
+ this.schema = schema;
9
+ }
10
+ async deserialize(stream, contentType) {
11
+ const buffer = await readableToBuffer(stream);
12
+ if (buffer.length === 0) {
13
+ return this.schema.parse({});
14
+ }
15
+ if (contentType !== this.contentType) {
16
+ throw new ValidationError([
17
+ new Issue([], `Expected content type \`${this.contentType}\`, but got \`${contentType}\``),
18
+ ]);
19
+ }
20
+ const obj = JSON.parse(buffer.toString('utf-8'));
21
+ return this.schema.parse(obj);
22
+ }
23
+ bodyDocs() {
24
+ return {
25
+ [this.contentType]: {
26
+ schema: this.schema.documentation(),
27
+ },
28
+ };
29
+ }
30
+ }
31
+ export const json = (schema, contentType) => {
32
+ return new JsonBody(contentType, schema);
33
+ };