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,18 +1,18 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { string } from "../../src/index.js";
|
|
3
|
-
|
|
4
|
-
const date = () => string().format("date");
|
|
5
|
-
|
|
6
|
-
describe("date format validation", () => {
|
|
7
|
-
it("accepts valid ISO dates including early years (0001-0099)", () => {
|
|
8
|
-
for (const d of ["0001-01-01", "0050-01-01", "0099-12-31", "2021-06-15", "9999-12-31"]) {
|
|
9
|
-
expect(date().safeParse(d).success).toBe(true);
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("rejects impossible calendar dates", () => {
|
|
14
|
-
for (const d of ["2021-02-30", "2021-04-31", "2021-13-01", "2021-00-10"]) {
|
|
15
|
-
expect(date().safeParse(d).success).toBe(false);
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
});
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { string } from "../../src/index.js";
|
|
3
|
+
|
|
4
|
+
const date = () => string().format("date");
|
|
5
|
+
|
|
6
|
+
describe("date format validation", () => {
|
|
7
|
+
it("accepts valid ISO dates including early years (0001-0099)", () => {
|
|
8
|
+
for (const d of ["0001-01-01", "0050-01-01", "0099-12-31", "2021-06-15", "9999-12-31"]) {
|
|
9
|
+
expect(date().safeParse(d).success).toBe(true);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("rejects impossible calendar dates", () => {
|
|
14
|
+
for (const d of ["2021-02-30", "2021-04-31", "2021-13-01", "2021-00-10"]) {
|
|
15
|
+
expect(date().safeParse(d).success).toBe(false);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { any, unknown, object } from "../../src/index.js";
|
|
3
|
-
|
|
4
|
-
// Repro for AVV-007: mutable default values shared between validations.
|
|
5
|
-
// Pass-through schemas (any/unknown) return the default value by reference,
|
|
6
|
-
// so mutating one parse result corrupts the default for the next parse.
|
|
7
|
-
describe("Mutable default isolation (AVV-007)", () => {
|
|
8
|
-
it("any() array default is not shared between parses", () => {
|
|
9
|
-
const s = any().default([] as unknown[]);
|
|
10
|
-
const a = s.parse(undefined) as unknown[];
|
|
11
|
-
a.push("mutated");
|
|
12
|
-
const b = s.parse(undefined) as unknown[];
|
|
13
|
-
expect(b).toEqual([]);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("unknown() object default is not shared between parses", () => {
|
|
17
|
-
const s = unknown().default({ tags: [] as string[] });
|
|
18
|
-
const a = s.parse(undefined) as { tags: string[] };
|
|
19
|
-
a.tags.push("x");
|
|
20
|
-
const b = s.parse(undefined) as { tags: string[] };
|
|
21
|
-
expect(b.tags).toEqual([]);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("object field with any() default is not shared", () => {
|
|
25
|
-
const s = object({ meta: any().default({ count: 0, items: [] as number[] }) });
|
|
26
|
-
const a = s.parse({}) as { meta: { count: number; items: number[] } };
|
|
27
|
-
a.meta.items.push(1);
|
|
28
|
-
a.meta.count = 99;
|
|
29
|
-
const b = s.parse({}) as { meta: { count: number; items: number[] } };
|
|
30
|
-
expect(b.meta).toEqual({ count: 0, items: [] });
|
|
31
|
-
});
|
|
32
|
-
});
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { any, unknown, object } from "../../src/index.js";
|
|
3
|
+
|
|
4
|
+
// Repro for AVV-007: mutable default values shared between validations.
|
|
5
|
+
// Pass-through schemas (any/unknown) return the default value by reference,
|
|
6
|
+
// so mutating one parse result corrupts the default for the next parse.
|
|
7
|
+
describe("Mutable default isolation (AVV-007)", () => {
|
|
8
|
+
it("any() array default is not shared between parses", () => {
|
|
9
|
+
const s = any().default([] as unknown[]);
|
|
10
|
+
const a = s.parse(undefined) as unknown[];
|
|
11
|
+
a.push("mutated");
|
|
12
|
+
const b = s.parse(undefined) as unknown[];
|
|
13
|
+
expect(b).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("unknown() object default is not shared between parses", () => {
|
|
17
|
+
const s = unknown().default({ tags: [] as string[] });
|
|
18
|
+
const a = s.parse(undefined) as { tags: string[] };
|
|
19
|
+
a.tags.push("x");
|
|
20
|
+
const b = s.parse(undefined) as { tags: string[] };
|
|
21
|
+
expect(b.tags).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("object field with any() default is not shared", () => {
|
|
25
|
+
const s = object({ meta: any().default({ count: 0, items: [] as number[] }) });
|
|
26
|
+
const a = s.parse({}) as { meta: { count: number; items: number[] } };
|
|
27
|
+
a.meta.items.push(1);
|
|
28
|
+
a.meta.count = 99;
|
|
29
|
+
const b = s.parse({}) as { meta: { count: number; items: number[] } };
|
|
30
|
+
expect(b.meta).toEqual({ count: 0, items: [] });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { string, int, object, optional } from "../../src/index.js";
|
|
2
|
+
import { any, string, int, object, optional, nullable, bool, exportSchema } from "../../src/index.js";
|
|
3
3
|
|
|
4
4
|
describe("Defaults", () => {
|
|
5
5
|
it("applies default when value is absent", () => {
|
|
@@ -46,4 +46,73 @@ describe("Defaults", () => {
|
|
|
46
46
|
const result = s.parse({});
|
|
47
47
|
expect(result).toEqual({ count: 0 });
|
|
48
48
|
});
|
|
49
|
+
|
|
50
|
+
it("applies default on optional wrapper field", () => {
|
|
51
|
+
const s = object({
|
|
52
|
+
host: optional(string()).default("localhost"),
|
|
53
|
+
});
|
|
54
|
+
expect(s.parse({})).toEqual({ host: "localhost" });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("does not override present optional wrapper field with default", () => {
|
|
58
|
+
const s = object({
|
|
59
|
+
host: optional(string()).default("localhost"),
|
|
60
|
+
});
|
|
61
|
+
expect(s.parse({ host: "example.com" })).toEqual({ host: "example.com" });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("validates default on optional wrapper field", () => {
|
|
65
|
+
const s = object({
|
|
66
|
+
host: optional(string().minLength(5)).default("hi"),
|
|
67
|
+
});
|
|
68
|
+
const result = s.safeParse({});
|
|
69
|
+
expect(result.success).toBe(false);
|
|
70
|
+
if (!result.success) {
|
|
71
|
+
expect(result.issues[0].code).toBe("default_invalid");
|
|
72
|
+
expect(result.issues[0].path).toEqual(["host"]);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("exports default on optional wrapper", () => {
|
|
77
|
+
const doc = exportSchema(optional(string()).default("localhost"));
|
|
78
|
+
expect(doc.root.kind).toBe("optional");
|
|
79
|
+
expect(doc.root.default).toBe("localhost");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("clones mutable default on optional wrapper field", () => {
|
|
83
|
+
const s = object({
|
|
84
|
+
config: optional(any()).default({ tags: [] as string[] }),
|
|
85
|
+
});
|
|
86
|
+
const first = s.parse({}) as any;
|
|
87
|
+
first.config.tags.push("mutated");
|
|
88
|
+
expect(s.parse({})).toEqual({ config: { tags: [] } });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("does not apply default when nullable field is null", () => {
|
|
92
|
+
const s = object({
|
|
93
|
+
value: nullable(string()).default("fallback"),
|
|
94
|
+
});
|
|
95
|
+
expect(s.parse({ value: null })).toEqual({ value: null });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("applies falsy defaults", () => {
|
|
99
|
+
const s = object({
|
|
100
|
+
count: int().default(0),
|
|
101
|
+
name: string().default(""),
|
|
102
|
+
active: bool().default(false),
|
|
103
|
+
});
|
|
104
|
+
expect(s.parse({})).toEqual({ count: 0, name: "", active: false });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("applies nested defaults", () => {
|
|
108
|
+
const s = object({
|
|
109
|
+
user: object({
|
|
110
|
+
name: string(),
|
|
111
|
+
role: string().default("guest"),
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
expect(s.parse({ user: { name: "Bob" } })).toEqual({
|
|
115
|
+
user: { name: "Bob", role: "guest" },
|
|
116
|
+
});
|
|
117
|
+
});
|
|
49
118
|
});
|