yedra 0.20.2 → 0.20.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.
@@ -1,4 +1,4 @@
1
- import { Schema } from './schema.js';
1
+ import { Schema } from "./schema.js";
2
2
  export declare class DocSchema<T> extends Schema<T> {
3
3
  private readonly _schema;
4
4
  private readonly _description;
@@ -1,4 +1,4 @@
1
- import { Schema } from './schema.js';
1
+ import { Schema } from "./schema.js";
2
2
  export class DocSchema extends Schema {
3
3
  constructor(schema, description, example) {
4
4
  super();
@@ -12,7 +12,7 @@ export class DocSchema extends Schema {
12
12
  documentation() {
13
13
  return {
14
14
  description: this._description,
15
- example: this._example,
15
+ ...(this._example !== undefined && { example: this._example }),
16
16
  ...this._schema.documentation(),
17
17
  };
18
18
  }
@@ -1,4 +1,4 @@
1
- import { ModifiableSchema } from './modifiable.js';
1
+ import { ModifiableSchema } from "./modifiable.js";
2
2
  declare class IntegerSchema extends ModifiableSchema<number> {
3
3
  private readonly minValue?;
4
4
  private readonly maxValue?;
@@ -1,5 +1,5 @@
1
- import { Issue, ValidationError } from './error.js';
2
- import { ModifiableSchema } from './modifiable.js';
1
+ import { Issue, ValidationError } from "./error.js";
2
+ import { ModifiableSchema } from "./modifiable.js";
3
3
  class IntegerSchema extends ModifiableSchema {
4
4
  constructor(min, max) {
5
5
  super();
@@ -12,7 +12,7 @@ class IntegerSchema extends ModifiableSchema {
12
12
  */
13
13
  min(value) {
14
14
  if (!Number.isInteger(value)) {
15
- throw new Error('minimum value has to be an integer');
15
+ throw new Error("minimum value has to be an integer");
16
16
  }
17
17
  return new IntegerSchema(value, this.maxValue);
18
18
  }
@@ -22,17 +22,17 @@ class IntegerSchema extends ModifiableSchema {
22
22
  */
23
23
  max(value) {
24
24
  if (!Number.isInteger(value)) {
25
- throw new Error('maximum value has to be an integer');
25
+ throw new Error("maximum value has to be an integer");
26
26
  }
27
27
  return new IntegerSchema(this.minValue, value);
28
28
  }
29
29
  parse(obj) {
30
- if (typeof obj !== 'number' && typeof obj !== 'string') {
30
+ if (typeof obj !== "number" && typeof obj !== "string") {
31
31
  throw new ValidationError([
32
32
  new Issue([], `Expected number but got ${typeof obj}`),
33
33
  ]);
34
34
  }
35
- const num = typeof obj === 'number' ? obj : Number.parseFloat(obj);
35
+ const num = typeof obj === "number" ? obj : Number.parseFloat(obj);
36
36
  if (Number.isNaN(num) || !Number.isInteger(num)) {
37
37
  throw new ValidationError([
38
38
  new Issue([], `Expected integer but got ${typeof obj}`),
@@ -52,9 +52,9 @@ class IntegerSchema extends ModifiableSchema {
52
52
  }
53
53
  documentation() {
54
54
  return {
55
- type: 'integer',
56
- minimum: this.minValue,
57
- maximum: this.maxValue,
55
+ type: "integer",
56
+ ...(this.minValue !== undefined && { minimum: this.minValue }),
57
+ ...(this.maxValue !== undefined && { maximum: this.maxValue }),
58
58
  };
59
59
  }
60
60
  }
@@ -1,11 +1,24 @@
1
- import type { Typeof } from './body.js';
2
- import { DocSchema } from './doc.js';
3
- import { Schema } from './schema.js';
1
+ import type { Typeof } from "./body.js";
2
+ import { DocSchema } from "./doc.js";
3
+ import { Schema } from "./schema.js";
4
4
  export declare abstract class ModifiableSchema<T> extends Schema<T> {
5
5
  /**
6
6
  * Mark this schema as optional.
7
7
  */
8
8
  optional(): OptionalSchema<T>;
9
+ /**
10
+ * Provide a default value. If the input is undefined or null, the default
11
+ * value is returned instead.
12
+ * @param value - The default value.
13
+ */
14
+ default(value: T): DefaultSchema<T>;
15
+ /**
16
+ * Add a custom validation predicate. The inner schema is parsed first,
17
+ * then the predicate is checked against the parsed value.
18
+ * @param fn - A predicate that returns true if the value is valid, or a
19
+ * string error message if it is not.
20
+ */
21
+ refine(fn: (value: T) => boolean | string, docs?: Record<string, unknown>): RefinedSchema<T>;
9
22
  describe(description: string, example?: T): DocSchema<T>;
10
23
  array(): ArraySchema<Schema<T>>;
11
24
  }
@@ -27,27 +40,44 @@ declare class OptionalSchema<T> extends Schema<T | undefined> {
27
40
  documentation(): object;
28
41
  isOptional(): boolean;
29
42
  }
43
+ declare class DefaultSchema<T> extends Schema<T> {
44
+ private readonly schema;
45
+ private readonly defaultValue;
46
+ constructor(schema: Schema<T>, defaultValue: T);
47
+ describe(description: string, example?: T): DocSchema<T>;
48
+ array(): ArraySchema<Schema<T>>;
49
+ parse(obj: unknown): T;
50
+ documentation(): object;
51
+ isOptional(): boolean;
52
+ }
53
+ export declare class RefinedSchema<T> extends ModifiableSchema<T> {
54
+ private readonly schema;
55
+ private readonly predicate;
56
+ private readonly docs?;
57
+ constructor(schema: Schema<T>, predicate: (value: T) => boolean | string, docs?: Record<string, unknown>);
58
+ parse(obj: unknown): T;
59
+ documentation(): object;
60
+ isOptional(): boolean;
61
+ }
30
62
  export declare class ArraySchema<ItemSchema extends Schema<unknown>> extends ModifiableSchema<Typeof<ItemSchema>[]> {
31
63
  private readonly itemSchema;
32
- private readonly minItems?;
33
- private readonly maxItems?;
34
- constructor(itemSchema: ItemSchema, minItems?: number, maxItems?: number);
64
+ constructor(itemSchema: ItemSchema);
35
65
  /**
36
66
  * Set the minimum number of items for arrays.
37
67
  * @param items - The minimum number of items.
38
68
  */
39
- min(items: number): ArraySchema<ItemSchema>;
69
+ min(items: number): RefinedSchema<Typeof<ItemSchema>[]>;
40
70
  /**
41
71
  * Set the maximum number of items for arrays.
42
72
  * @param items - The maximum number of items.
43
73
  */
44
- max(items: number): ArraySchema<ItemSchema>;
74
+ max(items: number): RefinedSchema<Typeof<ItemSchema>[]>;
45
75
  /**
46
76
  * Set the exact number of items for arrays.
47
77
  * This is equivalent to calling both min and max.
48
78
  * @param items - The number of items.
49
79
  */
50
- length(items: number): ArraySchema<ItemSchema>;
80
+ length(items: number): RefinedSchema<Typeof<ItemSchema>[]>;
51
81
  parse(obj: unknown): Typeof<ItemSchema>[];
52
82
  documentation(): object;
53
83
  }
@@ -1,6 +1,6 @@
1
- import { DocSchema } from './doc.js';
2
- import { Issue, ValidationError } from './error.js';
3
- import { Schema } from './schema.js';
1
+ import { DocSchema } from "./doc.js";
2
+ import { Issue, ValidationError } from "./error.js";
3
+ import { Schema } from "./schema.js";
4
4
  export class ModifiableSchema extends Schema {
5
5
  /**
6
6
  * Mark this schema as optional.
@@ -8,6 +8,23 @@ export class ModifiableSchema extends Schema {
8
8
  optional() {
9
9
  return new OptionalSchema(this);
10
10
  }
11
+ /**
12
+ * Provide a default value. If the input is undefined or null, the default
13
+ * value is returned instead.
14
+ * @param value - The default value.
15
+ */
16
+ default(value) {
17
+ return new DefaultSchema(this, value);
18
+ }
19
+ /**
20
+ * Add a custom validation predicate. The inner schema is parsed first,
21
+ * then the predicate is checked against the parsed value.
22
+ * @param fn - A predicate that returns true if the value is valid, or a
23
+ * string error message if it is not.
24
+ */
25
+ refine(fn, docs) {
26
+ return new RefinedSchema(this, fn, docs);
27
+ }
11
28
  describe(description, example) {
12
29
  return new DocSchema(this, description, example);
13
30
  }
@@ -47,26 +64,78 @@ class OptionalSchema extends Schema {
47
64
  return true;
48
65
  }
49
66
  }
67
+ class DefaultSchema extends Schema {
68
+ constructor(schema, defaultValue) {
69
+ super();
70
+ this.schema = schema;
71
+ this.defaultValue = defaultValue;
72
+ }
73
+ describe(description, example) {
74
+ return new DocSchema(this, description, example);
75
+ }
76
+ array() {
77
+ return new ArraySchema(this);
78
+ }
79
+ parse(obj) {
80
+ if (obj === undefined || obj === null) {
81
+ return this.defaultValue;
82
+ }
83
+ return this.schema.parse(obj);
84
+ }
85
+ documentation() {
86
+ return {
87
+ ...this.schema.documentation(),
88
+ default: this.defaultValue,
89
+ };
90
+ }
91
+ isOptional() {
92
+ return true;
93
+ }
94
+ }
95
+ export class RefinedSchema extends ModifiableSchema {
96
+ constructor(schema, predicate, docs) {
97
+ super();
98
+ this.schema = schema;
99
+ this.predicate = predicate;
100
+ this.docs = docs;
101
+ }
102
+ parse(obj) {
103
+ const parsed = this.schema.parse(obj);
104
+ const result = this.predicate(parsed);
105
+ if (result === true) {
106
+ return parsed;
107
+ }
108
+ const message = typeof result === "string" ? result : "Validation failed";
109
+ throw new ValidationError([new Issue([], message)]);
110
+ }
111
+ documentation() {
112
+ return {
113
+ ...this.schema.documentation(),
114
+ ...this.docs,
115
+ };
116
+ }
117
+ isOptional() {
118
+ return this.schema.isOptional();
119
+ }
120
+ }
50
121
  export class ArraySchema extends ModifiableSchema {
51
- constructor(itemSchema, minItems, maxItems) {
122
+ constructor(itemSchema) {
52
123
  super();
53
124
  this.itemSchema = itemSchema;
54
- this.minItems = minItems;
55
- this.maxItems = maxItems;
56
125
  }
57
126
  /**
58
127
  * Set the minimum number of items for arrays.
59
128
  * @param items - The minimum number of items.
60
129
  */
61
130
  min(items) {
62
- return new ArraySchema(this.itemSchema, items, this.maxItems);
131
+ return this.refine((arr) => arr.length >= items || `Must have at least ${items} items`, { minItems: items });
63
132
  }
64
133
  /**
65
134
  * Set the maximum number of items for arrays.
66
135
  * @param items - The maximum number of items.
67
136
  */
68
137
  max(items) {
69
- return new ArraySchema(this.itemSchema, this.minItems, items);
138
+ return this.refine((arr) => arr.length <= items || `Must have at most ${items} items`, { maxItems: items });
70
139
  }
71
140
  /**
72
141
  * Set the exact number of items for arrays.
@@ -74,7 +143,7 @@ export class ArraySchema extends ModifiableSchema {
74
143
  * @param items - The number of items.
75
144
  */
76
145
  length(items) {
77
- return new ArraySchema(this.itemSchema, items, items);
146
+ return this.min(items).refine((arr) => arr.length <= items || `Must have at most ${items} items`, { maxItems: items });
78
147
  }
79
148
  parse(obj) {
80
149
  if (!Array.isArray(obj)) {
@@ -82,16 +151,6 @@ export class ArraySchema extends ModifiableSchema {
82
151
  new Issue([], `Expected array but got ${typeof obj}`),
83
152
  ]);
84
153
  }
85
- if (this.minItems && obj.length < this.minItems) {
86
- throw new ValidationError([
87
- new Issue([], `Must have at least ${this.minItems} items, but has ${obj.length}`),
88
- ]);
89
- }
90
- if (this.maxItems && obj.length > this.maxItems) {
91
- throw new ValidationError([
92
- new Issue([], `Must have at most ${this.maxItems} items, but has ${obj.length}`),
93
- ]);
94
- }
95
154
  const elems = [];
96
155
  const issues = [];
97
156
  for (let i = 0; i < obj.length; ++i) {
@@ -114,10 +173,8 @@ export class ArraySchema extends ModifiableSchema {
114
173
  }
115
174
  documentation() {
116
175
  return {
117
- type: 'array',
176
+ type: "array",
118
177
  items: this.itemSchema.documentation(),
119
- minItems: this.minItems,
120
- maxItems: this.maxItems,
121
178
  };
122
179
  }
123
180
  }
@@ -1,4 +1,4 @@
1
- import { ModifiableSchema } from './modifiable.js';
1
+ import { ModifiableSchema } from "./modifiable.js";
2
2
  declare class NumberSchema extends ModifiableSchema<number> {
3
3
  private readonly minValue?;
4
4
  private readonly maxValue?;
@@ -1,5 +1,5 @@
1
- import { Issue, ValidationError } from './error.js';
2
- import { ModifiableSchema } from './modifiable.js';
1
+ import { Issue, ValidationError } from "./error.js";
2
+ import { ModifiableSchema } from "./modifiable.js";
3
3
  class NumberSchema extends ModifiableSchema {
4
4
  constructor(min, max) {
5
5
  super();
@@ -21,12 +21,12 @@ class NumberSchema extends ModifiableSchema {
21
21
  return new NumberSchema(this.minValue, value);
22
22
  }
23
23
  parse(obj) {
24
- if (typeof obj !== 'number' && typeof obj !== 'string') {
24
+ if (typeof obj !== "number" && typeof obj !== "string") {
25
25
  throw new ValidationError([
26
26
  new Issue([], `Expected number but got ${typeof obj}`),
27
27
  ]);
28
28
  }
29
- const num = typeof obj === 'number' ? obj : Number.parseFloat(obj);
29
+ const num = typeof obj === "number" ? obj : Number.parseFloat(obj);
30
30
  if (Number.isNaN(num)) {
31
31
  throw new ValidationError([
32
32
  new Issue([], `Expected number but got ${typeof obj}`),
@@ -46,9 +46,9 @@ class NumberSchema extends ModifiableSchema {
46
46
  }
47
47
  documentation() {
48
48
  return {
49
- type: 'number',
50
- minimum: this.minValue,
51
- maximum: this.maxValue,
49
+ type: "number",
50
+ ...(this.minValue !== undefined && { minimum: this.minValue }),
51
+ ...(this.maxValue !== undefined && { maximum: this.maxValue }),
52
52
  };
53
53
  }
54
54
  }
@@ -1,6 +1,6 @@
1
- import type { Typeof } from './body.js';
2
- import { ModifiableSchema } from './modifiable.js';
3
- import { Schema } from './schema.js';
1
+ import type { Typeof } from "./body.js";
2
+ import { ModifiableSchema } from "./modifiable.js";
3
+ import { Schema } from "./schema.js";
4
4
  /**
5
5
  * Make a union of all keys that are not extended by undefined
6
6
  * (i.e. have undefined as a variant) of the specified type.
@@ -15,7 +15,7 @@ type MakeFieldsOptional<T> = Pick<T, RequiredKeys<T>> & Partial<T>;
15
15
  export declare class ObjectSchema<Shape extends Record<string, Schema<unknown>>> extends ModifiableSchema<MakeFieldsOptional<{
16
16
  [K in keyof Shape]: Typeof<Shape[K]>;
17
17
  }>> {
18
- private shape;
18
+ readonly shape: Shape;
19
19
  private lax;
20
20
  constructor(shape: Shape, lax: boolean);
21
21
  parse(obj: unknown): MakeFieldsOptional<{
@@ -1,6 +1,6 @@
1
- import { Issue, ValidationError } from './error.js';
2
- import { ModifiableSchema } from './modifiable.js';
3
- import { Schema } from './schema.js';
1
+ import { Issue, ValidationError } from "./error.js";
2
+ import { ModifiableSchema } from "./modifiable.js";
3
+ import { Schema } from "./schema.js";
4
4
  export class ObjectSchema extends ModifiableSchema {
5
5
  constructor(shape, lax) {
6
6
  super();
@@ -8,14 +8,14 @@ export class ObjectSchema extends ModifiableSchema {
8
8
  this.lax = lax;
9
9
  }
10
10
  parse(obj) {
11
- if (typeof obj !== 'object') {
11
+ if (typeof obj !== "object") {
12
12
  throw new ValidationError([
13
13
  new Issue([], `Expected object but got ${typeof obj}`),
14
14
  ]);
15
15
  }
16
16
  if (obj == null) {
17
17
  throw new ValidationError([
18
- new Issue([], 'Expected object but got null'),
18
+ new Issue([], "Expected object but got null"),
19
19
  ]);
20
20
  }
21
21
  const result = {};
@@ -24,7 +24,7 @@ export class ObjectSchema extends ModifiableSchema {
24
24
  const propSchema = this.shape[prop];
25
25
  if (propSchema instanceof Schema) {
26
26
  if (!(prop in obj || propSchema.isOptional())) {
27
- issues.push(new Issue([prop], 'Required'));
27
+ issues.push(new Issue([prop], "Required"));
28
28
  continue;
29
29
  }
30
30
  try {
@@ -45,7 +45,7 @@ export class ObjectSchema extends ModifiableSchema {
45
45
  if (prop in this.shape) {
46
46
  continue;
47
47
  }
48
- issues.push(new Issue([prop], 'Unrecognized'));
48
+ issues.push(new Issue([prop], "Unrecognized"));
49
49
  }
50
50
  }
51
51
  if (issues.length > 0) {
@@ -66,10 +66,10 @@ export class ObjectSchema extends ModifiableSchema {
66
66
  }
67
67
  }
68
68
  return {
69
- type: 'object',
69
+ type: "object",
70
70
  properties,
71
- required: required.length > 0 ? required : undefined,
72
- additionalProperties: this.lax,
71
+ ...(required.length > 0 && { required }),
72
+ ...(this.lax && { additionalProperties: true }),
73
73
  };
74
74
  }
75
75
  }
@@ -4,7 +4,7 @@ import { BodyType } from './body.js';
4
4
  /**
5
5
  * The base class for all schemas.
6
6
  */
7
- export declare abstract class Schema<T> extends BodyType<T, T> implements StandardSchemaV1<unknown, T> {
7
+ export declare abstract class Schema<T> extends BodyType<T, T> implements StandardSchemaV1<T, T> {
8
8
  deserialize(stream: Readable, contentType: string): Promise<T>;
9
9
  bodyDocs(): object;
10
10
  /**
@@ -31,5 +31,5 @@ export declare abstract class Schema<T> extends BodyType<T, T> implements Standa
31
31
  *
32
32
  * @see https://standardschema.dev/
33
33
  */
34
- get '~standard'(): StandardSchemaV1.Props<unknown, T>;
34
+ get '~standard'(): StandardSchemaV1.Props<T, T>;
35
35
  }
@@ -1,16 +1,24 @@
1
- import { ModifiableSchema } from './modifiable.js';
1
+ import { ModifiableSchema } from "./modifiable.js";
2
2
  declare class StringSchema extends ModifiableSchema<string> {
3
+ /**
4
+ * Set the minimum length the string is allowed to be.
5
+ * @param length - The minimum length.
6
+ */
7
+ min(length: number): import("./modifiable.js").RefinedSchema<string>;
8
+ /**
9
+ * Set the maximum length the string is allowed to be.
10
+ * @param length - The maximum length.
11
+ */
12
+ max(length: number): import("./modifiable.js").RefinedSchema<string>;
13
+ /**
14
+ * Require the string to be a valid email address.
15
+ */
16
+ email(): import("./modifiable.js").RefinedSchema<string>;
3
17
  /**
4
18
  * Require the string to match the specified pattern.
5
19
  * @param pattern - A regular expression.
6
20
  */
7
- pattern(pattern: RegExp): PatternStringSchema;
8
- parse(obj: unknown): string;
9
- documentation(): object;
10
- }
11
- declare class PatternStringSchema extends ModifiableSchema<string> {
12
- private readonly pattern;
13
- constructor(pattern: RegExp);
21
+ pattern(pattern: RegExp): import("./modifiable.js").RefinedSchema<string>;
14
22
  parse(obj: unknown): string;
15
23
  documentation(): object;
16
24
  }
@@ -1,49 +1,47 @@
1
- import { Issue, ValidationError } from './error.js';
2
- import { ModifiableSchema } from './modifiable.js';
1
+ import { Issue, ValidationError } from "./error.js";
2
+ import { ModifiableSchema } from "./modifiable.js";
3
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3
4
  class StringSchema extends ModifiableSchema {
4
5
  /**
5
- * Require the string to match the specified pattern.
6
- * @param pattern - A regular expression.
6
+ * Set the minimum length the string is allowed to be.
7
+ * @param length - The minimum length.
7
8
  */
8
- pattern(pattern) {
9
- return new PatternStringSchema(pattern);
9
+ min(length) {
10
+ return this.refine((s) => s.length >= length || `Must be at least ${length} characters`, { minLength: length });
10
11
  }
11
- parse(obj) {
12
- if (typeof obj !== 'string') {
13
- throw new ValidationError([
14
- new Issue([], `Expected string but got ${typeof obj}`),
15
- ]);
16
- }
17
- return obj;
12
+ /**
13
+ * Set the maximum length the string is allowed to be.
14
+ * @param length - The maximum length.
15
+ */
16
+ max(length) {
17
+ return this.refine((s) => s.length <= length || `Must be at most ${length} characters`, { maxLength: length });
18
18
  }
19
- documentation() {
20
- return {
21
- type: 'string',
22
- };
19
+ /**
20
+ * Require the string to be a valid email address.
21
+ */
22
+ email() {
23
+ return this.refine((s) => EMAIL_REGEX.test(s) || "Expected email address", {
24
+ format: "email",
25
+ });
23
26
  }
24
- }
25
- class PatternStringSchema extends ModifiableSchema {
26
- constructor(pattern) {
27
- super();
28
- this.pattern = pattern;
27
+ /**
28
+ * Require the string to match the specified pattern.
29
+ * @param pattern - A regular expression.
30
+ */
31
+ pattern(pattern) {
32
+ return this.refine((s) => pattern.test(s) || `Does not match pattern /${pattern.source}/`, { pattern: pattern.source });
29
33
  }
30
34
  parse(obj) {
31
- if (typeof obj !== 'string') {
35
+ if (typeof obj !== "string") {
32
36
  throw new ValidationError([
33
37
  new Issue([], `Expected string but got ${typeof obj}`),
34
38
  ]);
35
39
  }
36
- if (!obj.match(this.pattern)) {
37
- throw new ValidationError([
38
- new Issue([], `\`${obj}\` does not match pattern /${this.pattern.source}/`),
39
- ]);
40
- }
41
40
  return obj;
42
41
  }
43
42
  documentation() {
44
43
  return {
45
- type: 'string',
46
- pattern: this.pattern.source,
44
+ type: "string",
47
45
  };
48
46
  }
49
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yedra",
3
- "version": "0.20.2",
3
+ "version": "0.20.3",
4
4
  "repository": "github:0codekit/yedra",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -14,9 +14,9 @@
14
14
  }
15
15
  },
16
16
  "devDependencies": {
17
- "@biomejs/biome": "^2.3.14",
18
- "@types/bun": "^1.3.9",
19
- "@types/node": "^25.2.3",
17
+ "@biomejs/biome": "^2.3.12",
18
+ "@types/bun": "^1.3.6",
19
+ "@types/node": "^25.0.10",
20
20
  "@types/uuid": "^11.0.0",
21
21
  "@types/ws": "^8.18.1",
22
22
  "typescript": "^5.9.3"