anyvali 0.3.1 → 0.3.3

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/src/types.ts CHANGED
@@ -1,239 +1,239 @@
1
- // ---- Schema Node (interchange JSON representation) ----
2
-
3
- export type SchemaKind =
4
- | "any"
5
- | "unknown"
6
- | "never"
7
- | "null"
8
- | "bool"
9
- | "string"
10
- | "number"
11
- | "int"
12
- | "float32"
13
- | "float64"
14
- | "int8"
15
- | "int16"
16
- | "int32"
17
- | "int64"
18
- | "uint8"
19
- | "uint16"
20
- | "uint32"
21
- | "uint64"
22
- | "literal"
23
- | "enum"
24
- | "array"
25
- | "tuple"
26
- | "object"
27
- | "record"
28
- | "union"
29
- | "intersection"
30
- | "optional"
31
- | "nullable"
32
- | "ref";
33
-
34
- export interface MetadataOptions {
35
- replace?: boolean;
36
- }
37
-
38
- export interface DescribeOptions {
39
- title?: string;
40
- deprecated?: boolean;
41
- deprecatedMessage?: string;
42
- notStable?: boolean;
43
- since?: string;
44
- sensitive?: boolean;
45
- readonly?: boolean;
46
- writeonly?: boolean;
47
- examples?: unknown[];
48
- }
49
-
50
- export interface SchemaNodeBase {
51
- kind: SchemaKind;
52
- default?: unknown;
53
- coerce?: CoercionConfig | string | string[];
54
- metadata?: Record<string, unknown>;
55
- }
56
-
57
- export interface CoercionConfig {
58
- from?: string;
59
- trim?: boolean;
60
- lower?: boolean;
61
- upper?: boolean;
62
- }
63
-
64
- // String node
65
- export interface StringSchemaNode extends SchemaNodeBase {
66
- kind: "string";
67
- minLength?: number;
68
- maxLength?: number;
69
- pattern?: string;
70
- startsWith?: string;
71
- endsWith?: string;
72
- includes?: string;
73
- format?: StringFormat;
74
- }
75
-
76
- export type StringFormat =
77
- | "email"
78
- | "url"
79
- | "uuid"
80
- | "ipv4"
81
- | "ipv6"
82
- | "date"
83
- | "date-time";
84
-
85
- // Numeric nodes
86
- export interface NumericSchemaNode extends SchemaNodeBase {
87
- kind:
88
- | "number"
89
- | "int"
90
- | "float32"
91
- | "float64"
92
- | "int8"
93
- | "int16"
94
- | "int32"
95
- | "int64"
96
- | "uint8"
97
- | "uint16"
98
- | "uint32"
99
- | "uint64";
100
- min?: number;
101
- max?: number;
102
- exclusiveMin?: number;
103
- exclusiveMax?: number;
104
- multipleOf?: number;
105
- }
106
-
107
- // Simple nodes
108
- export interface SimpleSchemaNode extends SchemaNodeBase {
109
- kind: "any" | "unknown" | "never" | "null" | "bool";
110
- }
111
-
112
- // Literal node
113
- export interface LiteralSchemaNode extends SchemaNodeBase {
114
- kind: "literal";
115
- value: string | number | boolean | null;
116
- }
117
-
118
- // Enum node
119
- export interface EnumSchemaNode extends SchemaNodeBase {
120
- kind: "enum";
121
- values: (string | number)[];
122
- }
123
-
124
- // Array node
125
- export interface ArraySchemaNode extends SchemaNodeBase {
126
- kind: "array";
127
- items: SchemaNode;
128
- minItems?: number;
129
- maxItems?: number;
130
- }
131
-
132
- // Tuple node
133
- export interface TupleSchemaNode extends SchemaNodeBase {
134
- kind: "tuple";
135
- items: SchemaNode[];
136
- }
137
-
138
- // Object node
139
- export interface ObjectSchemaNode extends SchemaNodeBase {
140
- kind: "object";
141
- properties: Record<string, SchemaNode>;
142
- required: string[];
143
- unknownKeys?: UnknownKeyMode;
144
- }
145
-
146
- // Record node
147
- export interface RecordSchemaNode extends SchemaNodeBase {
148
- kind: "record";
149
- valueSchema: SchemaNode;
150
- }
151
-
152
- // Union node
153
- export interface UnionSchemaNode extends SchemaNodeBase {
154
- kind: "union";
155
- variants: SchemaNode[];
156
- }
157
-
158
- // Intersection node
159
- export interface IntersectionSchemaNode extends SchemaNodeBase {
160
- kind: "intersection";
161
- allOf: SchemaNode[];
162
- }
163
-
164
- // Optional node
165
- export interface OptionalSchemaNode extends SchemaNodeBase {
166
- kind: "optional";
167
- inner: SchemaNode;
168
- }
169
-
170
- // Nullable node
171
- export interface NullableSchemaNode extends SchemaNodeBase {
172
- kind: "nullable";
173
- inner: SchemaNode;
174
- }
175
-
176
- // Ref node
177
- export interface RefSchemaNode extends SchemaNodeBase {
178
- kind: "ref";
179
- ref: string;
180
- }
181
-
182
- export type SchemaNode =
183
- | StringSchemaNode
184
- | NumericSchemaNode
185
- | SimpleSchemaNode
186
- | LiteralSchemaNode
187
- | EnumSchemaNode
188
- | ArraySchemaNode
189
- | TupleSchemaNode
190
- | ObjectSchemaNode
191
- | RecordSchemaNode
192
- | UnionSchemaNode
193
- | IntersectionSchemaNode
194
- | OptionalSchemaNode
195
- | NullableSchemaNode
196
- | RefSchemaNode;
197
-
198
- // ---- Parse Result ----
199
-
200
- export type ParseResult<T> =
201
- | { success: true; data: T }
202
- | { success: false; issues: ValidationIssue[] };
203
-
204
- export interface ValidationIssue {
205
- code: string;
206
- message: string;
207
- path: (string | number)[];
208
- expected?: string;
209
- received?: string;
210
- meta?: Record<string, unknown>;
211
- }
212
-
213
- // ---- Interchange Document ----
214
-
215
- export interface AnyValiDocument {
216
- anyvaliVersion: string;
217
- schemaVersion: string;
218
- root: SchemaNode;
219
- definitions: Record<string, SchemaNode>;
220
- extensions: Record<string, Record<string, unknown>>;
221
- }
222
-
223
- // ---- Modes ----
224
-
225
- export type ExportMode = "portable" | "extended";
226
-
227
- export type UnknownKeyMode = "reject" | "strip" | "allow";
228
-
229
- // ---- Parse Context (internal) ----
230
-
231
- export interface ParseContext {
232
- path: (string | number)[];
233
- issues: ValidationIssue[];
234
- definitions?: Record<string, SchemaNode>;
235
- /** Tracks objects already being validated to detect circular references. */
236
- seen?: WeakSet<object>;
237
- /** Current recursion depth, used to bound unbounded recursion (DoS guard). */
238
- depth?: number;
239
- }
1
+ // ---- Schema Node (interchange JSON representation) ----
2
+
3
+ export type SchemaKind =
4
+ | "any"
5
+ | "unknown"
6
+ | "never"
7
+ | "null"
8
+ | "bool"
9
+ | "string"
10
+ | "number"
11
+ | "int"
12
+ | "float32"
13
+ | "float64"
14
+ | "int8"
15
+ | "int16"
16
+ | "int32"
17
+ | "int64"
18
+ | "uint8"
19
+ | "uint16"
20
+ | "uint32"
21
+ | "uint64"
22
+ | "literal"
23
+ | "enum"
24
+ | "array"
25
+ | "tuple"
26
+ | "object"
27
+ | "record"
28
+ | "union"
29
+ | "intersection"
30
+ | "optional"
31
+ | "nullable"
32
+ | "ref";
33
+
34
+ export interface MetadataOptions {
35
+ replace?: boolean;
36
+ }
37
+
38
+ export interface DescribeOptions {
39
+ title?: string;
40
+ deprecated?: boolean;
41
+ deprecatedMessage?: string;
42
+ notStable?: boolean;
43
+ since?: string;
44
+ sensitive?: boolean;
45
+ readonly?: boolean;
46
+ writeonly?: boolean;
47
+ examples?: unknown[];
48
+ }
49
+
50
+ export interface SchemaNodeBase {
51
+ kind: SchemaKind;
52
+ default?: unknown;
53
+ coerce?: CoercionConfig | string | string[];
54
+ metadata?: Record<string, unknown>;
55
+ }
56
+
57
+ export interface CoercionConfig {
58
+ from?: string;
59
+ trim?: boolean;
60
+ lower?: boolean;
61
+ upper?: boolean;
62
+ }
63
+
64
+ // String node
65
+ export interface StringSchemaNode extends SchemaNodeBase {
66
+ kind: "string";
67
+ minLength?: number;
68
+ maxLength?: number;
69
+ pattern?: string;
70
+ startsWith?: string;
71
+ endsWith?: string;
72
+ includes?: string;
73
+ format?: StringFormat;
74
+ }
75
+
76
+ export type StringFormat =
77
+ | "email"
78
+ | "url"
79
+ | "uuid"
80
+ | "ipv4"
81
+ | "ipv6"
82
+ | "date"
83
+ | "date-time";
84
+
85
+ // Numeric nodes
86
+ export interface NumericSchemaNode extends SchemaNodeBase {
87
+ kind:
88
+ | "number"
89
+ | "int"
90
+ | "float32"
91
+ | "float64"
92
+ | "int8"
93
+ | "int16"
94
+ | "int32"
95
+ | "int64"
96
+ | "uint8"
97
+ | "uint16"
98
+ | "uint32"
99
+ | "uint64";
100
+ min?: number;
101
+ max?: number;
102
+ exclusiveMin?: number;
103
+ exclusiveMax?: number;
104
+ multipleOf?: number;
105
+ }
106
+
107
+ // Simple nodes
108
+ export interface SimpleSchemaNode extends SchemaNodeBase {
109
+ kind: "any" | "unknown" | "never" | "null" | "bool";
110
+ }
111
+
112
+ // Literal node
113
+ export interface LiteralSchemaNode extends SchemaNodeBase {
114
+ kind: "literal";
115
+ value: string | number | boolean | null;
116
+ }
117
+
118
+ // Enum node
119
+ export interface EnumSchemaNode extends SchemaNodeBase {
120
+ kind: "enum";
121
+ values: (string | number)[];
122
+ }
123
+
124
+ // Array node
125
+ export interface ArraySchemaNode extends SchemaNodeBase {
126
+ kind: "array";
127
+ items: SchemaNode;
128
+ minItems?: number;
129
+ maxItems?: number;
130
+ }
131
+
132
+ // Tuple node
133
+ export interface TupleSchemaNode extends SchemaNodeBase {
134
+ kind: "tuple";
135
+ items: SchemaNode[];
136
+ }
137
+
138
+ // Object node
139
+ export interface ObjectSchemaNode extends SchemaNodeBase {
140
+ kind: "object";
141
+ properties: Record<string, SchemaNode>;
142
+ required: string[];
143
+ unknownKeys?: UnknownKeyMode;
144
+ }
145
+
146
+ // Record node
147
+ export interface RecordSchemaNode extends SchemaNodeBase {
148
+ kind: "record";
149
+ valueSchema: SchemaNode;
150
+ }
151
+
152
+ // Union node
153
+ export interface UnionSchemaNode extends SchemaNodeBase {
154
+ kind: "union";
155
+ variants: SchemaNode[];
156
+ }
157
+
158
+ // Intersection node
159
+ export interface IntersectionSchemaNode extends SchemaNodeBase {
160
+ kind: "intersection";
161
+ allOf: SchemaNode[];
162
+ }
163
+
164
+ // Optional node
165
+ export interface OptionalSchemaNode extends SchemaNodeBase {
166
+ kind: "optional";
167
+ inner: SchemaNode;
168
+ }
169
+
170
+ // Nullable node
171
+ export interface NullableSchemaNode extends SchemaNodeBase {
172
+ kind: "nullable";
173
+ inner: SchemaNode;
174
+ }
175
+
176
+ // Ref node
177
+ export interface RefSchemaNode extends SchemaNodeBase {
178
+ kind: "ref";
179
+ ref: string;
180
+ }
181
+
182
+ export type SchemaNode =
183
+ | StringSchemaNode
184
+ | NumericSchemaNode
185
+ | SimpleSchemaNode
186
+ | LiteralSchemaNode
187
+ | EnumSchemaNode
188
+ | ArraySchemaNode
189
+ | TupleSchemaNode
190
+ | ObjectSchemaNode
191
+ | RecordSchemaNode
192
+ | UnionSchemaNode
193
+ | IntersectionSchemaNode
194
+ | OptionalSchemaNode
195
+ | NullableSchemaNode
196
+ | RefSchemaNode;
197
+
198
+ // ---- Parse Result ----
199
+
200
+ export type ParseResult<T> =
201
+ | { success: true; data: T }
202
+ | { success: false; issues: ValidationIssue[] };
203
+
204
+ export interface ValidationIssue {
205
+ code: string;
206
+ message: string;
207
+ path: (string | number)[];
208
+ expected?: string;
209
+ received?: string;
210
+ meta?: Record<string, unknown>;
211
+ }
212
+
213
+ // ---- Interchange Document ----
214
+
215
+ export interface AnyValiDocument {
216
+ anyvaliVersion: string;
217
+ schemaVersion: string;
218
+ root: SchemaNode;
219
+ definitions: Record<string, SchemaNode>;
220
+ extensions: Record<string, Record<string, unknown>>;
221
+ }
222
+
223
+ // ---- Modes ----
224
+
225
+ export type ExportMode = "portable" | "extended";
226
+
227
+ export type UnknownKeyMode = "reject" | "strip" | "allow";
228
+
229
+ // ---- Parse Context (internal) ----
230
+
231
+ export interface ParseContext {
232
+ path: (string | number)[];
233
+ issues: ValidationIssue[];
234
+ definitions?: Record<string, SchemaNode>;
235
+ /** Tracks objects already being validated to detect circular references. */
236
+ seen?: WeakSet<object>;
237
+ /** Current recursion depth, used to bound unbounded recursion (DoS guard). */
238
+ depth?: number;
239
+ }
@@ -1,99 +1,99 @@
1
- import { describe, it, expect } from "vitest";
2
- import { array, tuple, record, string, int, bool, any } from "../../src/index.js";
3
-
4
- describe("ArraySchema", () => {
5
- it("accepts valid arrays", () => {
6
- const s = array(int());
7
- expect(s.parse([1, 2, 3])).toEqual([1, 2, 3]);
8
- });
9
-
10
- it("rejects non-arrays", () => {
11
- const s = array(int());
12
- expect(s.safeParse("not an array").success).toBe(false);
13
- });
14
-
15
- it("validates array items", () => {
16
- const s = array(int());
17
- const result = s.safeParse([1, "two", 3]);
18
- expect(result.success).toBe(false);
19
- if (!result.success) {
20
- expect(result.issues[0].path).toEqual([1]);
21
- }
22
- });
23
-
24
- it("validates minItems", () => {
25
- const s = array(int()).minItems(2);
26
- expect(s.parse([1, 2])).toEqual([1, 2]);
27
- expect(s.safeParse([1]).success).toBe(false);
28
- });
29
-
30
- it("validates maxItems", () => {
31
- const s = array(int()).maxItems(2);
32
- expect(s.parse([1, 2])).toEqual([1, 2]);
33
- expect(s.safeParse([1, 2, 3]).success).toBe(false);
34
- });
35
- });
36
-
37
- describe("TupleSchema", () => {
38
- it("accepts valid tuples", () => {
39
- const s = tuple([string(), int(), bool()]);
40
- expect(s.parse(["hello", 42, true])).toEqual(["hello", 42, true]);
41
- });
42
-
43
- it("rejects wrong length", () => {
44
- const s = tuple([string(), int()]);
45
- expect(s.safeParse(["hello"]).success).toBe(false);
46
- expect(s.safeParse(["hello", 42, true]).success).toBe(false);
47
- });
48
-
49
- it("validates element types", () => {
50
- const s = tuple([string(), int()]);
51
- const result = s.safeParse([42, "hello"]);
52
- expect(result.success).toBe(false);
53
- if (!result.success) {
54
- expect(result.issues[0].path).toEqual([0]);
55
- }
56
- });
57
- });
58
-
59
- describe("RecordSchema", () => {
60
- it("accepts valid records", () => {
61
- const s = record(int());
62
- expect(s.parse({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
63
- });
64
-
65
- it("rejects non-objects", () => {
66
- const s = record(int());
67
- expect(s.safeParse("not object").success).toBe(false);
68
- expect(s.safeParse(null).success).toBe(false);
69
- expect(s.safeParse([]).success).toBe(false);
70
- });
71
-
72
- it("validates record values", () => {
73
- const s = record(int());
74
- const result = s.safeParse({ a: 1, b: "two" });
75
- expect(result.success).toBe(false);
76
- if (!result.success) {
77
- expect(result.issues[0].path).toEqual(["b"]);
78
- }
79
- });
80
-
81
- it("preserves __proto__ as data in records", () => {
82
- const s = record(any());
83
- const input = JSON.parse(
84
- '{"safe":1,"__proto__":{"polluted":"yes"}}'
85
- ) as Record<
86
- string,
87
- unknown
88
- >;
89
-
90
- const result = s.parse(input);
91
-
92
- expect(result.safe).toBe(1);
93
- expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
94
- expect(Object.prototype.hasOwnProperty.call(result, "__proto__")).toBe(true);
95
- expect(
96
- Object.getOwnPropertyDescriptor(result, "__proto__")?.value
97
- ).toEqual({ polluted: "yes" });
98
- });
99
- });
1
+ import { describe, it, expect } from "vitest";
2
+ import { array, tuple, record, string, int, bool, any } from "../../src/index.js";
3
+
4
+ describe("ArraySchema", () => {
5
+ it("accepts valid arrays", () => {
6
+ const s = array(int());
7
+ expect(s.parse([1, 2, 3])).toEqual([1, 2, 3]);
8
+ });
9
+
10
+ it("rejects non-arrays", () => {
11
+ const s = array(int());
12
+ expect(s.safeParse("not an array").success).toBe(false);
13
+ });
14
+
15
+ it("validates array items", () => {
16
+ const s = array(int());
17
+ const result = s.safeParse([1, "two", 3]);
18
+ expect(result.success).toBe(false);
19
+ if (!result.success) {
20
+ expect(result.issues[0].path).toEqual([1]);
21
+ }
22
+ });
23
+
24
+ it("validates minItems", () => {
25
+ const s = array(int()).minItems(2);
26
+ expect(s.parse([1, 2])).toEqual([1, 2]);
27
+ expect(s.safeParse([1]).success).toBe(false);
28
+ });
29
+
30
+ it("validates maxItems", () => {
31
+ const s = array(int()).maxItems(2);
32
+ expect(s.parse([1, 2])).toEqual([1, 2]);
33
+ expect(s.safeParse([1, 2, 3]).success).toBe(false);
34
+ });
35
+ });
36
+
37
+ describe("TupleSchema", () => {
38
+ it("accepts valid tuples", () => {
39
+ const s = tuple([string(), int(), bool()]);
40
+ expect(s.parse(["hello", 42, true])).toEqual(["hello", 42, true]);
41
+ });
42
+
43
+ it("rejects wrong length", () => {
44
+ const s = tuple([string(), int()]);
45
+ expect(s.safeParse(["hello"]).success).toBe(false);
46
+ expect(s.safeParse(["hello", 42, true]).success).toBe(false);
47
+ });
48
+
49
+ it("validates element types", () => {
50
+ const s = tuple([string(), int()]);
51
+ const result = s.safeParse([42, "hello"]);
52
+ expect(result.success).toBe(false);
53
+ if (!result.success) {
54
+ expect(result.issues[0].path).toEqual([0]);
55
+ }
56
+ });
57
+ });
58
+
59
+ describe("RecordSchema", () => {
60
+ it("accepts valid records", () => {
61
+ const s = record(int());
62
+ expect(s.parse({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
63
+ });
64
+
65
+ it("rejects non-objects", () => {
66
+ const s = record(int());
67
+ expect(s.safeParse("not object").success).toBe(false);
68
+ expect(s.safeParse(null).success).toBe(false);
69
+ expect(s.safeParse([]).success).toBe(false);
70
+ });
71
+
72
+ it("validates record values", () => {
73
+ const s = record(int());
74
+ const result = s.safeParse({ a: 1, b: "two" });
75
+ expect(result.success).toBe(false);
76
+ if (!result.success) {
77
+ expect(result.issues[0].path).toEqual(["b"]);
78
+ }
79
+ });
80
+
81
+ it("preserves __proto__ as data in records", () => {
82
+ const s = record(any());
83
+ const input = JSON.parse(
84
+ '{"safe":1,"__proto__":{"polluted":"yes"}}'
85
+ ) as Record<
86
+ string,
87
+ unknown
88
+ >;
89
+
90
+ const result = s.parse(input);
91
+
92
+ expect(result.safe).toBe(1);
93
+ expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
94
+ expect(Object.prototype.hasOwnProperty.call(result, "__proto__")).toBe(true);
95
+ expect(
96
+ Object.getOwnPropertyDescriptor(result, "__proto__")?.value
97
+ ).toEqual({ polluted: "yes" });
98
+ });
99
+ });