@agentuity/schema 0.0.69

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 (115) hide show
  1. package/AGENTS.md +86 -0
  2. package/README.md +323 -0
  3. package/dist/base.d.ts +111 -0
  4. package/dist/base.d.ts.map +1 -0
  5. package/dist/base.js +93 -0
  6. package/dist/base.js.map +1 -0
  7. package/dist/coerce/boolean.d.ts +37 -0
  8. package/dist/coerce/boolean.d.ts.map +1 -0
  9. package/dist/coerce/boolean.js +49 -0
  10. package/dist/coerce/boolean.js.map +1 -0
  11. package/dist/coerce/date.d.ts +36 -0
  12. package/dist/coerce/date.d.ts.map +1 -0
  13. package/dist/coerce/date.js +60 -0
  14. package/dist/coerce/date.js.map +1 -0
  15. package/dist/coerce/number.d.ts +36 -0
  16. package/dist/coerce/number.d.ts.map +1 -0
  17. package/dist/coerce/number.js +59 -0
  18. package/dist/coerce/number.js.map +1 -0
  19. package/dist/coerce/string.d.ts +35 -0
  20. package/dist/coerce/string.d.ts.map +1 -0
  21. package/dist/coerce/string.js +47 -0
  22. package/dist/coerce/string.js.map +1 -0
  23. package/dist/complex/array.d.ts +56 -0
  24. package/dist/complex/array.d.ts.map +1 -0
  25. package/dist/complex/array.js +96 -0
  26. package/dist/complex/array.js.map +1 -0
  27. package/dist/complex/object.d.ts +76 -0
  28. package/dist/complex/object.d.ts.map +1 -0
  29. package/dist/complex/object.js +104 -0
  30. package/dist/complex/object.js.map +1 -0
  31. package/dist/complex/record.d.ts +53 -0
  32. package/dist/complex/record.d.ts.map +1 -0
  33. package/dist/complex/record.js +109 -0
  34. package/dist/complex/record.js.map +1 -0
  35. package/dist/index.d.ts +151 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +128 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/json-schema.d.ts +60 -0
  40. package/dist/json-schema.d.ts.map +1 -0
  41. package/dist/json-schema.js +280 -0
  42. package/dist/json-schema.js.map +1 -0
  43. package/dist/primitives/any.d.ts +44 -0
  44. package/dist/primitives/any.d.ts.map +1 -0
  45. package/dist/primitives/any.js +57 -0
  46. package/dist/primitives/any.js.map +1 -0
  47. package/dist/primitives/boolean.d.ts +39 -0
  48. package/dist/primitives/boolean.d.ts.map +1 -0
  49. package/dist/primitives/boolean.js +53 -0
  50. package/dist/primitives/boolean.js.map +1 -0
  51. package/dist/primitives/null.d.ts +26 -0
  52. package/dist/primitives/null.d.ts.map +1 -0
  53. package/dist/primitives/null.js +40 -0
  54. package/dist/primitives/null.js.map +1 -0
  55. package/dist/primitives/number.d.ts +87 -0
  56. package/dist/primitives/number.d.ts.map +1 -0
  57. package/dist/primitives/number.js +129 -0
  58. package/dist/primitives/number.js.map +1 -0
  59. package/dist/primitives/string.d.ts +64 -0
  60. package/dist/primitives/string.d.ts.map +1 -0
  61. package/dist/primitives/string.js +102 -0
  62. package/dist/primitives/string.js.map +1 -0
  63. package/dist/primitives/undefined.d.ts +26 -0
  64. package/dist/primitives/undefined.d.ts.map +1 -0
  65. package/dist/primitives/undefined.js +40 -0
  66. package/dist/primitives/undefined.js.map +1 -0
  67. package/dist/primitives/unknown.d.ts +47 -0
  68. package/dist/primitives/unknown.d.ts.map +1 -0
  69. package/dist/primitives/unknown.js +56 -0
  70. package/dist/primitives/unknown.js.map +1 -0
  71. package/dist/utils/literal.d.ts +47 -0
  72. package/dist/utils/literal.d.ts.map +1 -0
  73. package/dist/utils/literal.js +64 -0
  74. package/dist/utils/literal.js.map +1 -0
  75. package/dist/utils/nullable.d.ts +50 -0
  76. package/dist/utils/nullable.d.ts.map +1 -0
  77. package/dist/utils/nullable.js +69 -0
  78. package/dist/utils/nullable.js.map +1 -0
  79. package/dist/utils/optional.d.ts +50 -0
  80. package/dist/utils/optional.d.ts.map +1 -0
  81. package/dist/utils/optional.js +69 -0
  82. package/dist/utils/optional.js.map +1 -0
  83. package/dist/utils/union.d.ts +60 -0
  84. package/dist/utils/union.d.ts.map +1 -0
  85. package/dist/utils/union.js +87 -0
  86. package/dist/utils/union.js.map +1 -0
  87. package/package.json +39 -0
  88. package/src/__tests__/coerce.test.ts +88 -0
  89. package/src/__tests__/complex.test.ts +124 -0
  90. package/src/__tests__/errors.test.ts +129 -0
  91. package/src/__tests__/json-schema.test.ts +138 -0
  92. package/src/__tests__/primitives.test.ts +184 -0
  93. package/src/__tests__/type-inference.test.ts +68 -0
  94. package/src/__tests__/utils.test.ts +100 -0
  95. package/src/base.ts +185 -0
  96. package/src/coerce/boolean.ts +56 -0
  97. package/src/coerce/date.ts +68 -0
  98. package/src/coerce/number.ts +67 -0
  99. package/src/coerce/string.ts +54 -0
  100. package/src/complex/array.ts +108 -0
  101. package/src/complex/object.ts +141 -0
  102. package/src/complex/record.ts +129 -0
  103. package/src/index.ts +177 -0
  104. package/src/json-schema.ts +331 -0
  105. package/src/primitives/any.ts +64 -0
  106. package/src/primitives/boolean.ts +60 -0
  107. package/src/primitives/null.ts +47 -0
  108. package/src/primitives/number.ts +141 -0
  109. package/src/primitives/string.ts +113 -0
  110. package/src/primitives/undefined.ts +47 -0
  111. package/src/primitives/unknown.ts +63 -0
  112. package/src/utils/literal.ts +71 -0
  113. package/src/utils/nullable.ts +80 -0
  114. package/src/utils/optional.ts +80 -0
  115. package/src/utils/union.ts +103 -0
@@ -0,0 +1,60 @@
1
+ import type { Schema } from '../base';
2
+ import { createIssue, failure, success, createParseMethods } from '../base';
3
+ import { optional } from '../utils/optional';
4
+ import { nullable } from '../utils/nullable';
5
+
6
+ const parseMethods = createParseMethods<boolean>();
7
+
8
+ /**
9
+ * Schema for validating boolean values.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const schema = s.boolean();
14
+ * const active = schema.parse(true); // true
15
+ * schema.parse('true'); // throws ValidationError
16
+ * ```
17
+ */
18
+ export class BooleanSchema implements Schema<boolean, boolean> {
19
+ description?: string;
20
+
21
+ readonly '~standard' = {
22
+ version: 1 as const,
23
+ vendor: 'agentuity',
24
+ validate: (value: unknown) => {
25
+ if (typeof value !== 'boolean') {
26
+ return failure([createIssue(`Expected boolean, got ${typeof value}`)]);
27
+ }
28
+ return success(value);
29
+ },
30
+ types: undefined as unknown as { input: boolean; output: boolean },
31
+ };
32
+
33
+ describe(description: string): this {
34
+ this.description = description;
35
+ return this;
36
+ }
37
+
38
+ optional() {
39
+ return optional(this);
40
+ }
41
+
42
+ nullable() {
43
+ return nullable(this);
44
+ }
45
+ parse = parseMethods.parse;
46
+ safeParse = parseMethods.safeParse;
47
+ }
48
+
49
+ /**
50
+ * Create a boolean schema.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const activeSchema = s.boolean().describe('Account status');
55
+ * const active = activeSchema.parse(true);
56
+ * ```
57
+ */
58
+ export function boolean(): BooleanSchema {
59
+ return new BooleanSchema();
60
+ }
@@ -0,0 +1,47 @@
1
+ import type { Schema } from '../base';
2
+ import { createIssue, failure, success, createParseMethods } from '../base';
3
+ import { optional } from '../utils/optional';
4
+ import { nullable } from '../utils/nullable';
5
+
6
+ const parseMethods = createParseMethods<null>();
7
+
8
+ /**
9
+ * Schema for validating null values.
10
+ */
11
+ export class NullSchema implements Schema<null, null> {
12
+ description?: string;
13
+
14
+ readonly '~standard' = {
15
+ version: 1 as const,
16
+ vendor: 'agentuity',
17
+ validate: (value: unknown) => {
18
+ if (value !== null) {
19
+ return failure([createIssue(`Expected null, got ${typeof value}`)]);
20
+ }
21
+ return success(value);
22
+ },
23
+ types: undefined as unknown as { input: null; output: null },
24
+ };
25
+
26
+ describe(description: string): this {
27
+ this.description = description;
28
+ return this;
29
+ }
30
+
31
+ optional() {
32
+ return optional(this);
33
+ }
34
+
35
+ nullable() {
36
+ return nullable(this);
37
+ }
38
+ parse = parseMethods.parse;
39
+ safeParse = parseMethods.safeParse;
40
+ }
41
+
42
+ /**
43
+ * Create a null schema.
44
+ */
45
+ export function null_(): NullSchema {
46
+ return new NullSchema();
47
+ }
@@ -0,0 +1,141 @@
1
+ import type { Schema } from '../base';
2
+ import { createIssue, failure, success, createParseMethods } from '../base';
3
+ import { optional } from '../utils/optional';
4
+ import { nullable } from '../utils/nullable';
5
+
6
+ const parseMethods = createParseMethods<number>();
7
+
8
+ /**
9
+ * Schema for validating number values.
10
+ * Rejects NaN values.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const schema = s.number();
15
+ * const age = schema.parse(30); // 30
16
+ * schema.parse('30'); // throws ValidationError
17
+ * schema.parse(NaN); // throws ValidationError
18
+ * ```
19
+ */
20
+ export class NumberSchema implements Schema<number, number> {
21
+ description?: string;
22
+ private _finite = false;
23
+ private _min?: number;
24
+ private _max?: number;
25
+
26
+ readonly '~standard' = {
27
+ version: 1 as const,
28
+ vendor: 'agentuity',
29
+ validate: (value: unknown) => {
30
+ if (typeof value !== 'number' || Number.isNaN(value)) {
31
+ return failure([createIssue(`Expected number, got ${typeof value}`)]);
32
+ }
33
+ if (this._finite && !Number.isFinite(value)) {
34
+ return failure([createIssue('Expected finite number (not Infinity or -Infinity)')]);
35
+ }
36
+ if (this._min !== undefined && value < this._min) {
37
+ return failure([createIssue(`Expected number >= ${this._min}, got ${value}`)]);
38
+ }
39
+ if (this._max !== undefined && value > this._max) {
40
+ return failure([createIssue(`Expected number <= ${this._max}, got ${value}`)]);
41
+ }
42
+ return success(value);
43
+ },
44
+ types: undefined as unknown as { input: number; output: number },
45
+ };
46
+
47
+ describe(description: string): this {
48
+ this.description = description;
49
+ return this;
50
+ }
51
+
52
+ /**
53
+ * Require the number to be finite (not Infinity, -Infinity, or NaN).
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const schema = s.number().finite();
58
+ * schema.parse(123); // 123
59
+ * schema.parse(Infinity); // throws ValidationError
60
+ * schema.parse(-Infinity); // throws ValidationError
61
+ * ```
62
+ */
63
+ finite(): NumberSchema {
64
+ const clone = this._clone();
65
+ clone._finite = true;
66
+ return clone;
67
+ }
68
+
69
+ /**
70
+ * Set minimum value (inclusive).
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const schema = s.number().min(0);
75
+ * schema.parse(5); // 5
76
+ * schema.parse(-1); // throws ValidationError
77
+ * ```
78
+ */
79
+ min(value: number): NumberSchema {
80
+ const clone = this._clone();
81
+ clone._min = value;
82
+ return clone;
83
+ }
84
+
85
+ /**
86
+ * Set maximum value (inclusive).
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const schema = s.number().max(100);
91
+ * schema.parse(50); // 50
92
+ * schema.parse(101); // throws ValidationError
93
+ * ```
94
+ */
95
+ max(value: number): NumberSchema {
96
+ const clone = this._clone();
97
+ clone._max = value;
98
+ return clone;
99
+ }
100
+
101
+ optional() {
102
+ return optional(this);
103
+ }
104
+
105
+ nullable() {
106
+ return nullable(this);
107
+ }
108
+
109
+ private _clone(): NumberSchema {
110
+ const clone = new NumberSchema();
111
+ clone.description = this.description;
112
+ clone._finite = this._finite;
113
+ clone._min = this._min;
114
+ clone._max = this._max;
115
+ return clone;
116
+ }
117
+
118
+ parse = parseMethods.parse;
119
+ safeParse = parseMethods.safeParse;
120
+ }
121
+
122
+ /**
123
+ * Create a number schema.
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const ageSchema = s.number().describe('User age');
128
+ * const age = ageSchema.parse(30);
129
+ *
130
+ * const finiteSchema = s.number().finite();
131
+ * finiteSchema.parse(123); // OK
132
+ * finiteSchema.parse(Infinity); // throws
133
+ *
134
+ * const rangeSchema = s.number().min(0).max(100);
135
+ * rangeSchema.parse(50); // OK
136
+ * rangeSchema.parse(101); // throws
137
+ * ```
138
+ */
139
+ export function number(): NumberSchema {
140
+ return new NumberSchema();
141
+ }
@@ -0,0 +1,113 @@
1
+ import type { Schema } from '../base';
2
+ import { createIssue, failure, success, createParseMethods } from '../base';
3
+ import { optional } from '../utils/optional';
4
+ import { nullable } from '../utils/nullable';
5
+
6
+ const parseMethods = createParseMethods<string>();
7
+
8
+ /**
9
+ * Schema for validating string values.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const schema = s.string();
14
+ * const name = schema.parse('John'); // "John"
15
+ * schema.parse(123); // throws ValidationError
16
+ * ```
17
+ */
18
+ export class StringSchema implements Schema<string, string> {
19
+ description?: string;
20
+ private _min?: number;
21
+ private _max?: number;
22
+
23
+ readonly '~standard' = {
24
+ version: 1 as const,
25
+ vendor: 'agentuity',
26
+ validate: (value: unknown) => {
27
+ if (typeof value !== 'string') {
28
+ return failure([createIssue(`Expected string, got ${typeof value}`)]);
29
+ }
30
+ if (this._min !== undefined && value.length < this._min) {
31
+ return failure([
32
+ createIssue(`String must be at least ${this._min} characters, got ${value.length}`),
33
+ ]);
34
+ }
35
+ if (this._max !== undefined && value.length > this._max) {
36
+ return failure([
37
+ createIssue(`String must be at most ${this._max} characters, got ${value.length}`),
38
+ ]);
39
+ }
40
+ return success(value);
41
+ },
42
+ types: undefined as unknown as { input: string; output: string },
43
+ };
44
+
45
+ describe(description: string): this {
46
+ this.description = description;
47
+ return this;
48
+ }
49
+
50
+ /**
51
+ * Set minimum length.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const schema = s.string().min(3);
56
+ * schema.parse('hello'); // "hello"
57
+ * schema.parse('hi'); // throws ValidationError
58
+ * ```
59
+ */
60
+ min(length: number): StringSchema {
61
+ const clone = this._clone();
62
+ clone._min = length;
63
+ return clone;
64
+ }
65
+
66
+ /**
67
+ * Set maximum length.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const schema = s.string().max(10);
72
+ * schema.parse('hello'); // "hello"
73
+ * schema.parse('hello world'); // throws ValidationError
74
+ * ```
75
+ */
76
+ max(length: number): StringSchema {
77
+ const clone = this._clone();
78
+ clone._max = length;
79
+ return clone;
80
+ }
81
+
82
+ optional() {
83
+ return optional(this);
84
+ }
85
+
86
+ nullable() {
87
+ return nullable(this);
88
+ }
89
+
90
+ private _clone(): StringSchema {
91
+ const clone = new StringSchema();
92
+ clone.description = this.description;
93
+ clone._min = this._min;
94
+ clone._max = this._max;
95
+ return clone;
96
+ }
97
+
98
+ parse = parseMethods.parse;
99
+ safeParse = parseMethods.safeParse;
100
+ }
101
+
102
+ /**
103
+ * Create a string schema.
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const nameSchema = s.string().describe('User name');
108
+ * const name = nameSchema.parse('John');
109
+ * ```
110
+ */
111
+ export function string(): StringSchema {
112
+ return new StringSchema();
113
+ }
@@ -0,0 +1,47 @@
1
+ import type { Schema } from '../base';
2
+ import { createIssue, failure, success, createParseMethods } from '../base';
3
+ import { optional } from '../utils/optional';
4
+ import { nullable } from '../utils/nullable';
5
+
6
+ const parseMethods = createParseMethods<undefined>();
7
+
8
+ /**
9
+ * Schema for validating undefined values.
10
+ */
11
+ export class UndefinedSchema implements Schema<undefined, undefined> {
12
+ description?: string;
13
+
14
+ readonly '~standard' = {
15
+ version: 1 as const,
16
+ vendor: 'agentuity',
17
+ validate: (value: unknown) => {
18
+ if (value !== undefined) {
19
+ return failure([createIssue(`Expected undefined, got ${typeof value}`)]);
20
+ }
21
+ return success(value);
22
+ },
23
+ types: undefined as unknown as { input: undefined; output: undefined },
24
+ };
25
+
26
+ describe(description: string): this {
27
+ this.description = description;
28
+ return this;
29
+ }
30
+
31
+ optional() {
32
+ return optional(this);
33
+ }
34
+
35
+ nullable() {
36
+ return nullable(this);
37
+ }
38
+ parse = parseMethods.parse;
39
+ safeParse = parseMethods.safeParse;
40
+ }
41
+
42
+ /**
43
+ * Create an undefined schema.
44
+ */
45
+ export function undefined_(): UndefinedSchema {
46
+ return new UndefinedSchema();
47
+ }
@@ -0,0 +1,63 @@
1
+ import type { Schema } from '../base';
2
+ import { success, createParseMethods } from '../base';
3
+ import { optional } from '../utils/optional';
4
+ import { nullable } from '../utils/nullable';
5
+
6
+ const parseMethods = createParseMethods<unknown>();
7
+
8
+ /**
9
+ * Schema that accepts any value with type-safe unknown.
10
+ * Returns the value as-is without validation.
11
+ * Use this when you want to accept any value but force type checking at usage site.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const schema = s.unknown();
16
+ * const value = schema.parse(123); // unknown
17
+ * const value2 = schema.parse('hello'); // unknown
18
+ * const value3 = schema.parse(null); // unknown
19
+ *
20
+ * // Forces type narrowing
21
+ * if (typeof value === 'string') {
22
+ * console.log(value.toUpperCase());
23
+ * }
24
+ * ```
25
+ */
26
+ export class UnknownSchema implements Schema<unknown, unknown> {
27
+ description?: string;
28
+
29
+ readonly '~standard' = {
30
+ version: 1 as const,
31
+ vendor: 'agentuity',
32
+ validate: (value: unknown) => success(value),
33
+ types: undefined as unknown as { input: unknown; output: unknown },
34
+ };
35
+
36
+ describe(description: string): this {
37
+ this.description = description;
38
+ return this;
39
+ }
40
+
41
+ optional() {
42
+ return optional(this);
43
+ }
44
+
45
+ nullable() {
46
+ return nullable(this);
47
+ }
48
+ parse = parseMethods.parse;
49
+ safeParse = parseMethods.safeParse;
50
+ }
51
+
52
+ /**
53
+ * Create an unknown schema that accepts any value.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const schema = s.unknown();
58
+ * const value = schema.parse(anything); // Type is unknown
59
+ * ```
60
+ */
61
+ export function unknown(): UnknownSchema {
62
+ return new UnknownSchema();
63
+ }
@@ -0,0 +1,71 @@
1
+ import type { Schema } from '../base';
2
+ import { createIssue, failure, success, createParseMethods } from '../base';
3
+ import { optional } from '../utils/optional';
4
+ import { nullable } from '../utils/nullable';
5
+
6
+ /**
7
+ * Schema for validating exact literal values.
8
+ *
9
+ * @template T - The exact value type
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const adminSchema = s.literal('admin');
14
+ * adminSchema.parse('admin'); // 'admin'
15
+ * adminSchema.parse('user'); // throws ValidationError
16
+ * ```
17
+ */
18
+ export class LiteralSchema<T extends string | number | boolean> implements Schema<T, T> {
19
+ description?: string;
20
+ private parseMethods = createParseMethods<T>();
21
+
22
+ constructor(private value: T) {}
23
+
24
+ readonly '~standard' = {
25
+ version: 1 as const,
26
+ vendor: 'agentuity',
27
+ validate: (input: unknown) => {
28
+ if (input !== this.value) {
29
+ return failure([
30
+ createIssue(
31
+ `Expected literal value ${JSON.stringify(this.value)}, got ${JSON.stringify(input)}`
32
+ ),
33
+ ]);
34
+ }
35
+ return success(this.value);
36
+ },
37
+ types: undefined as unknown as { input: T; output: T },
38
+ };
39
+
40
+ describe(description: string): this {
41
+ this.description = description;
42
+ return this;
43
+ }
44
+
45
+ optional() {
46
+ return optional(this);
47
+ }
48
+
49
+ nullable() {
50
+ return nullable(this);
51
+ }
52
+
53
+ parse = this.parseMethods.parse;
54
+ safeParse = this.parseMethods.safeParse;
55
+ }
56
+
57
+ /**
58
+ * Create a schema for an exact literal value.
59
+ *
60
+ * @param value - The exact value to match
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const adminRole = s.literal('admin');
65
+ * const maxValue = s.literal(100);
66
+ * const enabled = s.literal(true);
67
+ * ```
68
+ */
69
+ export function literal<T extends string | number | boolean>(value: T): LiteralSchema<T> {
70
+ return new LiteralSchema(value);
71
+ }
@@ -0,0 +1,80 @@
1
+ import type { Schema, Infer } from '../base';
2
+ import { success, createParseMethods } from '../base';
3
+
4
+ /**
5
+ * Schema for nullable values (T | null).
6
+ * Accepts null or the wrapped schema's type.
7
+ *
8
+ * @template T - The wrapped schema type
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const schema = s.nullable(s.string());
13
+ * schema.parse('hello'); // 'hello'
14
+ * schema.parse(null); // null
15
+ * schema.parse(123); // throws ValidationError
16
+ * ```
17
+ */
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ export class NullableSchema<T extends Schema<any, any>>
20
+ implements Schema<Infer<T> | null, Infer<T> | null>
21
+ {
22
+ readonly schema: T;
23
+ description?: string;
24
+
25
+ readonly '~standard' = {
26
+ version: 1 as const,
27
+ vendor: 'agentuity',
28
+ validate: (value: unknown) => {
29
+ if (value === null) {
30
+ return success(null as Infer<T> | null);
31
+ }
32
+ return this.schema['~standard'].validate(value);
33
+ },
34
+ types: undefined as unknown as { input: Infer<T> | null; output: Infer<T> | null },
35
+ };
36
+
37
+ // Type-safe parse methods for this instance
38
+ private parseMethods = createParseMethods<Infer<T> | null>();
39
+
40
+ constructor(schema: T) {
41
+ this.schema = schema;
42
+ }
43
+
44
+ describe(description: string): this {
45
+ this.description = description;
46
+ return this;
47
+ }
48
+
49
+ optional(): Schema<Infer<T> | null | undefined, Infer<T> | null | undefined> {
50
+ // Import here to avoid circular dependency
51
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
52
+ const { optional } = require('./optional.js');
53
+ return optional(this);
54
+ }
55
+
56
+ nullable() {
57
+ return this; // Already nullable
58
+ }
59
+
60
+ parse = this.parseMethods.parse;
61
+ safeParse = this.parseMethods.safeParse;
62
+ }
63
+
64
+ /**
65
+ * Make a schema nullable (T | null).
66
+ *
67
+ * @param schema - The schema to make nullable
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const userSchema = s.object({
72
+ * name: s.string(),
73
+ * bio: s.nullable(s.string())
74
+ * });
75
+ * ```
76
+ */
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ export function nullable<T extends Schema<any, any>>(schema: T): NullableSchema<T> {
79
+ return new NullableSchema(schema);
80
+ }
@@ -0,0 +1,80 @@
1
+ import type { Schema, Infer } from '../base';
2
+ import { success, createParseMethods } from '../base';
3
+
4
+ /**
5
+ * Schema for optional values (T | undefined).
6
+ * Accepts undefined or the wrapped schema's type.
7
+ *
8
+ * @template T - The wrapped schema type
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const schema = s.optional(s.string());
13
+ * schema.parse('hello'); // 'hello'
14
+ * schema.parse(undefined); // undefined
15
+ * schema.parse(123); // throws ValidationError
16
+ * ```
17
+ */
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ export class OptionalSchema<T extends Schema<any, any>>
20
+ implements Schema<Infer<T> | undefined, Infer<T> | undefined>
21
+ {
22
+ readonly schema: T;
23
+ description?: string;
24
+
25
+ readonly '~standard' = {
26
+ version: 1 as const,
27
+ vendor: 'agentuity',
28
+ validate: (value: unknown) => {
29
+ if (value === undefined) {
30
+ return success(undefined as Infer<T> | undefined);
31
+ }
32
+ return this.schema['~standard'].validate(value);
33
+ },
34
+ types: undefined as unknown as { input: Infer<T> | undefined; output: Infer<T> | undefined },
35
+ };
36
+
37
+ // Type-safe parse methods for this instance
38
+ private parseMethods = createParseMethods<Infer<T> | undefined>();
39
+
40
+ constructor(schema: T) {
41
+ this.schema = schema;
42
+ }
43
+
44
+ describe(description: string): this {
45
+ this.description = description;
46
+ return this;
47
+ }
48
+
49
+ optional() {
50
+ return this; // Already optional
51
+ }
52
+
53
+ nullable(): Schema<Infer<T> | undefined | null, Infer<T> | undefined | null> {
54
+ // Import here to avoid circular dependency
55
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
56
+ const { nullable } = require('./nullable.js');
57
+ return nullable(this);
58
+ }
59
+
60
+ parse = this.parseMethods.parse;
61
+ safeParse = this.parseMethods.safeParse;
62
+ }
63
+
64
+ /**
65
+ * Make a schema optional (T | undefined).
66
+ *
67
+ * @param schema - The schema to make optional
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const userSchema = s.object({
72
+ * name: s.string(),
73
+ * nickname: s.optional(s.string())
74
+ * });
75
+ * ```
76
+ */
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ export function optional<T extends Schema<any, any>>(schema: T): OptionalSchema<T> {
79
+ return new OptionalSchema(schema);
80
+ }