anyvali 0.3.1 → 0.3.4
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/CHANGELOG.md +67 -44
- package/README.md +370 -370
- package/dist/parse/coerce.d.ts.map +1 -1
- package/dist/parse/coerce.js +14 -0
- package/dist/parse/coerce.js.map +1 -1
- package/dist/schemas/number.d.ts.map +1 -1
- package/dist/schemas/number.js +15 -0
- package/dist/schemas/number.js.map +1 -1
- package/dist/schemas/optional.d.ts.map +1 -1
- package/dist/schemas/optional.js +4 -3
- package/dist/schemas/optional.js.map +1 -1
- package/package.json +40 -40
- package/sdk/js/CHANGELOG.md +13 -13
- package/src/format/validators.ts +71 -71
- package/src/index.ts +285 -285
- package/src/infer.ts +12 -12
- package/src/interchange/importer.ts +285 -285
- package/src/issue-codes.ts +19 -19
- package/src/parse/coerce.ts +15 -0
- package/src/schemas/base.ts +322 -322
- package/src/schemas/intersection.ts +81 -81
- package/src/schemas/number.ts +17 -0
- package/src/schemas/object.ts +203 -203
- package/src/schemas/optional.ts +4 -3
- package/src/schemas/record.ts +55 -55
- package/src/schemas/string.ts +192 -192
- package/src/schemas/union.ts +53 -53
- package/src/types.ts +239 -239
- package/tests/unit/collections.test.ts +99 -99
- package/tests/unit/date-format.test.ts +18 -18
- package/tests/unit/default-mutation.test.ts +32 -32
- package/tests/unit/defaults.test.ts +70 -1
- package/tests/unit/inference.test.ts +306 -306
- package/tests/unit/interchange.test.ts +191 -191
- package/tests/unit/object.test.ts +208 -208
- package/tests/unit/security-recursion.test.ts +105 -105
- package/tests/unit/security.test.ts +1067 -945
- package/tests/unit/shared-ref-falsepos.test.ts +33 -33
- package/tests/unit/string-pattern-redos.test.ts +46 -46
- package/tests/unit/string.test.ts +147 -147
|
@@ -1,306 +1,306 @@
|
|
|
1
|
-
import { describe, it, expect, expectTypeOf } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
object,
|
|
4
|
-
string,
|
|
5
|
-
int,
|
|
6
|
-
int64,
|
|
7
|
-
number,
|
|
8
|
-
bool,
|
|
9
|
-
null_,
|
|
10
|
-
array,
|
|
11
|
-
tuple,
|
|
12
|
-
record,
|
|
13
|
-
union,
|
|
14
|
-
intersection,
|
|
15
|
-
optional,
|
|
16
|
-
nullable,
|
|
17
|
-
literal,
|
|
18
|
-
enum_,
|
|
19
|
-
type Infer,
|
|
20
|
-
} from "../../src/index.js";
|
|
21
|
-
|
|
22
|
-
describe("Type Inference", () => {
|
|
23
|
-
describe("Infer<T> utility type", () => {
|
|
24
|
-
it("infers string", () => {
|
|
25
|
-
const schema = string();
|
|
26
|
-
type T = Infer<typeof schema>;
|
|
27
|
-
expectTypeOf<T>().toEqualTypeOf<string>();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("infers number", () => {
|
|
31
|
-
const schema = number();
|
|
32
|
-
type T = Infer<typeof schema>;
|
|
33
|
-
expectTypeOf<T>().toEqualTypeOf<number>();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("infers int as number", () => {
|
|
37
|
-
const schema = int();
|
|
38
|
-
type T = Infer<typeof schema>;
|
|
39
|
-
expectTypeOf<T>().toEqualTypeOf<number>();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("infers boolean", () => {
|
|
43
|
-
const schema = bool();
|
|
44
|
-
type T = Infer<typeof schema>;
|
|
45
|
-
expectTypeOf<T>().toEqualTypeOf<boolean>();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("infers null", () => {
|
|
49
|
-
const schema = null_();
|
|
50
|
-
type T = Infer<typeof schema>;
|
|
51
|
-
expectTypeOf<T>().toEqualTypeOf<null>();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("infers literal string", () => {
|
|
55
|
-
const schema = literal("hello");
|
|
56
|
-
type T = Infer<typeof schema>;
|
|
57
|
-
expectTypeOf<T>().toEqualTypeOf<"hello">();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("infers literal number", () => {
|
|
61
|
-
const schema = literal(42);
|
|
62
|
-
type T = Infer<typeof schema>;
|
|
63
|
-
expectTypeOf<T>().toEqualTypeOf<42>();
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe("Object type inference", () => {
|
|
68
|
-
it("infers flat object shape", () => {
|
|
69
|
-
const schema = object({
|
|
70
|
-
name: string(),
|
|
71
|
-
age: int(),
|
|
72
|
-
});
|
|
73
|
-
type T = Infer<typeof schema>;
|
|
74
|
-
expectTypeOf<T>().toEqualTypeOf<{ name: string; age: number }>();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("infers object with optional fields", () => {
|
|
78
|
-
const schema = object({
|
|
79
|
-
name: string(),
|
|
80
|
-
nick: optional(string()),
|
|
81
|
-
});
|
|
82
|
-
type T = Infer<typeof schema>;
|
|
83
|
-
expectTypeOf<T>().toEqualTypeOf<{
|
|
84
|
-
name: string;
|
|
85
|
-
nick?: string | undefined;
|
|
86
|
-
}>();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("infers object with nullable fields", () => {
|
|
90
|
-
const schema = object({
|
|
91
|
-
name: string(),
|
|
92
|
-
bio: nullable(string()),
|
|
93
|
-
});
|
|
94
|
-
type T = Infer<typeof schema>;
|
|
95
|
-
expectTypeOf<T>().toEqualTypeOf<{
|
|
96
|
-
name: string;
|
|
97
|
-
bio: string | null;
|
|
98
|
-
}>();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("infers nested objects", () => {
|
|
102
|
-
const schema = object({
|
|
103
|
-
user: object({
|
|
104
|
-
name: string(),
|
|
105
|
-
age: int(),
|
|
106
|
-
}),
|
|
107
|
-
});
|
|
108
|
-
type T = Infer<typeof schema>;
|
|
109
|
-
expectTypeOf<T>().toEqualTypeOf<{
|
|
110
|
-
user: { name: string; age: number };
|
|
111
|
-
}>();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("parse returns typed value, not Record<string, unknown>", () => {
|
|
115
|
-
const schema = object({
|
|
116
|
-
a: string(),
|
|
117
|
-
});
|
|
118
|
-
const result = schema.parse({ a: "hello" });
|
|
119
|
-
expectTypeOf(result).toEqualTypeOf<{ a: string }>();
|
|
120
|
-
|
|
121
|
-
// This should be accessible:
|
|
122
|
-
expectTypeOf(result.a).toEqualTypeOf<string>();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("safeParse data is typed on success", () => {
|
|
126
|
-
const schema = object({
|
|
127
|
-
a: string(),
|
|
128
|
-
b: int(),
|
|
129
|
-
});
|
|
130
|
-
const result = schema.safeParse({ a: "hello", b: 1 });
|
|
131
|
-
if (result.success) {
|
|
132
|
-
expectTypeOf(result.data).toEqualTypeOf<{
|
|
133
|
-
a: string;
|
|
134
|
-
b: number;
|
|
135
|
-
}>();
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("rejects accessing unknown properties at type level", () => {
|
|
140
|
-
const schema = object({ a: string() });
|
|
141
|
-
type T = Infer<typeof schema>;
|
|
142
|
-
// T should have 'a' but not 'b'
|
|
143
|
-
expectTypeOf<T>().toHaveProperty("a");
|
|
144
|
-
expectTypeOf<T>().not.toHaveProperty("b");
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe("Array type inference", () => {
|
|
149
|
-
it("infers array of strings", () => {
|
|
150
|
-
const schema = array(string());
|
|
151
|
-
type T = Infer<typeof schema>;
|
|
152
|
-
expectTypeOf<T>().toEqualTypeOf<string[]>();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("infers array of objects", () => {
|
|
156
|
-
const schema = array(
|
|
157
|
-
object({
|
|
158
|
-
id: int64(),
|
|
159
|
-
name: string(),
|
|
160
|
-
}),
|
|
161
|
-
);
|
|
162
|
-
type T = Infer<typeof schema>;
|
|
163
|
-
expectTypeOf<T>().toEqualTypeOf<
|
|
164
|
-
{ id: number; name: string }[]
|
|
165
|
-
>();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe("Tuple type inference", () => {
|
|
170
|
-
it("infers tuple element types", () => {
|
|
171
|
-
const schema = tuple([string(), int(), bool()]);
|
|
172
|
-
type T = Infer<typeof schema>;
|
|
173
|
-
expectTypeOf<T>().toEqualTypeOf<[string, number, boolean]>();
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe("Record type inference", () => {
|
|
178
|
-
it("infers record value type", () => {
|
|
179
|
-
const schema = record(number());
|
|
180
|
-
type T = Infer<typeof schema>;
|
|
181
|
-
expectTypeOf<T>().toEqualTypeOf<Record<string, number>>();
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe("Union type inference", () => {
|
|
186
|
-
it("infers union of types", () => {
|
|
187
|
-
const schema = union([string(), int()]);
|
|
188
|
-
type T = Infer<typeof schema>;
|
|
189
|
-
expectTypeOf<T>().toEqualTypeOf<string | number>();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("infers union of literals", () => {
|
|
193
|
-
const schema = union([literal("a"), literal("b")]);
|
|
194
|
-
type T = Infer<typeof schema>;
|
|
195
|
-
expectTypeOf<T>().toEqualTypeOf<"a" | "b">();
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
describe("Intersection type inference", () => {
|
|
200
|
-
it("infers intersection of object types", () => {
|
|
201
|
-
const schema = intersection([
|
|
202
|
-
object({ name: string() }),
|
|
203
|
-
object({ age: int() }),
|
|
204
|
-
]);
|
|
205
|
-
type T = Infer<typeof schema>;
|
|
206
|
-
expectTypeOf<T>().toEqualTypeOf<{
|
|
207
|
-
name: string;
|
|
208
|
-
age: number;
|
|
209
|
-
}>();
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe("Enum type inference", () => {
|
|
214
|
-
it("infers enum values as union", () => {
|
|
215
|
-
const schema = enum_(["free", "pro", "enterprise"] as const);
|
|
216
|
-
type T = Infer<typeof schema>;
|
|
217
|
-
expectTypeOf<T>().toEqualTypeOf<"free" | "pro" | "enterprise">();
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
describe("Modifier type inference", () => {
|
|
222
|
-
it("optional adds undefined", () => {
|
|
223
|
-
const schema = optional(string());
|
|
224
|
-
type T = Infer<typeof schema>;
|
|
225
|
-
expectTypeOf<T>().toEqualTypeOf<string | undefined>();
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("nullable adds null", () => {
|
|
229
|
-
const schema = nullable(string());
|
|
230
|
-
type T = Infer<typeof schema>;
|
|
231
|
-
expectTypeOf<T>().toEqualTypeOf<string | null>();
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("optional nullable adds both", () => {
|
|
235
|
-
const schema = optional(nullable(string()));
|
|
236
|
-
type T = Infer<typeof schema>;
|
|
237
|
-
expectTypeOf<T>().toEqualTypeOf<string | null | undefined>();
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
describe("Method chaining preserves types", () => {
|
|
242
|
-
it("string with constraints stays string", () => {
|
|
243
|
-
const schema = string().minLength(1).maxLength(100).format("email");
|
|
244
|
-
type T = Infer<typeof schema>;
|
|
245
|
-
expectTypeOf<T>().toEqualTypeOf<string>();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it("int with constraints stays number", () => {
|
|
249
|
-
const schema = int().min(0).max(100);
|
|
250
|
-
type T = Infer<typeof schema>;
|
|
251
|
-
expectTypeOf<T>().toEqualTypeOf<number>();
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it("array with constraints preserves element type", () => {
|
|
255
|
-
const schema = array(string()).minItems(1).maxItems(10);
|
|
256
|
-
type T = Infer<typeof schema>;
|
|
257
|
-
expectTypeOf<T>().toEqualTypeOf<string[]>();
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
describe("Runtime behavior unchanged", () => {
|
|
262
|
-
it("object parse still works correctly", () => {
|
|
263
|
-
const schema = object({ a: string() });
|
|
264
|
-
const result = schema.parse({ a: "hello" });
|
|
265
|
-
expect(result).toEqual({ a: "hello" });
|
|
266
|
-
expect(result.a).toBe("hello");
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("object safeParse still works correctly", () => {
|
|
270
|
-
const schema = object({ a: string(), b: int() });
|
|
271
|
-
const result = schema.safeParse({ a: "hello", b: 42 });
|
|
272
|
-
expect(result.success).toBe(true);
|
|
273
|
-
if (result.success) {
|
|
274
|
-
expect(result.data.a).toBe("hello");
|
|
275
|
-
expect(result.data.b).toBe(42);
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it("object safeParse strips unknown properties by default", () => {
|
|
280
|
-
const schema = object({ a: string() });
|
|
281
|
-
const result = schema.safeParse({ a: "hello", b: "extra" });
|
|
282
|
-
expect(result.success).toBe(true);
|
|
283
|
-
if (result.success) {
|
|
284
|
-
expect(result.data).toEqual({ a: "hello" });
|
|
285
|
-
expect("b" in result.data).toBe(false);
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it("object safeParse rejects unknown properties when unknownKeys('reject')", () => {
|
|
290
|
-
const schema = object({ a: string() }).unknownKeys("reject");
|
|
291
|
-
const result = schema.safeParse({ a: "hello", b: "extra" });
|
|
292
|
-
expect(result.success).toBe(false);
|
|
293
|
-
if (!result.success) {
|
|
294
|
-
expect(result.issues[0].code).toBe("unknown_key");
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it("typed parse result correctly narrows - accessing known property is ok", () => {
|
|
299
|
-
const schema = object({ a: string() });
|
|
300
|
-
const res = schema.parse({ a: "a" });
|
|
301
|
-
// res.a should work fine at runtime and type level
|
|
302
|
-
expect(res.a).toBe("a");
|
|
303
|
-
expectTypeOf(res.a).toEqualTypeOf<string>();
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
});
|
|
1
|
+
import { describe, it, expect, expectTypeOf } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
object,
|
|
4
|
+
string,
|
|
5
|
+
int,
|
|
6
|
+
int64,
|
|
7
|
+
number,
|
|
8
|
+
bool,
|
|
9
|
+
null_,
|
|
10
|
+
array,
|
|
11
|
+
tuple,
|
|
12
|
+
record,
|
|
13
|
+
union,
|
|
14
|
+
intersection,
|
|
15
|
+
optional,
|
|
16
|
+
nullable,
|
|
17
|
+
literal,
|
|
18
|
+
enum_,
|
|
19
|
+
type Infer,
|
|
20
|
+
} from "../../src/index.js";
|
|
21
|
+
|
|
22
|
+
describe("Type Inference", () => {
|
|
23
|
+
describe("Infer<T> utility type", () => {
|
|
24
|
+
it("infers string", () => {
|
|
25
|
+
const schema = string();
|
|
26
|
+
type T = Infer<typeof schema>;
|
|
27
|
+
expectTypeOf<T>().toEqualTypeOf<string>();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("infers number", () => {
|
|
31
|
+
const schema = number();
|
|
32
|
+
type T = Infer<typeof schema>;
|
|
33
|
+
expectTypeOf<T>().toEqualTypeOf<number>();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("infers int as number", () => {
|
|
37
|
+
const schema = int();
|
|
38
|
+
type T = Infer<typeof schema>;
|
|
39
|
+
expectTypeOf<T>().toEqualTypeOf<number>();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("infers boolean", () => {
|
|
43
|
+
const schema = bool();
|
|
44
|
+
type T = Infer<typeof schema>;
|
|
45
|
+
expectTypeOf<T>().toEqualTypeOf<boolean>();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("infers null", () => {
|
|
49
|
+
const schema = null_();
|
|
50
|
+
type T = Infer<typeof schema>;
|
|
51
|
+
expectTypeOf<T>().toEqualTypeOf<null>();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("infers literal string", () => {
|
|
55
|
+
const schema = literal("hello");
|
|
56
|
+
type T = Infer<typeof schema>;
|
|
57
|
+
expectTypeOf<T>().toEqualTypeOf<"hello">();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("infers literal number", () => {
|
|
61
|
+
const schema = literal(42);
|
|
62
|
+
type T = Infer<typeof schema>;
|
|
63
|
+
expectTypeOf<T>().toEqualTypeOf<42>();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("Object type inference", () => {
|
|
68
|
+
it("infers flat object shape", () => {
|
|
69
|
+
const schema = object({
|
|
70
|
+
name: string(),
|
|
71
|
+
age: int(),
|
|
72
|
+
});
|
|
73
|
+
type T = Infer<typeof schema>;
|
|
74
|
+
expectTypeOf<T>().toEqualTypeOf<{ name: string; age: number }>();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("infers object with optional fields", () => {
|
|
78
|
+
const schema = object({
|
|
79
|
+
name: string(),
|
|
80
|
+
nick: optional(string()),
|
|
81
|
+
});
|
|
82
|
+
type T = Infer<typeof schema>;
|
|
83
|
+
expectTypeOf<T>().toEqualTypeOf<{
|
|
84
|
+
name: string;
|
|
85
|
+
nick?: string | undefined;
|
|
86
|
+
}>();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("infers object with nullable fields", () => {
|
|
90
|
+
const schema = object({
|
|
91
|
+
name: string(),
|
|
92
|
+
bio: nullable(string()),
|
|
93
|
+
});
|
|
94
|
+
type T = Infer<typeof schema>;
|
|
95
|
+
expectTypeOf<T>().toEqualTypeOf<{
|
|
96
|
+
name: string;
|
|
97
|
+
bio: string | null;
|
|
98
|
+
}>();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("infers nested objects", () => {
|
|
102
|
+
const schema = object({
|
|
103
|
+
user: object({
|
|
104
|
+
name: string(),
|
|
105
|
+
age: int(),
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
type T = Infer<typeof schema>;
|
|
109
|
+
expectTypeOf<T>().toEqualTypeOf<{
|
|
110
|
+
user: { name: string; age: number };
|
|
111
|
+
}>();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("parse returns typed value, not Record<string, unknown>", () => {
|
|
115
|
+
const schema = object({
|
|
116
|
+
a: string(),
|
|
117
|
+
});
|
|
118
|
+
const result = schema.parse({ a: "hello" });
|
|
119
|
+
expectTypeOf(result).toEqualTypeOf<{ a: string }>();
|
|
120
|
+
|
|
121
|
+
// This should be accessible:
|
|
122
|
+
expectTypeOf(result.a).toEqualTypeOf<string>();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("safeParse data is typed on success", () => {
|
|
126
|
+
const schema = object({
|
|
127
|
+
a: string(),
|
|
128
|
+
b: int(),
|
|
129
|
+
});
|
|
130
|
+
const result = schema.safeParse({ a: "hello", b: 1 });
|
|
131
|
+
if (result.success) {
|
|
132
|
+
expectTypeOf(result.data).toEqualTypeOf<{
|
|
133
|
+
a: string;
|
|
134
|
+
b: number;
|
|
135
|
+
}>();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("rejects accessing unknown properties at type level", () => {
|
|
140
|
+
const schema = object({ a: string() });
|
|
141
|
+
type T = Infer<typeof schema>;
|
|
142
|
+
// T should have 'a' but not 'b'
|
|
143
|
+
expectTypeOf<T>().toHaveProperty("a");
|
|
144
|
+
expectTypeOf<T>().not.toHaveProperty("b");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("Array type inference", () => {
|
|
149
|
+
it("infers array of strings", () => {
|
|
150
|
+
const schema = array(string());
|
|
151
|
+
type T = Infer<typeof schema>;
|
|
152
|
+
expectTypeOf<T>().toEqualTypeOf<string[]>();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("infers array of objects", () => {
|
|
156
|
+
const schema = array(
|
|
157
|
+
object({
|
|
158
|
+
id: int64(),
|
|
159
|
+
name: string(),
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
type T = Infer<typeof schema>;
|
|
163
|
+
expectTypeOf<T>().toEqualTypeOf<
|
|
164
|
+
{ id: number; name: string }[]
|
|
165
|
+
>();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("Tuple type inference", () => {
|
|
170
|
+
it("infers tuple element types", () => {
|
|
171
|
+
const schema = tuple([string(), int(), bool()]);
|
|
172
|
+
type T = Infer<typeof schema>;
|
|
173
|
+
expectTypeOf<T>().toEqualTypeOf<[string, number, boolean]>();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("Record type inference", () => {
|
|
178
|
+
it("infers record value type", () => {
|
|
179
|
+
const schema = record(number());
|
|
180
|
+
type T = Infer<typeof schema>;
|
|
181
|
+
expectTypeOf<T>().toEqualTypeOf<Record<string, number>>();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("Union type inference", () => {
|
|
186
|
+
it("infers union of types", () => {
|
|
187
|
+
const schema = union([string(), int()]);
|
|
188
|
+
type T = Infer<typeof schema>;
|
|
189
|
+
expectTypeOf<T>().toEqualTypeOf<string | number>();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("infers union of literals", () => {
|
|
193
|
+
const schema = union([literal("a"), literal("b")]);
|
|
194
|
+
type T = Infer<typeof schema>;
|
|
195
|
+
expectTypeOf<T>().toEqualTypeOf<"a" | "b">();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("Intersection type inference", () => {
|
|
200
|
+
it("infers intersection of object types", () => {
|
|
201
|
+
const schema = intersection([
|
|
202
|
+
object({ name: string() }),
|
|
203
|
+
object({ age: int() }),
|
|
204
|
+
]);
|
|
205
|
+
type T = Infer<typeof schema>;
|
|
206
|
+
expectTypeOf<T>().toEqualTypeOf<{
|
|
207
|
+
name: string;
|
|
208
|
+
age: number;
|
|
209
|
+
}>();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("Enum type inference", () => {
|
|
214
|
+
it("infers enum values as union", () => {
|
|
215
|
+
const schema = enum_(["free", "pro", "enterprise"] as const);
|
|
216
|
+
type T = Infer<typeof schema>;
|
|
217
|
+
expectTypeOf<T>().toEqualTypeOf<"free" | "pro" | "enterprise">();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("Modifier type inference", () => {
|
|
222
|
+
it("optional adds undefined", () => {
|
|
223
|
+
const schema = optional(string());
|
|
224
|
+
type T = Infer<typeof schema>;
|
|
225
|
+
expectTypeOf<T>().toEqualTypeOf<string | undefined>();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("nullable adds null", () => {
|
|
229
|
+
const schema = nullable(string());
|
|
230
|
+
type T = Infer<typeof schema>;
|
|
231
|
+
expectTypeOf<T>().toEqualTypeOf<string | null>();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("optional nullable adds both", () => {
|
|
235
|
+
const schema = optional(nullable(string()));
|
|
236
|
+
type T = Infer<typeof schema>;
|
|
237
|
+
expectTypeOf<T>().toEqualTypeOf<string | null | undefined>();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("Method chaining preserves types", () => {
|
|
242
|
+
it("string with constraints stays string", () => {
|
|
243
|
+
const schema = string().minLength(1).maxLength(100).format("email");
|
|
244
|
+
type T = Infer<typeof schema>;
|
|
245
|
+
expectTypeOf<T>().toEqualTypeOf<string>();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("int with constraints stays number", () => {
|
|
249
|
+
const schema = int().min(0).max(100);
|
|
250
|
+
type T = Infer<typeof schema>;
|
|
251
|
+
expectTypeOf<T>().toEqualTypeOf<number>();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("array with constraints preserves element type", () => {
|
|
255
|
+
const schema = array(string()).minItems(1).maxItems(10);
|
|
256
|
+
type T = Infer<typeof schema>;
|
|
257
|
+
expectTypeOf<T>().toEqualTypeOf<string[]>();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe("Runtime behavior unchanged", () => {
|
|
262
|
+
it("object parse still works correctly", () => {
|
|
263
|
+
const schema = object({ a: string() });
|
|
264
|
+
const result = schema.parse({ a: "hello" });
|
|
265
|
+
expect(result).toEqual({ a: "hello" });
|
|
266
|
+
expect(result.a).toBe("hello");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("object safeParse still works correctly", () => {
|
|
270
|
+
const schema = object({ a: string(), b: int() });
|
|
271
|
+
const result = schema.safeParse({ a: "hello", b: 42 });
|
|
272
|
+
expect(result.success).toBe(true);
|
|
273
|
+
if (result.success) {
|
|
274
|
+
expect(result.data.a).toBe("hello");
|
|
275
|
+
expect(result.data.b).toBe(42);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("object safeParse strips unknown properties by default", () => {
|
|
280
|
+
const schema = object({ a: string() });
|
|
281
|
+
const result = schema.safeParse({ a: "hello", b: "extra" });
|
|
282
|
+
expect(result.success).toBe(true);
|
|
283
|
+
if (result.success) {
|
|
284
|
+
expect(result.data).toEqual({ a: "hello" });
|
|
285
|
+
expect("b" in result.data).toBe(false);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("object safeParse rejects unknown properties when unknownKeys('reject')", () => {
|
|
290
|
+
const schema = object({ a: string() }).unknownKeys("reject");
|
|
291
|
+
const result = schema.safeParse({ a: "hello", b: "extra" });
|
|
292
|
+
expect(result.success).toBe(false);
|
|
293
|
+
if (!result.success) {
|
|
294
|
+
expect(result.issues[0].code).toBe("unknown_key");
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("typed parse result correctly narrows - accessing known property is ok", () => {
|
|
299
|
+
const schema = object({ a: string() });
|
|
300
|
+
const res = schema.parse({ a: "a" });
|
|
301
|
+
// res.a should work fine at runtime and type level
|
|
302
|
+
expect(res.a).toBe("a");
|
|
303
|
+
expectTypeOf(res.a).toEqualTypeOf<string>();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|