@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.
- package/AGENTS.md +86 -0
- package/README.md +323 -0
- package/dist/base.d.ts +111 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +93 -0
- package/dist/base.js.map +1 -0
- package/dist/coerce/boolean.d.ts +37 -0
- package/dist/coerce/boolean.d.ts.map +1 -0
- package/dist/coerce/boolean.js +49 -0
- package/dist/coerce/boolean.js.map +1 -0
- package/dist/coerce/date.d.ts +36 -0
- package/dist/coerce/date.d.ts.map +1 -0
- package/dist/coerce/date.js +60 -0
- package/dist/coerce/date.js.map +1 -0
- package/dist/coerce/number.d.ts +36 -0
- package/dist/coerce/number.d.ts.map +1 -0
- package/dist/coerce/number.js +59 -0
- package/dist/coerce/number.js.map +1 -0
- package/dist/coerce/string.d.ts +35 -0
- package/dist/coerce/string.d.ts.map +1 -0
- package/dist/coerce/string.js +47 -0
- package/dist/coerce/string.js.map +1 -0
- package/dist/complex/array.d.ts +56 -0
- package/dist/complex/array.d.ts.map +1 -0
- package/dist/complex/array.js +96 -0
- package/dist/complex/array.js.map +1 -0
- package/dist/complex/object.d.ts +76 -0
- package/dist/complex/object.d.ts.map +1 -0
- package/dist/complex/object.js +104 -0
- package/dist/complex/object.js.map +1 -0
- package/dist/complex/record.d.ts +53 -0
- package/dist/complex/record.d.ts.map +1 -0
- package/dist/complex/record.js +109 -0
- package/dist/complex/record.js.map +1 -0
- package/dist/index.d.ts +151 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/dist/json-schema.d.ts +60 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +280 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/primitives/any.d.ts +44 -0
- package/dist/primitives/any.d.ts.map +1 -0
- package/dist/primitives/any.js +57 -0
- package/dist/primitives/any.js.map +1 -0
- package/dist/primitives/boolean.d.ts +39 -0
- package/dist/primitives/boolean.d.ts.map +1 -0
- package/dist/primitives/boolean.js +53 -0
- package/dist/primitives/boolean.js.map +1 -0
- package/dist/primitives/null.d.ts +26 -0
- package/dist/primitives/null.d.ts.map +1 -0
- package/dist/primitives/null.js +40 -0
- package/dist/primitives/null.js.map +1 -0
- package/dist/primitives/number.d.ts +87 -0
- package/dist/primitives/number.d.ts.map +1 -0
- package/dist/primitives/number.js +129 -0
- package/dist/primitives/number.js.map +1 -0
- package/dist/primitives/string.d.ts +64 -0
- package/dist/primitives/string.d.ts.map +1 -0
- package/dist/primitives/string.js +102 -0
- package/dist/primitives/string.js.map +1 -0
- package/dist/primitives/undefined.d.ts +26 -0
- package/dist/primitives/undefined.d.ts.map +1 -0
- package/dist/primitives/undefined.js +40 -0
- package/dist/primitives/undefined.js.map +1 -0
- package/dist/primitives/unknown.d.ts +47 -0
- package/dist/primitives/unknown.d.ts.map +1 -0
- package/dist/primitives/unknown.js +56 -0
- package/dist/primitives/unknown.js.map +1 -0
- package/dist/utils/literal.d.ts +47 -0
- package/dist/utils/literal.d.ts.map +1 -0
- package/dist/utils/literal.js +64 -0
- package/dist/utils/literal.js.map +1 -0
- package/dist/utils/nullable.d.ts +50 -0
- package/dist/utils/nullable.d.ts.map +1 -0
- package/dist/utils/nullable.js +69 -0
- package/dist/utils/nullable.js.map +1 -0
- package/dist/utils/optional.d.ts +50 -0
- package/dist/utils/optional.d.ts.map +1 -0
- package/dist/utils/optional.js +69 -0
- package/dist/utils/optional.js.map +1 -0
- package/dist/utils/union.d.ts +60 -0
- package/dist/utils/union.d.ts.map +1 -0
- package/dist/utils/union.js +87 -0
- package/dist/utils/union.js.map +1 -0
- package/package.json +39 -0
- package/src/__tests__/coerce.test.ts +88 -0
- package/src/__tests__/complex.test.ts +124 -0
- package/src/__tests__/errors.test.ts +129 -0
- package/src/__tests__/json-schema.test.ts +138 -0
- package/src/__tests__/primitives.test.ts +184 -0
- package/src/__tests__/type-inference.test.ts +68 -0
- package/src/__tests__/utils.test.ts +100 -0
- package/src/base.ts +185 -0
- package/src/coerce/boolean.ts +56 -0
- package/src/coerce/date.ts +68 -0
- package/src/coerce/number.ts +67 -0
- package/src/coerce/string.ts +54 -0
- package/src/complex/array.ts +108 -0
- package/src/complex/object.ts +141 -0
- package/src/complex/record.ts +129 -0
- package/src/index.ts +177 -0
- package/src/json-schema.ts +331 -0
- package/src/primitives/any.ts +64 -0
- package/src/primitives/boolean.ts +60 -0
- package/src/primitives/null.ts +47 -0
- package/src/primitives/number.ts +141 -0
- package/src/primitives/string.ts +113 -0
- package/src/primitives/undefined.ts +47 -0
- package/src/primitives/unknown.ts +63 -0
- package/src/utils/literal.ts +71 -0
- package/src/utils/nullable.ts +80 -0
- package/src/utils/optional.ts +80 -0
- package/src/utils/union.ts +103 -0
package/src/base.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@agentuity/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A validation issue from a failed schema validation.
|
|
5
|
+
*/
|
|
6
|
+
export type ValidationIssue = StandardSchemaV1.Issue;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The result of a schema validation (success or failure).
|
|
10
|
+
*/
|
|
11
|
+
export type ValidationResult<T> = StandardSchemaV1.Result<T>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Successful parse result containing validated data.
|
|
15
|
+
*/
|
|
16
|
+
export interface SafeParseSuccess<T> {
|
|
17
|
+
/** Indicates successful validation */
|
|
18
|
+
success: true;
|
|
19
|
+
/** The validated and typed data */
|
|
20
|
+
data: T;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Failed parse result containing validation error.
|
|
25
|
+
*/
|
|
26
|
+
export interface SafeParseError {
|
|
27
|
+
/** Indicates failed validation */
|
|
28
|
+
success: false;
|
|
29
|
+
/** The validation error with detailed issues */
|
|
30
|
+
error: ValidationError;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Result of safeParse - either success with data or failure with error.
|
|
35
|
+
*/
|
|
36
|
+
export type SafeParseResult<T> = SafeParseSuccess<T> | SafeParseError;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when schema validation fails.
|
|
40
|
+
* Contains detailed information about all validation issues including field paths.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* try {
|
|
45
|
+
* schema.parse(data);
|
|
46
|
+
* } catch (error) {
|
|
47
|
+
* if (error instanceof ValidationError) {
|
|
48
|
+
* console.log(error.message); // Human-readable error
|
|
49
|
+
* console.log(error.issues); // Detailed issues array
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export class ValidationError extends Error {
|
|
55
|
+
/** Array of validation issues with paths and messages */
|
|
56
|
+
readonly issues: readonly ValidationIssue[];
|
|
57
|
+
|
|
58
|
+
constructor(issues: readonly ValidationIssue[]) {
|
|
59
|
+
const message = issues
|
|
60
|
+
.map((issue) => {
|
|
61
|
+
const path = issue.path
|
|
62
|
+
? `[${issue.path
|
|
63
|
+
.map((p) =>
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
|
+
typeof p === 'object' ? (p as any).key : p
|
|
66
|
+
)
|
|
67
|
+
.join('.')}]`
|
|
68
|
+
: '';
|
|
69
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
70
|
+
})
|
|
71
|
+
.join('\n');
|
|
72
|
+
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = 'ValidationError';
|
|
75
|
+
this.issues = issues;
|
|
76
|
+
|
|
77
|
+
// Maintain proper stack trace for where our error was thrown
|
|
78
|
+
if (Error.captureStackTrace) {
|
|
79
|
+
Error.captureStackTrace(this, ValidationError);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
toString(): string {
|
|
84
|
+
return `${this.name}: ${this.message}`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Base schema interface that all schemas implement.
|
|
90
|
+
* Provides StandardSchema v1 compliance plus additional methods for parsing and description.
|
|
91
|
+
*/
|
|
92
|
+
export interface Schema<Input = unknown, Output = Input> extends StandardSchemaV1<Input, Output> {
|
|
93
|
+
readonly '~standard': StandardSchemaV1.Props<Input, Output>;
|
|
94
|
+
/** Optional description for documentation */
|
|
95
|
+
description?: string;
|
|
96
|
+
/** Add a description to the schema for documentation and JSON Schema */
|
|
97
|
+
describe(description: string): this;
|
|
98
|
+
/** Parse and validate data, throwing ValidationError on failure */
|
|
99
|
+
parse(value: unknown): Output;
|
|
100
|
+
/** Parse and validate data, returning result object without throwing */
|
|
101
|
+
safeParse(value: unknown): SafeParseResult<Output>;
|
|
102
|
+
/** Make this schema optional (allow undefined) */
|
|
103
|
+
optional(): Schema<Input | undefined, Output | undefined>;
|
|
104
|
+
/** Make this schema nullable (allow null) */
|
|
105
|
+
nullable(): Schema<Input | null, Output | null>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extract the output type from a schema (like zod's z.infer).
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const User = s.object({ name: s.string(), age: s.number() });
|
|
114
|
+
* type User = Infer<typeof User>; // { name: string; age: number }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
+
export type Infer<T extends Schema<any, any>> = StandardSchemaV1.InferOutput<T>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extract the input type from a schema.
|
|
122
|
+
*/
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
124
|
+
export type InferInput<T extends Schema<any, any>> = StandardSchemaV1.InferInput<T>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Extract the output type from a schema (alias for Infer).
|
|
128
|
+
*/
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
export type InferOutput<T extends Schema<any, any>> = StandardSchemaV1.InferOutput<T>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a validation issue with an optional field path.
|
|
134
|
+
*/
|
|
135
|
+
export function createIssue(
|
|
136
|
+
message: string,
|
|
137
|
+
path?: ReadonlyArray<PropertyKey | StandardSchemaV1.PathSegment>
|
|
138
|
+
): ValidationIssue {
|
|
139
|
+
return path ? { message, path } : { message };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create a successful validation result.
|
|
144
|
+
*/
|
|
145
|
+
export function success<T>(value: T): StandardSchemaV1.SuccessResult<T> {
|
|
146
|
+
return { value };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create a failed validation result.
|
|
151
|
+
*/
|
|
152
|
+
export function failure(issues: ValidationIssue[]): StandardSchemaV1.FailureResult {
|
|
153
|
+
return { issues };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create parse and safeParse methods for a schema.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
export function createParseMethods<Output>() {
|
|
161
|
+
return {
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
|
+
parse(this: Schema<any, Output>, value: unknown): Output {
|
|
164
|
+
const result = this['~standard'].validate(value);
|
|
165
|
+
if (result instanceof Promise) {
|
|
166
|
+
throw new Error('Async validation not supported in parse()');
|
|
167
|
+
}
|
|
168
|
+
if (result.issues) {
|
|
169
|
+
throw new ValidationError(result.issues);
|
|
170
|
+
}
|
|
171
|
+
return result.value;
|
|
172
|
+
},
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
|
+
safeParse(this: Schema<any, Output>, value: unknown): SafeParseResult<Output> {
|
|
175
|
+
const result = this['~standard'].validate(value);
|
|
176
|
+
if (result instanceof Promise) {
|
|
177
|
+
throw new Error('Async validation not supported in safeParse()');
|
|
178
|
+
}
|
|
179
|
+
if (result.issues) {
|
|
180
|
+
return { success: false, error: new ValidationError(result.issues) };
|
|
181
|
+
}
|
|
182
|
+
return { success: true, data: result.value };
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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<boolean>();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema that coerces values to booleans using Boolean(value).
|
|
10
|
+
* Uses JavaScript truthy/falsy rules.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const schema = s.coerce.boolean();
|
|
15
|
+
* schema.parse(1); // true
|
|
16
|
+
* schema.parse(0); // false
|
|
17
|
+
* schema.parse(''); // false
|
|
18
|
+
* schema.parse('hello'); // true
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export class CoerceBooleanSchema implements Schema<unknown, boolean> {
|
|
22
|
+
description?: string;
|
|
23
|
+
|
|
24
|
+
readonly '~standard' = {
|
|
25
|
+
version: 1 as const,
|
|
26
|
+
vendor: 'agentuity',
|
|
27
|
+
validate: (value: unknown) => {
|
|
28
|
+
// Coerce to boolean using JavaScript truthiness rules
|
|
29
|
+
return success(Boolean(value));
|
|
30
|
+
},
|
|
31
|
+
types: undefined as unknown as { input: unknown; output: boolean },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe(description: string): this {
|
|
35
|
+
this.description = description;
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
optional() {
|
|
40
|
+
return optional(this);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
nullable() {
|
|
44
|
+
return nullable(this);
|
|
45
|
+
}
|
|
46
|
+
parse = parseMethods.parse;
|
|
47
|
+
safeParse = parseMethods.safeParse;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a schema that coerces values to booleans.
|
|
52
|
+
* Useful for parsing checkboxes or boolean flags from strings.
|
|
53
|
+
*/
|
|
54
|
+
export function coerceBoolean(): CoerceBooleanSchema {
|
|
55
|
+
return new CoerceBooleanSchema();
|
|
56
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
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<Date>();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema that coerces values to Date objects using new Date(value).
|
|
10
|
+
* Fails if the result is an invalid date.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const schema = s.coerce.date();
|
|
15
|
+
* schema.parse('2025-01-01'); // Date object
|
|
16
|
+
* schema.parse(1609459200000); // Date from timestamp
|
|
17
|
+
* schema.parse('invalid'); // throws ValidationError
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class CoerceDateSchema implements Schema<unknown, Date> {
|
|
21
|
+
description?: string;
|
|
22
|
+
|
|
23
|
+
readonly '~standard' = {
|
|
24
|
+
version: 1 as const,
|
|
25
|
+
vendor: 'agentuity',
|
|
26
|
+
validate: (value: unknown) => {
|
|
27
|
+
// Already a Date
|
|
28
|
+
if (value instanceof Date) {
|
|
29
|
+
if (isNaN(value.getTime())) {
|
|
30
|
+
return failure([createIssue('Invalid date')]);
|
|
31
|
+
}
|
|
32
|
+
return success(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Coerce to Date
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
const coerced = new Date(value as any);
|
|
38
|
+
if (isNaN(coerced.getTime())) {
|
|
39
|
+
return failure([createIssue(`Cannot coerce ${typeof value} to date`)]);
|
|
40
|
+
}
|
|
41
|
+
return success(coerced);
|
|
42
|
+
},
|
|
43
|
+
types: undefined as unknown as { input: unknown; output: Date },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe(description: string): this {
|
|
47
|
+
this.description = description;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
optional() {
|
|
52
|
+
return optional(this);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
nullable() {
|
|
56
|
+
return nullable(this);
|
|
57
|
+
}
|
|
58
|
+
parse = parseMethods.parse;
|
|
59
|
+
safeParse = parseMethods.safeParse;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a schema that coerces values to Date objects.
|
|
64
|
+
* Useful for parsing ISO date strings or timestamps.
|
|
65
|
+
*/
|
|
66
|
+
export function coerceDate(): CoerceDateSchema {
|
|
67
|
+
return new CoerceDateSchema();
|
|
68
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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 that coerces values to numbers using Number(value).
|
|
10
|
+
* Fails if the result is NaN.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const schema = s.coerce.number();
|
|
15
|
+
* schema.parse('123'); // 123
|
|
16
|
+
* schema.parse(true); // 1
|
|
17
|
+
* schema.parse('abc'); // throws ValidationError
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class CoerceNumberSchema implements Schema<unknown, number> {
|
|
21
|
+
description?: string;
|
|
22
|
+
|
|
23
|
+
readonly '~standard' = {
|
|
24
|
+
version: 1 as const,
|
|
25
|
+
vendor: 'agentuity',
|
|
26
|
+
validate: (value: unknown) => {
|
|
27
|
+
// Already a number
|
|
28
|
+
if (typeof value === 'number') {
|
|
29
|
+
if (Number.isNaN(value)) {
|
|
30
|
+
return failure([createIssue('Cannot coerce NaN to number')]);
|
|
31
|
+
}
|
|
32
|
+
return success(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Coerce to number
|
|
36
|
+
const coerced = Number(value);
|
|
37
|
+
if (Number.isNaN(coerced)) {
|
|
38
|
+
return failure([createIssue(`Cannot coerce ${typeof value} to number`)]);
|
|
39
|
+
}
|
|
40
|
+
return success(coerced);
|
|
41
|
+
},
|
|
42
|
+
types: undefined as unknown as { input: unknown; output: number },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
describe(description: string): this {
|
|
46
|
+
this.description = description;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
optional() {
|
|
51
|
+
return optional(this);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
nullable() {
|
|
55
|
+
return nullable(this);
|
|
56
|
+
}
|
|
57
|
+
parse = parseMethods.parse;
|
|
58
|
+
safeParse = parseMethods.safeParse;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a schema that coerces values to numbers.
|
|
63
|
+
* Useful for parsing form data or query parameters where numbers come as strings.
|
|
64
|
+
*/
|
|
65
|
+
export function coerceNumber(): CoerceNumberSchema {
|
|
66
|
+
return new CoerceNumberSchema();
|
|
67
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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<string>();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema that coerces any value to a string using String(value).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const schema = s.coerce.string();
|
|
14
|
+
* schema.parse(123); // '123'
|
|
15
|
+
* schema.parse(true); // 'true'
|
|
16
|
+
* schema.parse(null); // 'null'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export class CoerceStringSchema implements Schema<unknown, string> {
|
|
20
|
+
description?: string;
|
|
21
|
+
|
|
22
|
+
readonly '~standard' = {
|
|
23
|
+
version: 1 as const,
|
|
24
|
+
vendor: 'agentuity',
|
|
25
|
+
validate: (value: unknown) => {
|
|
26
|
+
// Coerce to string
|
|
27
|
+
return success(String(value));
|
|
28
|
+
},
|
|
29
|
+
types: undefined as unknown as { input: unknown; output: string },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe(description: string): this {
|
|
33
|
+
this.description = description;
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
optional() {
|
|
38
|
+
return optional(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
nullable() {
|
|
42
|
+
return nullable(this);
|
|
43
|
+
}
|
|
44
|
+
parse = parseMethods.parse;
|
|
45
|
+
safeParse = parseMethods.safeParse;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a schema that coerces values to strings.
|
|
50
|
+
* Useful for parsing form data or query parameters.
|
|
51
|
+
*/
|
|
52
|
+
export function coerceString(): CoerceStringSchema {
|
|
53
|
+
return new CoerceStringSchema();
|
|
54
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Schema, Infer } 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 arrays with typed elements.
|
|
8
|
+
* Validates each element and collects all validation errors with array indices in paths.
|
|
9
|
+
*
|
|
10
|
+
* @template T - The schema type for array elements
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const tagsSchema = s.array(s.string());
|
|
15
|
+
* const tags = tagsSchema.parse(['tag1', 'tag2']);
|
|
16
|
+
*
|
|
17
|
+
* const usersSchema = s.array(s.object({
|
|
18
|
+
* name: s.string(),
|
|
19
|
+
* age: s.number()
|
|
20
|
+
* }));
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
export class ArraySchema<T extends Schema<any, any>>
|
|
25
|
+
implements Schema<Array<Infer<T>>, Array<Infer<T>>>
|
|
26
|
+
{
|
|
27
|
+
description?: string;
|
|
28
|
+
private parseMethods = createParseMethods<Array<Infer<T>>>();
|
|
29
|
+
|
|
30
|
+
constructor(private itemSchema: T) {}
|
|
31
|
+
|
|
32
|
+
readonly '~standard' = {
|
|
33
|
+
version: 1 as const,
|
|
34
|
+
vendor: 'agentuity',
|
|
35
|
+
validate: (value: unknown) => {
|
|
36
|
+
if (value === null) {
|
|
37
|
+
return failure([createIssue('Expected array, got null')]);
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(value)) {
|
|
40
|
+
return failure([createIssue(`Expected array, got ${typeof value}`)]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result: Infer<T>[] = [];
|
|
44
|
+
const issues: ReturnType<typeof createIssue>[] = [];
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < value.length; i++) {
|
|
47
|
+
const validation = this.itemSchema['~standard'].validate(value[i]);
|
|
48
|
+
|
|
49
|
+
// Only support synchronous validation for now
|
|
50
|
+
if (validation instanceof Promise) {
|
|
51
|
+
throw new Error('Async validation not supported');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (validation.issues) {
|
|
55
|
+
for (const issue of validation.issues) {
|
|
56
|
+
issues.push(createIssue(issue.message, issue.path ? [i, ...issue.path] : [i]));
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
result.push(validation.value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (issues.length > 0) {
|
|
64
|
+
return failure(issues);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return success(result);
|
|
68
|
+
},
|
|
69
|
+
types: undefined as unknown as { input: Array<Infer<T>>; output: Array<Infer<T>> },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
describe(description: string): this {
|
|
73
|
+
this.description = description;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
optional() {
|
|
78
|
+
return optional(this);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
nullable() {
|
|
82
|
+
return nullable(this);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
parse = this.parseMethods.parse;
|
|
86
|
+
safeParse = this.parseMethods.safeParse;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create an array schema with typed elements.
|
|
91
|
+
*
|
|
92
|
+
* @param itemSchema - The schema for validating each array element
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const stringArray = s.array(s.string());
|
|
97
|
+
* const tags = stringArray.parse(['tag1', 'tag2']);
|
|
98
|
+
*
|
|
99
|
+
* const userArray = s.array(s.object({
|
|
100
|
+
* name: s.string(),
|
|
101
|
+
* age: s.number()
|
|
102
|
+
* }));
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
export function array<T extends Schema<any, any>>(itemSchema: T): ArraySchema<T> {
|
|
107
|
+
return new ArraySchema(itemSchema);
|
|
108
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Schema, Infer } from '../base';
|
|
2
|
+
import { createIssue, failure, success, createParseMethods } from '../base';
|
|
3
|
+
import { optional, OptionalSchema } from '../utils/optional';
|
|
4
|
+
import { nullable } from '../utils/nullable';
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
type ObjectShape = Record<string, Schema<any, any>>;
|
|
8
|
+
|
|
9
|
+
// Helper to check if a schema is optional
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
type IsOptional<T> = T extends OptionalSchema<any> ? true : false;
|
|
12
|
+
|
|
13
|
+
// Split required and optional keys
|
|
14
|
+
type RequiredKeys<T extends ObjectShape> = {
|
|
15
|
+
[K in keyof T]: IsOptional<T[K]> extends true ? never : K;
|
|
16
|
+
}[keyof T];
|
|
17
|
+
|
|
18
|
+
type OptionalKeys<T extends ObjectShape> = {
|
|
19
|
+
[K in keyof T]: IsOptional<T[K]> extends true ? K : never;
|
|
20
|
+
}[keyof T];
|
|
21
|
+
|
|
22
|
+
// Infer object shape with proper optional handling
|
|
23
|
+
type InferObjectShape<T extends ObjectShape> = {
|
|
24
|
+
[K in RequiredKeys<T>]: Infer<T[K]>;
|
|
25
|
+
} & {
|
|
26
|
+
[K in OptionalKeys<T>]?: Infer<T[K]>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Schema for validating objects with typed properties.
|
|
31
|
+
* Validates each property according to its schema and collects all validation errors.
|
|
32
|
+
*
|
|
33
|
+
* @template T - The object shape definition
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const userSchema = s.object({
|
|
38
|
+
* name: s.string(),
|
|
39
|
+
* age: s.number(),
|
|
40
|
+
* email: s.string()
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* const user = userSchema.parse({
|
|
44
|
+
* name: 'John',
|
|
45
|
+
* age: 30,
|
|
46
|
+
* email: 'john@example.com'
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export class ObjectSchema<T extends ObjectShape>
|
|
51
|
+
implements Schema<InferObjectShape<T>, InferObjectShape<T>>
|
|
52
|
+
{
|
|
53
|
+
description?: string;
|
|
54
|
+
private parseMethods = createParseMethods<InferObjectShape<T>>();
|
|
55
|
+
|
|
56
|
+
constructor(private shape: T) {}
|
|
57
|
+
|
|
58
|
+
readonly '~standard' = {
|
|
59
|
+
version: 1 as const,
|
|
60
|
+
vendor: 'agentuity',
|
|
61
|
+
validate: (value: unknown) => {
|
|
62
|
+
if (value === null) {
|
|
63
|
+
return failure([createIssue('Expected object, got null')]);
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
return failure([createIssue('Expected object, got array')]);
|
|
67
|
+
}
|
|
68
|
+
if (typeof value !== 'object') {
|
|
69
|
+
return failure([createIssue(`Expected object, got ${typeof value}`)]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
|
+
const result: Record<string, any> = {};
|
|
74
|
+
const issues: ReturnType<typeof createIssue>[] = [];
|
|
75
|
+
|
|
76
|
+
for (const [key, schema] of Object.entries(this.shape)) {
|
|
77
|
+
const fieldValue = (value as Record<string, unknown>)[key];
|
|
78
|
+
const validation = schema['~standard'].validate(fieldValue);
|
|
79
|
+
|
|
80
|
+
// Only support synchronous validation for now
|
|
81
|
+
if (validation instanceof Promise) {
|
|
82
|
+
throw new Error('Async validation not supported');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (validation.issues) {
|
|
86
|
+
for (const issue of validation.issues) {
|
|
87
|
+
issues.push(
|
|
88
|
+
createIssue(issue.message, issue.path ? [key, ...issue.path] : [key])
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
result[key] = validation.value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (issues.length > 0) {
|
|
97
|
+
return failure(issues);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return success(result as InferObjectShape<T>);
|
|
101
|
+
},
|
|
102
|
+
types: undefined as unknown as { input: InferObjectShape<T>; output: InferObjectShape<T> },
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
describe(description: string): this {
|
|
106
|
+
this.description = description;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
optional() {
|
|
111
|
+
return optional(this);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
nullable() {
|
|
115
|
+
return nullable(this);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
parse = this.parseMethods.parse;
|
|
119
|
+
safeParse = this.parseMethods.safeParse;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create an object schema with typed properties.
|
|
124
|
+
*
|
|
125
|
+
* @param shape - Object defining the schema for each property
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const userSchema = s.object({
|
|
130
|
+
* name: s.string().describe('Full name'),
|
|
131
|
+
* age: s.number().describe('Age in years'),
|
|
132
|
+
* email: s.optional(s.string())
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* type User = s.infer<typeof userSchema>;
|
|
136
|
+
* const user = userSchema.parse(data);
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export function object<T extends ObjectShape>(shape: T): ObjectSchema<T> {
|
|
140
|
+
return new ObjectSchema(shape);
|
|
141
|
+
}
|