@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,129 @@
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 records (objects with string keys and typed values).
8
+ * Like TypeScript's Record<string, T> type.
9
+ *
10
+ * @template K - The key schema (must be string)
11
+ * @template V - The value schema
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const schema = s.record(s.string(), s.number());
16
+ * schema.parse({ a: 1, b: 2 }); // { a: 1, b: 2 }
17
+ * schema.parse({ a: 'invalid' }); // throws ValidationError
18
+ * ```
19
+ */
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ export class RecordSchema<K extends Schema<string, string>, V extends Schema<any, any>>
22
+ implements Schema<Record<Infer<K>, Infer<V>>, Record<Infer<K>, Infer<V>>>
23
+ {
24
+ description?: string;
25
+ private recordParseMethods = createParseMethods<Record<Infer<K>, Infer<V>>>();
26
+
27
+ constructor(
28
+ private keySchema: K,
29
+ private valueSchema: V
30
+ ) {}
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 record, got null')]);
38
+ }
39
+ if (Array.isArray(value)) {
40
+ return failure([createIssue('Expected record, got array')]);
41
+ }
42
+ if (typeof value !== 'object') {
43
+ return failure([createIssue(`Expected record, got ${typeof value}`)]);
44
+ }
45
+
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ const result: Record<string, any> = {};
48
+ const issues: ReturnType<typeof createIssue>[] = [];
49
+
50
+ for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
51
+ // Validate key
52
+ const keyValidation = this.keySchema['~standard'].validate(key);
53
+ if (keyValidation instanceof Promise) {
54
+ throw new Error('Async validation not supported');
55
+ }
56
+ if (keyValidation.issues) {
57
+ for (const issue of keyValidation.issues) {
58
+ issues.push(createIssue(`Invalid key "${key}": ${issue.message}`, [key]));
59
+ }
60
+ continue;
61
+ }
62
+
63
+ // Validate value
64
+ const valueValidation = this.valueSchema['~standard'].validate(val);
65
+ if (valueValidation instanceof Promise) {
66
+ throw new Error('Async validation not supported');
67
+ }
68
+ if (valueValidation.issues) {
69
+ for (const issue of valueValidation.issues) {
70
+ issues.push(
71
+ createIssue(issue.message, issue.path ? [key, ...issue.path] : [key])
72
+ );
73
+ }
74
+ } else {
75
+ result[key] = valueValidation.value;
76
+ }
77
+ }
78
+
79
+ if (issues.length > 0) {
80
+ return failure(issues);
81
+ }
82
+
83
+ return success(result as Record<Infer<K>, Infer<V>>);
84
+ },
85
+ types: undefined as unknown as {
86
+ input: Record<Infer<K>, Infer<V>>;
87
+ output: Record<Infer<K>, Infer<V>>;
88
+ },
89
+ };
90
+
91
+ describe(description: string): this {
92
+ this.description = description;
93
+ return this;
94
+ }
95
+
96
+ optional() {
97
+ return optional(this);
98
+ }
99
+
100
+ nullable() {
101
+ return nullable(this);
102
+ }
103
+
104
+ parse = this.recordParseMethods.parse;
105
+ safeParse = this.recordParseMethods.safeParse;
106
+ }
107
+
108
+ /**
109
+ * Create a record schema for objects with string keys and typed values.
110
+ *
111
+ * @param keySchema - Schema for keys (typically s.string())
112
+ * @param valueSchema - Schema for values
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const configSchema = s.record(s.string(), s.number());
117
+ * const config = configSchema.parse({ timeout: 30, retries: 3 });
118
+ *
119
+ * const metadataSchema = s.record(s.string(), s.unknown());
120
+ * const metadata = metadataSchema.parse({ any: 'data', here: 123 });
121
+ * ```
122
+ */
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ export function record<K extends Schema<string, string>, V extends Schema<any, any>>(
125
+ keySchema: K,
126
+ valueSchema: V
127
+ ): RecordSchema<K, V> {
128
+ return new RecordSchema(keySchema, valueSchema);
129
+ }
package/src/index.ts ADDED
@@ -0,0 +1,177 @@
1
+ export type {
2
+ Schema,
3
+ Infer,
4
+ InferInput,
5
+ InferOutput,
6
+ ValidationIssue,
7
+ ValidationResult,
8
+ SafeParseResult,
9
+ SafeParseSuccess,
10
+ SafeParseError,
11
+ } from './base';
12
+ export { createIssue, success, failure, ValidationError } from './base';
13
+
14
+ export { StringSchema, string } from './primitives/string';
15
+ export { NumberSchema, number } from './primitives/number';
16
+ export { BooleanSchema, boolean } from './primitives/boolean';
17
+ export { NullSchema, null_ } from './primitives/null';
18
+ export { UndefinedSchema, undefined_ } from './primitives/undefined';
19
+ export { UnknownSchema, unknown } from './primitives/unknown';
20
+ export { AnySchema, any } from './primitives/any';
21
+
22
+ export { ObjectSchema, object } from './complex/object';
23
+ export { ArraySchema, array } from './complex/array';
24
+ export { RecordSchema, record } from './complex/record';
25
+
26
+ export { LiteralSchema, literal } from './utils/literal';
27
+ export { OptionalSchema, optional } from './utils/optional';
28
+ export { NullableSchema, nullable } from './utils/nullable';
29
+ export { UnionSchema, union } from './utils/union';
30
+
31
+ export { toJSONSchema, fromJSONSchema, type JSONSchema } from './json-schema';
32
+
33
+ export { CoerceStringSchema, coerceString } from './coerce/string';
34
+ export { CoerceNumberSchema, coerceNumber } from './coerce/number';
35
+ export { CoerceBooleanSchema, coerceBoolean } from './coerce/boolean';
36
+ export { CoerceDateSchema, coerceDate } from './coerce/date';
37
+
38
+ import { string } from './primitives/string';
39
+ import { number } from './primitives/number';
40
+ import { boolean } from './primitives/boolean';
41
+ import { null_ } from './primitives/null';
42
+ import { undefined_ } from './primitives/undefined';
43
+ import { unknown } from './primitives/unknown';
44
+ import { any } from './primitives/any';
45
+ import { object } from './complex/object';
46
+ import { array } from './complex/array';
47
+ import { record } from './complex/record';
48
+ import { literal } from './utils/literal';
49
+ import { optional } from './utils/optional';
50
+ import { nullable } from './utils/nullable';
51
+ import { union } from './utils/union';
52
+ import { toJSONSchema, fromJSONSchema } from './json-schema';
53
+ import { coerceString } from './coerce/string';
54
+ import { coerceNumber } from './coerce/number';
55
+ import { coerceBoolean } from './coerce/boolean';
56
+ import { coerceDate } from './coerce/date';
57
+
58
+ import type { Infer as InferType, Schema } from './base';
59
+
60
+ /**
61
+ * Create an enum schema (union of literal values).
62
+ * Shorthand for creating a union of literals.
63
+ *
64
+ * @param values - Array of literal values
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const roleSchema = s.enum(['admin', 'user', 'guest']);
69
+ * const role = roleSchema.parse('admin'); // 'admin'
70
+ *
71
+ * // Equivalent to:
72
+ * s.union(s.literal('admin'), s.literal('user'), s.literal('guest'))
73
+ * ```
74
+ */
75
+ function enumSchema<
76
+ T extends readonly [string | number | boolean, ...(string | number | boolean)[]],
77
+ >(values: T) {
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ return union(...values.map((v) => literal(v as any)));
80
+ }
81
+
82
+ /**
83
+ * Main schema builder object.
84
+ * Provides access to all schema types and utilities.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * import { s } from '@agentuity/schema';
89
+ *
90
+ * // Define a schema
91
+ * const User = s.object({
92
+ * name: s.string(),
93
+ * age: s.number(),
94
+ * role: s.enum(['admin', 'user'])
95
+ * });
96
+ *
97
+ * // Extract type
98
+ * type User = s.infer<typeof User>;
99
+ *
100
+ * // Parse data
101
+ * const user = User.parse(data);
102
+ * ```
103
+ */
104
+ export const s = {
105
+ /** Create a string schema */
106
+ string,
107
+ /** Create a number schema */
108
+ number,
109
+ /** Create a boolean schema */
110
+ boolean,
111
+ /** Create a null schema */
112
+ null: null_,
113
+ /** Create an undefined schema */
114
+ undefined: undefined_,
115
+ /** Create an unknown schema (accepts any value) */
116
+ unknown,
117
+ /** Create an any schema (accepts any value) */
118
+ any,
119
+ /** Create an object schema with typed properties */
120
+ object,
121
+ /** Create an array schema with typed elements */
122
+ array,
123
+ /** Create a record schema (object with string keys and typed values) */
124
+ record,
125
+ /** Create a literal value schema */
126
+ literal,
127
+ /** Make a schema optional (T | undefined) */
128
+ optional,
129
+ /** Make a schema nullable (T | null) */
130
+ nullable,
131
+ /** Create a union of schemas */
132
+ union,
133
+ /** Create an enum schema (union of literals) */
134
+ enum: enumSchema,
135
+ /** Convert schema to JSON Schema format */
136
+ toJSONSchema,
137
+ /** Convert JSON Schema to schema */
138
+ fromJSONSchema,
139
+ /** Coercion schemas for type conversion */
140
+ coerce: {
141
+ /** Coerce to string using String(value) */
142
+ string: coerceString,
143
+ /** Coerce to number using Number(value) */
144
+ number: coerceNumber,
145
+ /** Coerce to boolean using Boolean(value) */
146
+ boolean: coerceBoolean,
147
+ /** Coerce to Date using new Date(value) */
148
+ date: coerceDate,
149
+ },
150
+ };
151
+
152
+ /**
153
+ * Namespace for s.infer type extraction (like zod's z.infer).
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * const User = s.object({ name: s.string(), age: s.number() });
158
+ * type User = s.infer<typeof User>;
159
+ * ```
160
+ */
161
+ // eslint-disable-next-line @typescript-eslint/no-namespace
162
+ export namespace s {
163
+ /**
164
+ * Extract the TypeScript type from a schema (like zod's z.infer).
165
+ *
166
+ * @template T - The schema type
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const Player = s.object({ username: s.string(), xp: s.number() });
171
+ * type Player = s.infer<typeof Player>;
172
+ * // { username: string; xp: number }
173
+ * ```
174
+ */
175
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
176
+ export type infer<T extends Schema<any, any>> = InferType<T>;
177
+ }
@@ -0,0 +1,331 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { Schema } from './base';
3
+ import { StringSchema, string } from './primitives/string';
4
+ import { NumberSchema, number } from './primitives/number';
5
+ import { BooleanSchema, boolean } from './primitives/boolean';
6
+ import { NullSchema, null_ } from './primitives/null';
7
+ import { UndefinedSchema } from './primitives/undefined';
8
+ import { ObjectSchema, object } from './complex/object';
9
+ import { ArraySchema, array } from './complex/array';
10
+ import { LiteralSchema, literal } from './utils/literal';
11
+ import { OptionalSchema, optional } from './utils/optional';
12
+ import { NullableSchema, nullable } from './utils/nullable';
13
+ import { UnionSchema, union } from './utils/union';
14
+
15
+ /**
16
+ * JSON Schema object representation.
17
+ * Subset of JSON Schema Draft 7 specification.
18
+ */
19
+ export interface JSONSchema {
20
+ type?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
21
+ description?: string;
22
+ const?: string | number | boolean;
23
+ enum?: Array<string | number | boolean>;
24
+ properties?: Record<string, JSONSchema>;
25
+ required?: string[];
26
+ items?: JSONSchema;
27
+ anyOf?: JSONSchema[];
28
+ oneOf?: JSONSchema[];
29
+ allOf?: JSONSchema[];
30
+ }
31
+
32
+ /**
33
+ * Convert a schema to JSON Schema format.
34
+ * Supports primitives, objects, arrays, unions, literals, optional, and nullable types.
35
+ *
36
+ * @param schema - The schema to convert
37
+ * @returns JSON Schema object
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const userSchema = s.object({
42
+ * name: s.string().describe('User name'),
43
+ * age: s.number().describe('User age')
44
+ * });
45
+ *
46
+ * const jsonSchema = s.toJSONSchema(userSchema);
47
+ * // { type: 'object', properties: {...}, required: [...] }
48
+ * ```
49
+ */
50
+ export function toJSONSchema(schema: Schema<any, any>): JSONSchema {
51
+ const result: JSONSchema = {};
52
+
53
+ // Add description if available
54
+ if (schema.description) {
55
+ result.description = schema.description;
56
+ }
57
+
58
+ // Primitive types
59
+ if (schema instanceof StringSchema) {
60
+ result.type = 'string';
61
+ return result;
62
+ }
63
+
64
+ if (schema instanceof NumberSchema) {
65
+ result.type = 'number';
66
+ return result;
67
+ }
68
+
69
+ if (schema instanceof BooleanSchema) {
70
+ result.type = 'boolean';
71
+ return result;
72
+ }
73
+
74
+ if (schema instanceof NullSchema) {
75
+ result.type = 'null';
76
+ return result;
77
+ }
78
+
79
+ if (schema instanceof UndefinedSchema) {
80
+ // JSON Schema doesn't have a direct "undefined" type
81
+ // We can represent it as an empty schema or omit the field
82
+ return {};
83
+ }
84
+
85
+ // Literal types
86
+ if (schema instanceof LiteralSchema) {
87
+ const value = (schema as any).value;
88
+ result.const = value;
89
+ if (typeof value === 'string') {
90
+ result.type = 'string';
91
+ } else if (typeof value === 'number') {
92
+ result.type = 'number';
93
+ } else if (typeof value === 'boolean') {
94
+ result.type = 'boolean';
95
+ }
96
+ return result;
97
+ }
98
+
99
+ // Object types
100
+ if (schema instanceof ObjectSchema) {
101
+ result.type = 'object';
102
+ const shape = (schema as any).shape;
103
+ result.properties = {};
104
+ result.required = [];
105
+
106
+ for (const [key, fieldSchema] of Object.entries(shape) as Array<[string, Schema<any, any>]>) {
107
+ result.properties[key] = toJSONSchema(fieldSchema);
108
+
109
+ // If the field is not optional, add it to required
110
+ if (!(fieldSchema instanceof OptionalSchema)) {
111
+ result.required.push(key);
112
+ }
113
+ }
114
+
115
+ // Remove required if empty
116
+ if (result.required.length === 0) {
117
+ delete result.required;
118
+ }
119
+
120
+ return result;
121
+ }
122
+
123
+ // Array types
124
+ if (schema instanceof ArraySchema) {
125
+ result.type = 'array';
126
+ const itemSchema = (schema as any).itemSchema;
127
+ result.items = toJSONSchema(itemSchema);
128
+ return result;
129
+ }
130
+
131
+ // Optional types
132
+ if (schema instanceof OptionalSchema) {
133
+ const innerSchema = (schema as any).schema;
134
+ const innerJSON = toJSONSchema(innerSchema);
135
+ // Optional is typically handled at the object level via required array
136
+ return innerJSON;
137
+ }
138
+
139
+ // Nullable types
140
+ if (schema instanceof NullableSchema) {
141
+ const innerSchema = (schema as any).schema;
142
+ const innerJSON = toJSONSchema(innerSchema);
143
+ // Nullable can be represented as anyOf with null
144
+ return {
145
+ anyOf: [innerJSON, { type: 'null' }],
146
+ ...(schema.description && { description: schema.description }),
147
+ };
148
+ }
149
+
150
+ // Union types
151
+ if (schema instanceof UnionSchema) {
152
+ const schemas = (schema as any).schemas as Schema<any, any>[];
153
+ result.anyOf = schemas.map((schema) => toJSONSchema(schema));
154
+ return result;
155
+ }
156
+
157
+ // Fallback for unknown schema types
158
+ return result;
159
+ }
160
+
161
+ /**
162
+ * Convert a JSON Schema object to a schema.
163
+ * Supports round-trip conversion with toJSONSchema.
164
+ *
165
+ * @param jsonSchema - The JSON Schema object to convert
166
+ * @returns Schema instance
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const jsonSchema = {
171
+ * type: 'object',
172
+ * properties: {
173
+ * name: { type: 'string' },
174
+ * age: { type: 'number' }
175
+ * },
176
+ * required: ['name', 'age']
177
+ * };
178
+ *
179
+ * const schema = s.fromJSONSchema(jsonSchema);
180
+ * const user = schema.parse({ name: 'John', age: 30 });
181
+ * ```
182
+ */
183
+ export function fromJSONSchema(jsonSchema: JSONSchema): Schema<any, any> {
184
+ // Handle const (literal values)
185
+ if (jsonSchema.const !== undefined) {
186
+ const schema = literal(jsonSchema.const);
187
+ if (jsonSchema.description) {
188
+ schema.describe(jsonSchema.description);
189
+ }
190
+ return schema;
191
+ }
192
+
193
+ // Handle anyOf (union or nullable)
194
+ if (jsonSchema.anyOf && Array.isArray(jsonSchema.anyOf)) {
195
+ // Check if it's a nullable pattern (anyOf with one schema and one null)
196
+ if (jsonSchema.anyOf.length === 2) {
197
+ const nullIndex = jsonSchema.anyOf.findIndex((s) => s.type === 'null');
198
+ if (nullIndex !== -1) {
199
+ const otherIndex = nullIndex === 0 ? 1 : 0;
200
+ const innerSchema = fromJSONSchema(jsonSchema.anyOf[otherIndex]);
201
+ const schema = nullable(innerSchema);
202
+ if (jsonSchema.description) {
203
+ schema.describe(jsonSchema.description);
204
+ }
205
+ return schema;
206
+ }
207
+ }
208
+
209
+ // Otherwise treat as union
210
+ const schemas = jsonSchema.anyOf.map((s) => fromJSONSchema(s));
211
+ const schema = union(...schemas);
212
+ if (jsonSchema.description) {
213
+ schema.describe(jsonSchema.description);
214
+ }
215
+ return schema;
216
+ }
217
+
218
+ // Handle oneOf (union)
219
+ if (jsonSchema.oneOf && Array.isArray(jsonSchema.oneOf)) {
220
+ const schemas = jsonSchema.oneOf.map((s) => fromJSONSchema(s));
221
+ const schema = union(...schemas);
222
+ if (jsonSchema.description) {
223
+ schema.describe(jsonSchema.description);
224
+ }
225
+ return schema;
226
+ }
227
+
228
+ // Handle enum (union of literals)
229
+ if (jsonSchema.enum && Array.isArray(jsonSchema.enum)) {
230
+ const schemas = jsonSchema.enum.map((value) => literal(value as string | number | boolean));
231
+ const schema = union(...schemas);
232
+ if (jsonSchema.description) {
233
+ schema.describe(jsonSchema.description);
234
+ }
235
+ return schema;
236
+ }
237
+
238
+ // Handle primitive types
239
+ switch (jsonSchema.type) {
240
+ case 'string': {
241
+ const schema = string();
242
+ if (jsonSchema.description) {
243
+ schema.describe(jsonSchema.description);
244
+ }
245
+ return schema;
246
+ }
247
+
248
+ case 'number':
249
+ case 'integer': {
250
+ const schema = number();
251
+ if (jsonSchema.description) {
252
+ schema.describe(jsonSchema.description);
253
+ }
254
+ return schema;
255
+ }
256
+
257
+ case 'boolean': {
258
+ const schema = boolean();
259
+ if (jsonSchema.description) {
260
+ schema.describe(jsonSchema.description);
261
+ }
262
+ return schema;
263
+ }
264
+
265
+ case 'null': {
266
+ const schema = null_();
267
+ if (jsonSchema.description) {
268
+ schema.describe(jsonSchema.description);
269
+ }
270
+ return schema;
271
+ }
272
+
273
+ case 'array': {
274
+ if (!jsonSchema.items) {
275
+ throw new Error('Array type must have items property');
276
+ }
277
+ const itemSchema = fromJSONSchema(jsonSchema.items);
278
+ const schema = array(itemSchema);
279
+ if (jsonSchema.description) {
280
+ schema.describe(jsonSchema.description);
281
+ }
282
+ return schema;
283
+ }
284
+
285
+ case 'object': {
286
+ if (!jsonSchema.properties) {
287
+ // Empty object schema
288
+ const schema = object({});
289
+ if (jsonSchema.description) {
290
+ schema.describe(jsonSchema.description);
291
+ }
292
+ return schema;
293
+ }
294
+
295
+ const shape: Record<string, Schema<any, any>> = {};
296
+ const requiredFields = new Set(jsonSchema.required || []);
297
+
298
+ for (const [key, propSchema] of Object.entries(jsonSchema.properties)) {
299
+ let fieldSchema = fromJSONSchema(propSchema);
300
+
301
+ // If field is not in required array, make it optional
302
+ if (!requiredFields.has(key)) {
303
+ fieldSchema = optional(fieldSchema);
304
+ }
305
+
306
+ shape[key] = fieldSchema;
307
+ }
308
+
309
+ const schema = object(shape);
310
+ if (jsonSchema.description) {
311
+ schema.describe(jsonSchema.description);
312
+ }
313
+ return schema;
314
+ }
315
+
316
+ default: {
317
+ // If no type is specified, try to infer from other properties
318
+ if (jsonSchema.properties) {
319
+ // Treat as object
320
+ return fromJSONSchema({ ...jsonSchema, type: 'object' });
321
+ }
322
+ if (jsonSchema.items) {
323
+ // Treat as array
324
+ return fromJSONSchema({ ...jsonSchema, type: 'array' });
325
+ }
326
+ // Fallback to string schema for unrecognized/untyped JSON Schema
327
+ // This provides a permissive default but may mask schema issues
328
+ return string();
329
+ }
330
+ }
331
+ }
@@ -0,0 +1,64 @@
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
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ const parseMethods = createParseMethods<any>();
8
+
9
+ /**
10
+ * Schema that accepts any value with 'any' type.
11
+ * Returns the value as-is without validation or type safety.
12
+ * Use this sparingly - prefer unknown() for better type safety.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const schema = s.any();
17
+ * const value = schema.parse(123); // any
18
+ * const value2 = schema.parse('hello'); // any
19
+ *
20
+ * // No type checking required
21
+ * value.toUpperCase(); // No error, but may fail at runtime
22
+ * ```
23
+ */
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ export class AnySchema implements Schema<any, any> {
26
+ description?: string;
27
+
28
+ readonly '~standard' = {
29
+ version: 1 as const,
30
+ vendor: 'agentuity',
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ validate: (value: unknown) => success(value as any),
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ types: undefined as unknown as { input: any; output: any },
35
+ };
36
+
37
+ describe(description: string): this {
38
+ this.description = description;
39
+ return this;
40
+ }
41
+
42
+ optional() {
43
+ return optional(this);
44
+ }
45
+
46
+ nullable() {
47
+ return nullable(this);
48
+ }
49
+ parse = parseMethods.parse;
50
+ safeParse = parseMethods.safeParse;
51
+ }
52
+
53
+ /**
54
+ * Create an any schema that accepts any value.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const schema = s.any();
59
+ * const value = schema.parse(anything); // Type is any
60
+ * ```
61
+ */
62
+ export function any(): AnySchema {
63
+ return new AnySchema();
64
+ }