@vertz/schema 0.2.0 → 0.2.1
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/README.md +285 -382
- package/dist/index.d.ts +5 -10
- package/dist/index.js +33 -10
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,75 +1,58 @@
|
|
|
1
1
|
# @vertz/schema
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
A powerful validation library with end-to-end type inference, inspired by Zod. Define schemas with a fluent API and get full TypeScript types automatically.
|
|
6
|
-
|
|
7
|
-
## Prerequisites
|
|
8
|
-
|
|
9
|
-
- **Node.js** 18+ or **Bun** 1.0+
|
|
10
|
-
- **TypeScript** 5.0+
|
|
3
|
+
Type-safe validation with end-to-end type inference. Define schemas with a fluent API, get full TypeScript types automatically.
|
|
11
4
|
|
|
12
5
|
## Installation
|
|
13
6
|
|
|
14
7
|
```bash
|
|
15
|
-
# npm
|
|
16
|
-
npm install @vertz/schema
|
|
17
|
-
|
|
18
|
-
# bun
|
|
19
8
|
bun add @vertz/schema
|
|
20
9
|
```
|
|
21
10
|
|
|
22
11
|
## Quick Start
|
|
23
12
|
|
|
24
13
|
```typescript
|
|
25
|
-
import { s } from '@vertz/schema';
|
|
14
|
+
import { s, type Infer } from '@vertz/schema';
|
|
26
15
|
|
|
27
16
|
// Define a schema
|
|
28
17
|
const userSchema = s.object({
|
|
29
18
|
name: s.string().min(1),
|
|
30
|
-
email: s.
|
|
19
|
+
email: s.email(),
|
|
31
20
|
age: s.number().int().min(18),
|
|
32
21
|
});
|
|
33
22
|
|
|
34
|
-
//
|
|
23
|
+
// Infer the type
|
|
24
|
+
type User = Infer<typeof userSchema>;
|
|
25
|
+
// { name: string; email: string; age: number }
|
|
26
|
+
|
|
27
|
+
// Parse (throws on invalid)
|
|
35
28
|
const user = userSchema.parse({
|
|
36
29
|
name: 'Alice',
|
|
37
30
|
email: 'alice@example.com',
|
|
38
31
|
age: 25,
|
|
39
32
|
});
|
|
40
33
|
|
|
41
|
-
// Safe parse (
|
|
42
|
-
const result = userSchema.safeParse(
|
|
43
|
-
name: 'Bob',
|
|
44
|
-
email: 'not-an-email',
|
|
45
|
-
age: 17,
|
|
46
|
-
});
|
|
47
|
-
|
|
34
|
+
// Safe parse (never throws)
|
|
35
|
+
const result = userSchema.safeParse(data);
|
|
48
36
|
if (result.success) {
|
|
49
|
-
console.log(
|
|
37
|
+
console.log(result.data);
|
|
50
38
|
} else {
|
|
51
|
-
console.log(
|
|
39
|
+
console.log(result.error.issues);
|
|
52
40
|
}
|
|
53
|
-
|
|
54
|
-
// Type inference
|
|
55
|
-
type User = typeof userSchema._output;
|
|
56
|
-
// ✅ { name: string; email: string; age: number }
|
|
57
41
|
```
|
|
58
42
|
|
|
59
|
-
##
|
|
43
|
+
## Schema Types
|
|
60
44
|
|
|
61
45
|
### Primitives
|
|
62
46
|
|
|
63
47
|
```typescript
|
|
64
|
-
import { s } from '@vertz/schema';
|
|
65
|
-
|
|
66
48
|
s.string() // string
|
|
67
49
|
s.number() // number
|
|
68
50
|
s.boolean() // boolean
|
|
69
51
|
s.bigint() // bigint
|
|
70
52
|
s.date() // Date
|
|
71
53
|
s.symbol() // symbol
|
|
72
|
-
s.int() // number (integer)
|
|
54
|
+
s.int() // number (integer-only)
|
|
55
|
+
s.nan() // NaN
|
|
73
56
|
```
|
|
74
57
|
|
|
75
58
|
### Special Types
|
|
@@ -83,74 +66,65 @@ s.void() // void
|
|
|
83
66
|
s.never() // never
|
|
84
67
|
```
|
|
85
68
|
|
|
86
|
-
###
|
|
69
|
+
### Composites
|
|
87
70
|
|
|
88
71
|
```typescript
|
|
89
72
|
// Objects
|
|
90
|
-
|
|
73
|
+
s.object({
|
|
91
74
|
name: s.string(),
|
|
92
75
|
age: s.number(),
|
|
93
|
-
})
|
|
76
|
+
})
|
|
94
77
|
|
|
95
78
|
// Arrays
|
|
96
|
-
|
|
79
|
+
s.array(s.number())
|
|
97
80
|
|
|
98
81
|
// Tuples
|
|
99
|
-
|
|
82
|
+
s.tuple([s.string(), s.number()])
|
|
100
83
|
|
|
101
84
|
// Enums
|
|
102
|
-
|
|
85
|
+
s.enum(['admin', 'user', 'guest'])
|
|
103
86
|
|
|
104
87
|
// Literals
|
|
105
|
-
|
|
88
|
+
s.literal('active')
|
|
106
89
|
|
|
107
90
|
// Unions
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
91
|
+
s.union([s.string(), s.number()])
|
|
92
|
+
|
|
93
|
+
// Discriminated unions
|
|
94
|
+
s.discriminatedUnion('type', [
|
|
95
|
+
s.object({ type: s.literal('text'), content: s.string() }),
|
|
96
|
+
s.object({ type: s.literal('image'), url: s.url() }),
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
// Intersections
|
|
100
|
+
s.intersection(
|
|
101
|
+
s.object({ id: s.string() }),
|
|
102
|
+
s.object({ name: s.string() }),
|
|
103
|
+
)
|
|
113
104
|
|
|
114
105
|
// Records (dynamic keys)
|
|
115
|
-
|
|
106
|
+
s.record(s.string())
|
|
116
107
|
|
|
117
108
|
// Maps
|
|
118
|
-
|
|
109
|
+
s.map(s.string(), s.number())
|
|
119
110
|
|
|
120
111
|
// Sets
|
|
121
|
-
|
|
122
|
-
```
|
|
112
|
+
s.set(s.string())
|
|
123
113
|
|
|
124
|
-
|
|
114
|
+
// Files
|
|
115
|
+
s.file()
|
|
125
116
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.regex(/^[a-z]+$/) // Pattern matching
|
|
132
|
-
.startsWith('hello') // Prefix check
|
|
133
|
-
.endsWith('world') // Suffix check
|
|
134
|
-
.includes('mid') // Substring check
|
|
135
|
-
.uppercase() // Must be all uppercase
|
|
136
|
-
.lowercase() // Must be all lowercase
|
|
137
|
-
.nonempty() // Alias for .min(1)
|
|
138
|
-
.trim(); // Trim whitespace (transforms)
|
|
139
|
-
```
|
|
117
|
+
// Custom validators
|
|
118
|
+
s.custom<number>(
|
|
119
|
+
(val) => typeof val === 'number' && val % 2 === 0,
|
|
120
|
+
'Must be an even number',
|
|
121
|
+
)
|
|
140
122
|
|
|
141
|
-
|
|
123
|
+
// Instance checks
|
|
124
|
+
s.instanceof(Date)
|
|
142
125
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
.int() // Must be integer
|
|
146
|
-
.positive() // > 0
|
|
147
|
-
.negative() // < 0
|
|
148
|
-
.nonpositive() // <= 0
|
|
149
|
-
.nonnegative() // >= 0
|
|
150
|
-
.min(0) // Minimum value
|
|
151
|
-
.max(100) // Maximum value
|
|
152
|
-
.multipleOf(5) // Must be divisible by n
|
|
153
|
-
.finite(); // No Infinity or NaN
|
|
126
|
+
// Recursive types
|
|
127
|
+
s.lazy(() => categorySchema)
|
|
154
128
|
```
|
|
155
129
|
|
|
156
130
|
### Format Validators
|
|
@@ -159,151 +133,205 @@ Built-in validators for common formats:
|
|
|
159
133
|
|
|
160
134
|
```typescript
|
|
161
135
|
s.email() // Email address
|
|
162
|
-
s.uuid() // UUID
|
|
136
|
+
s.uuid() // UUID
|
|
163
137
|
s.url() // HTTP(S) URL
|
|
164
138
|
s.hostname() // Valid hostname
|
|
165
139
|
s.ipv4() // IPv4 address
|
|
166
140
|
s.ipv6() // IPv6 address
|
|
167
141
|
s.base64() // Base64 string
|
|
168
142
|
s.hex() // Hexadecimal string
|
|
169
|
-
s.jwt() // JWT token (format only
|
|
143
|
+
s.jwt() // JWT token (format only)
|
|
170
144
|
s.cuid() // CUID
|
|
171
145
|
s.ulid() // ULID
|
|
172
146
|
s.nanoid() // Nano ID
|
|
173
147
|
|
|
174
148
|
// ISO formats
|
|
175
|
-
s.iso.date() //
|
|
176
|
-
s.iso.time() //
|
|
149
|
+
s.iso.date() // YYYY-MM-DD
|
|
150
|
+
s.iso.time() // HH:MM:SS
|
|
177
151
|
s.iso.datetime() // ISO 8601 datetime
|
|
178
152
|
s.iso.duration() // ISO 8601 duration (P1Y2M3D)
|
|
179
153
|
```
|
|
180
154
|
|
|
181
|
-
###
|
|
155
|
+
### Database Enum Bridge
|
|
182
156
|
|
|
183
157
|
```typescript
|
|
184
|
-
|
|
185
|
-
|
|
158
|
+
// Convert a @vertz/db enum column to a schema
|
|
159
|
+
s.fromDbEnum(statusColumn)
|
|
160
|
+
```
|
|
186
161
|
|
|
187
|
-
|
|
188
|
-
// string | null
|
|
162
|
+
## Modifiers
|
|
189
163
|
|
|
190
|
-
|
|
191
|
-
|
|
164
|
+
### Optional & Nullable
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
s.string().optional() // string | undefined
|
|
168
|
+
s.string().nullable() // string | null
|
|
169
|
+
s.string().nullable().optional() // string | null | undefined
|
|
192
170
|
```
|
|
193
171
|
|
|
194
172
|
### Default Values
|
|
195
173
|
|
|
196
174
|
```typescript
|
|
197
|
-
|
|
175
|
+
s.string().default('hello')
|
|
176
|
+
s.number().default(() => Math.random())
|
|
198
177
|
|
|
199
|
-
|
|
200
|
-
schema.parse('world'); // 'world'
|
|
178
|
+
s.string().default('hello').parse(undefined) // 'hello'
|
|
201
179
|
```
|
|
202
180
|
|
|
203
181
|
### Transformations
|
|
204
182
|
|
|
205
183
|
```typescript
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
schema.parse('hello'); // 'HELLO'
|
|
209
|
-
|
|
210
|
-
// Chain transformations
|
|
211
|
-
const trimmed = s.string().trim().transform((s) => s.toUpperCase());
|
|
212
|
-
trimmed.parse(' hello '); // 'HELLO'
|
|
184
|
+
s.string().transform((val) => val.toUpperCase())
|
|
185
|
+
s.string().trim().transform((s) => s.split(','))
|
|
213
186
|
```
|
|
214
187
|
|
|
215
|
-
### Refinements
|
|
188
|
+
### Refinements
|
|
216
189
|
|
|
217
190
|
```typescript
|
|
218
|
-
|
|
191
|
+
// Simple predicate
|
|
192
|
+
s.string().refine(
|
|
219
193
|
(val) => val.includes('@'),
|
|
220
194
|
{ message: 'Must contain @' },
|
|
221
|
-
)
|
|
195
|
+
)
|
|
222
196
|
|
|
223
197
|
// Multiple refinements
|
|
224
|
-
|
|
198
|
+
s.string()
|
|
225
199
|
.min(8)
|
|
226
|
-
.refine((val) => /[A-Z]/.test(val), {
|
|
227
|
-
|
|
228
|
-
})
|
|
229
|
-
.refine((val) => /[0-9]/.test(val), {
|
|
230
|
-
message: 'Must contain number',
|
|
231
|
-
});
|
|
200
|
+
.refine((val) => /[A-Z]/.test(val), { message: 'Need uppercase' })
|
|
201
|
+
.refine((val) => /[0-9]/.test(val), { message: 'Need digit' })
|
|
232
202
|
```
|
|
233
203
|
|
|
234
|
-
### Super Refine
|
|
204
|
+
### Super Refine
|
|
205
|
+
|
|
206
|
+
Access the refinement context for cross-field validation:
|
|
235
207
|
|
|
236
208
|
```typescript
|
|
237
|
-
|
|
209
|
+
s.object({
|
|
238
210
|
password: s.string(),
|
|
239
|
-
|
|
211
|
+
confirm: s.string(),
|
|
240
212
|
}).superRefine((data, ctx) => {
|
|
241
|
-
if (data.password !== data.
|
|
213
|
+
if (data.password !== data.confirm) {
|
|
242
214
|
ctx.addIssue({
|
|
243
215
|
code: 'custom',
|
|
244
|
-
path: ['
|
|
216
|
+
path: ['confirm'],
|
|
245
217
|
message: 'Passwords must match',
|
|
246
218
|
});
|
|
247
219
|
}
|
|
248
|
-
})
|
|
220
|
+
})
|
|
249
221
|
```
|
|
250
222
|
|
|
251
223
|
### Branded Types
|
|
252
224
|
|
|
253
|
-
Create nominal types that are structurally identical but semantically distinct:
|
|
254
|
-
|
|
255
225
|
```typescript
|
|
256
|
-
const
|
|
257
|
-
const
|
|
226
|
+
const UserId = s.string().uuid().brand('UserId');
|
|
227
|
+
const PostId = s.string().uuid().brand('PostId');
|
|
258
228
|
|
|
259
|
-
type UserId = typeof
|
|
260
|
-
type PostId = typeof
|
|
229
|
+
type UserId = Infer<typeof UserId>; // string & { __brand: 'UserId' }
|
|
230
|
+
type PostId = Infer<typeof PostId>; // string & { __brand: 'PostId' }
|
|
261
231
|
|
|
262
|
-
// Type error: UserId and PostId are not assignable to each other
|
|
263
232
|
function getUser(id: UserId) { /* ... */ }
|
|
264
|
-
|
|
233
|
+
getUser(UserId.parse('...')); // OK
|
|
234
|
+
getUser(PostId.parse('...')); // Type error
|
|
235
|
+
```
|
|
265
236
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
237
|
+
### Catch (Error Recovery)
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
s.number().catch(0).parse('invalid') // 0
|
|
269
241
|
```
|
|
270
242
|
|
|
271
243
|
### Readonly
|
|
272
244
|
|
|
273
|
-
|
|
245
|
+
```typescript
|
|
246
|
+
s.object({ tags: s.array(s.string()) }).readonly()
|
|
247
|
+
// Readonly<{ tags: readonly string[] }>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Pipe
|
|
251
|
+
|
|
252
|
+
Chain schemas sequentially:
|
|
274
253
|
|
|
275
254
|
```typescript
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
255
|
+
s.string().pipe(s.coerce.number())
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## String Validations
|
|
280
259
|
|
|
281
|
-
|
|
282
|
-
|
|
260
|
+
```typescript
|
|
261
|
+
s.string()
|
|
262
|
+
.min(3) // min length
|
|
263
|
+
.max(20) // max length
|
|
264
|
+
.length(10) // exact length
|
|
265
|
+
.regex(/^[a-z]+$/) // pattern
|
|
266
|
+
.startsWith('hello')
|
|
267
|
+
.endsWith('world')
|
|
268
|
+
.includes('mid')
|
|
269
|
+
.uppercase() // must be uppercase
|
|
270
|
+
.lowercase() // must be lowercase
|
|
271
|
+
.trim() // trims whitespace (transform)
|
|
272
|
+
.toLowerCase() // converts to lowercase (transform)
|
|
273
|
+
.toUpperCase() // converts to uppercase (transform)
|
|
283
274
|
```
|
|
284
275
|
|
|
285
|
-
|
|
276
|
+
## Number Validations
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
s.number()
|
|
280
|
+
.int() // integer only
|
|
281
|
+
.positive() // > 0
|
|
282
|
+
.negative() // < 0
|
|
283
|
+
.nonnegative() // >= 0
|
|
284
|
+
.nonpositive() // <= 0
|
|
285
|
+
.min(0) // >= n
|
|
286
|
+
.max(100) // <= n
|
|
287
|
+
.gt(0) // > n
|
|
288
|
+
.lt(100) // < n
|
|
289
|
+
.multipleOf(5) // divisible by n
|
|
290
|
+
.finite() // no Infinity
|
|
291
|
+
```
|
|
286
292
|
|
|
287
|
-
|
|
293
|
+
## Array Validations
|
|
288
294
|
|
|
289
295
|
```typescript
|
|
290
|
-
|
|
296
|
+
s.array(s.string())
|
|
297
|
+
.min(1) // at least 1 element
|
|
298
|
+
.max(10) // at most 10 elements
|
|
299
|
+
.length(5) // exactly 5 elements
|
|
300
|
+
```
|
|
291
301
|
|
|
292
|
-
|
|
293
|
-
|
|
302
|
+
## Object Methods
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const base = s.object({ id: s.string(), name: s.string(), email: s.email() });
|
|
306
|
+
|
|
307
|
+
base.pick('id', 'name') // { id: string; name: string }
|
|
308
|
+
base.omit('email') // { id: string; name: string }
|
|
309
|
+
base.partial() // { id?: string; name?: string; email?: string }
|
|
310
|
+
base.required() // all fields required
|
|
311
|
+
base.extend({ age: s.number() }) // add fields
|
|
312
|
+
base.merge(otherSchema) // merge two object schemas
|
|
313
|
+
base.strict() // reject unknown keys
|
|
314
|
+
base.passthrough() // pass through unknown keys
|
|
315
|
+
base.catchall(s.string()) // validate unknown keys with schema
|
|
316
|
+
base.keyof() // ['id', 'name', 'email']
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Tuple Rest Elements
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
s.tuple([s.string(), s.number()]).rest(s.boolean())
|
|
323
|
+
// [string, number, ...boolean[]]
|
|
294
324
|
```
|
|
295
325
|
|
|
296
326
|
## Parsing
|
|
297
327
|
|
|
298
328
|
### `.parse(data)`
|
|
299
329
|
|
|
300
|
-
|
|
330
|
+
Returns the parsed value. Throws `ParseError` on failure:
|
|
301
331
|
|
|
302
332
|
```typescript
|
|
303
|
-
const schema = s.string();
|
|
304
|
-
|
|
305
333
|
try {
|
|
306
|
-
const value = schema.parse(
|
|
334
|
+
const value = schema.parse(data);
|
|
307
335
|
} catch (error) {
|
|
308
336
|
if (error instanceof ParseError) {
|
|
309
337
|
console.log(error.issues);
|
|
@@ -313,328 +341,203 @@ try {
|
|
|
313
341
|
|
|
314
342
|
### `.safeParse(data)`
|
|
315
343
|
|
|
316
|
-
Returns a result object
|
|
344
|
+
Returns a result object. Never throws:
|
|
317
345
|
|
|
318
346
|
```typescript
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
const result = schema.safeParse('42');
|
|
347
|
+
const result = schema.safeParse(data);
|
|
322
348
|
|
|
323
349
|
if (result.success) {
|
|
324
|
-
|
|
350
|
+
result.data // parsed value
|
|
325
351
|
} else {
|
|
326
|
-
|
|
352
|
+
result.error // ParseError
|
|
353
|
+
result.error.issues // ValidationIssue[]
|
|
327
354
|
}
|
|
328
355
|
```
|
|
329
356
|
|
|
330
357
|
## Type Inference
|
|
331
358
|
|
|
332
|
-
### Output Types (Parsed Value)
|
|
333
|
-
|
|
334
359
|
```typescript
|
|
335
|
-
import type { Infer, Output } from '@vertz/schema';
|
|
336
|
-
|
|
337
|
-
const schema = s.object({
|
|
338
|
-
name: s.string(),
|
|
339
|
-
age: s.number().optional(),
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
// All equivalent:
|
|
343
|
-
type User1 = typeof schema._output;
|
|
344
|
-
type User2 = Infer<typeof schema>;
|
|
345
|
-
type User3 = Output<typeof schema>;
|
|
346
|
-
|
|
347
|
-
// Result: { name: string; age?: number }
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Input Types (Before Parsing)
|
|
351
|
-
|
|
352
|
-
Use `Input<T>` for the type before transformations:
|
|
353
|
-
|
|
354
|
-
```typescript
|
|
355
|
-
import type { Input } from '@vertz/schema';
|
|
360
|
+
import type { Infer, Input, Output } from '@vertz/schema';
|
|
356
361
|
|
|
357
362
|
const schema = s.string().transform((s) => s.length);
|
|
358
363
|
|
|
359
364
|
type In = Input<typeof schema>; // string
|
|
360
365
|
type Out = Output<typeof schema>; // number
|
|
366
|
+
type Out2 = Infer<typeof schema>; // number (alias for Output)
|
|
367
|
+
|
|
368
|
+
// Also available as instance properties:
|
|
369
|
+
type In3 = typeof schema._input;
|
|
370
|
+
type Out3 = typeof schema._output;
|
|
361
371
|
```
|
|
362
372
|
|
|
363
373
|
## Coercion
|
|
364
374
|
|
|
365
|
-
Convert values to the target type:
|
|
375
|
+
Convert values to the target type before validation:
|
|
366
376
|
|
|
367
377
|
```typescript
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
378
|
+
s.coerce.string() // String(value)
|
|
379
|
+
s.coerce.number() // Number(value)
|
|
380
|
+
s.coerce.boolean() // Boolean(value)
|
|
381
|
+
s.coerce.bigint() // BigInt(value)
|
|
382
|
+
s.coerce.date() // new Date(value)
|
|
371
383
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// Available coercions:
|
|
376
|
-
s.coerce.string() // → string
|
|
377
|
-
s.coerce.number() // → number
|
|
378
|
-
s.coerce.boolean() // → boolean
|
|
379
|
-
s.coerce.bigint() // → bigint
|
|
380
|
-
s.coerce.date() // → Date
|
|
384
|
+
s.coerce.number().parse('42') // 42
|
|
385
|
+
s.coerce.date().parse('2024-01-01') // Date object
|
|
381
386
|
```
|
|
382
387
|
|
|
383
|
-
##
|
|
388
|
+
## Error Handling
|
|
384
389
|
|
|
385
|
-
|
|
390
|
+
### ParseError
|
|
386
391
|
|
|
387
392
|
```typescript
|
|
388
|
-
import {
|
|
393
|
+
import { ParseError } from '@vertz/schema';
|
|
389
394
|
|
|
390
|
-
const
|
|
391
|
-
name: s.string().min(1),
|
|
392
|
-
age: s.number().int().min(0),
|
|
393
|
-
});
|
|
395
|
+
const result = schema.safeParse(data);
|
|
394
396
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
age: { type: 'integer', minimum: 0 }
|
|
402
|
-
},
|
|
403
|
-
required: ['name', 'age']
|
|
397
|
+
if (!result.success) {
|
|
398
|
+
for (const issue of result.error.issues) {
|
|
399
|
+
console.log(issue.code); // 'invalid_type', 'too_small', etc.
|
|
400
|
+
console.log(issue.message); // human-readable message
|
|
401
|
+
console.log(issue.path); // ['address', 'street']
|
|
402
|
+
}
|
|
404
403
|
}
|
|
405
|
-
*/
|
|
406
404
|
```
|
|
407
405
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
Register and reuse schemas by name:
|
|
406
|
+
### Error Codes
|
|
411
407
|
|
|
412
408
|
```typescript
|
|
413
|
-
import {
|
|
409
|
+
import { ErrorCode } from '@vertz/schema';
|
|
414
410
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const userSchema = registry.get('User');
|
|
411
|
+
ErrorCode.InvalidType // 'invalid_type'
|
|
412
|
+
ErrorCode.TooSmall // 'too_small'
|
|
413
|
+
ErrorCode.TooBig // 'too_big'
|
|
414
|
+
ErrorCode.InvalidString // 'invalid_string'
|
|
415
|
+
ErrorCode.InvalidEnumValue // 'invalid_enum_value'
|
|
416
|
+
ErrorCode.InvalidLiteral // 'invalid_literal'
|
|
417
|
+
ErrorCode.InvalidUnion // 'invalid_union'
|
|
418
|
+
ErrorCode.InvalidDate // 'invalid_date'
|
|
419
|
+
ErrorCode.MissingProperty // 'missing_property'
|
|
420
|
+
ErrorCode.UnrecognizedKeys // 'unrecognized_keys'
|
|
421
|
+
ErrorCode.Custom // 'custom'
|
|
422
|
+
ErrorCode.NotMultipleOf // 'not_multiple_of'
|
|
423
|
+
ErrorCode.NotFinite // 'not_finite'
|
|
429
424
|
```
|
|
430
425
|
|
|
431
|
-
##
|
|
426
|
+
## Result Type
|
|
432
427
|
|
|
433
|
-
|
|
428
|
+
Errors-as-values pattern for explicit error handling without try/catch:
|
|
434
429
|
|
|
435
430
|
```typescript
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
type: s.literal('text'),
|
|
439
|
-
content: s.string(),
|
|
440
|
-
}),
|
|
441
|
-
s.object({
|
|
442
|
-
type: s.literal('image'),
|
|
443
|
-
url: s.url(),
|
|
444
|
-
alt: s.string().optional(),
|
|
445
|
-
}),
|
|
446
|
-
]);
|
|
431
|
+
import { ok, err, unwrap, map, flatMap, match, matchErr } from '@vertz/schema';
|
|
432
|
+
import type { Result, Ok, Err } from '@vertz/schema';
|
|
447
433
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
434
|
+
// Create results
|
|
435
|
+
const success: Result<number, string> = ok(42);
|
|
436
|
+
const failure: Result<number, string> = err('not found');
|
|
451
437
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
438
|
+
// Check and extract
|
|
439
|
+
if (success.ok) {
|
|
440
|
+
success.data // 42
|
|
441
|
+
}
|
|
442
|
+
if (failure.ok === false) {
|
|
443
|
+
failure.error // 'not found'
|
|
458
444
|
}
|
|
459
445
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
subcategories: s.lazy(() => s.array(categorySchema)),
|
|
463
|
-
});
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### Intersection
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
const baseSchema = s.object({ id: s.string() });
|
|
470
|
-
const namedSchema = s.object({ name: s.string() });
|
|
471
|
-
|
|
472
|
-
const userSchema = s.intersection(baseSchema, namedSchema);
|
|
473
|
-
// { id: string; name: string }
|
|
474
|
-
```
|
|
446
|
+
// Unwrap (throws if Err)
|
|
447
|
+
const value = unwrap(success); // 42
|
|
475
448
|
|
|
476
|
-
|
|
449
|
+
// Map success value
|
|
450
|
+
const doubled = map(success, (n) => n * 2); // Ok(84)
|
|
477
451
|
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
'Must be an even number',
|
|
452
|
+
// Chain Result-returning functions
|
|
453
|
+
const chained = flatMap(success, (n) =>
|
|
454
|
+
n > 0 ? ok(n.toString()) : err('must be positive')
|
|
482
455
|
);
|
|
483
456
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
```typescript
|
|
491
|
-
const imageSchema = s.file()
|
|
492
|
-
.maxSize(5 * 1024 * 1024) // 5MB
|
|
493
|
-
.mimeType(['image/png', 'image/jpeg', 'image/webp']);
|
|
457
|
+
// Pattern matching
|
|
458
|
+
const message = match(result, {
|
|
459
|
+
ok: (data) => `Got ${data}`,
|
|
460
|
+
err: (error) => `Failed: ${error}`,
|
|
461
|
+
});
|
|
494
462
|
|
|
495
|
-
|
|
463
|
+
// Exhaustive error matching by code
|
|
464
|
+
const handled = matchErr(result, {
|
|
465
|
+
ok: (data) => data,
|
|
466
|
+
NOT_FOUND: (e) => fallback,
|
|
467
|
+
CONFLICT: (e) => retry(),
|
|
468
|
+
});
|
|
496
469
|
```
|
|
497
470
|
|
|
498
|
-
|
|
471
|
+
The `Result` type is used throughout `@vertz/db` for all query methods.
|
|
499
472
|
|
|
500
|
-
|
|
473
|
+
## JSON Schema Generation
|
|
501
474
|
|
|
502
475
|
```typescript
|
|
503
|
-
import {
|
|
504
|
-
import { s } from '@vertz/schema';
|
|
505
|
-
|
|
506
|
-
const moduleDef = createModuleDef({ name: 'users' });
|
|
476
|
+
import { toJSONSchema } from '@vertz/schema';
|
|
507
477
|
|
|
508
|
-
const
|
|
478
|
+
const schema = s.object({
|
|
509
479
|
name: s.string().min(1),
|
|
510
|
-
|
|
511
|
-
age: s.number().int().min(18),
|
|
480
|
+
age: s.number().int().min(0),
|
|
512
481
|
});
|
|
513
482
|
|
|
514
|
-
const
|
|
483
|
+
const jsonSchema = toJSONSchema(schema);
|
|
484
|
+
// {
|
|
485
|
+
// type: 'object',
|
|
486
|
+
// properties: {
|
|
487
|
+
// name: { type: 'string', minLength: 1 },
|
|
488
|
+
// age: { type: 'integer', minimum: 0 }
|
|
489
|
+
// },
|
|
490
|
+
// required: ['name', 'age']
|
|
491
|
+
// }
|
|
515
492
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
handler: (ctx) => {
|
|
519
|
-
// ctx.body is fully typed as { name: string; email: string; age: number }
|
|
520
|
-
const { name, email, age } = ctx.body;
|
|
521
|
-
return { created: true, user: { name, email, age } };
|
|
522
|
-
},
|
|
523
|
-
});
|
|
493
|
+
// Also available as instance method:
|
|
494
|
+
schema.toJSONSchema()
|
|
524
495
|
```
|
|
525
496
|
|
|
526
|
-
|
|
497
|
+
## Schema Registry
|
|
527
498
|
|
|
528
|
-
|
|
499
|
+
Register and retrieve schemas by name:
|
|
529
500
|
|
|
530
501
|
```typescript
|
|
531
|
-
import {
|
|
532
|
-
|
|
533
|
-
const result = schema.safeParse(data);
|
|
534
|
-
|
|
535
|
-
if (!result.success) {
|
|
536
|
-
const { error } = result;
|
|
537
|
-
|
|
538
|
-
console.log(error.issues);
|
|
539
|
-
/*
|
|
540
|
-
[
|
|
541
|
-
{
|
|
542
|
-
code: 'invalid_type',
|
|
543
|
-
expected: 'string',
|
|
544
|
-
received: 'number',
|
|
545
|
-
path: ['name'],
|
|
546
|
-
message: 'Expected string, received number'
|
|
547
|
-
},
|
|
548
|
-
...
|
|
549
|
-
]
|
|
550
|
-
*/
|
|
551
|
-
}
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
## Comparison to Zod
|
|
555
|
-
|
|
556
|
-
`@vertz/schema` is heavily inspired by Zod with similar API design:
|
|
557
|
-
|
|
558
|
-
| Feature | @vertz/schema | Zod |
|
|
559
|
-
|---------|--------------|-----|
|
|
560
|
-
| Type inference | ✅ | ✅ |
|
|
561
|
-
| Primitives | ✅ | ✅ |
|
|
562
|
-
| Objects/Arrays | ✅ | ✅ |
|
|
563
|
-
| Transformations | ✅ | ✅ |
|
|
564
|
-
| Refinements | ✅ | ✅ |
|
|
565
|
-
| Branded types | ✅ | ✅ |
|
|
566
|
-
| JSON Schema export | ✅ | ✅ |
|
|
567
|
-
| Schema registry | ✅ | ❌ |
|
|
568
|
-
| Format validators | ✅ (built-in) | ❌ (plugin) |
|
|
569
|
-
| ISO format methods | ✅ (`s.iso.*`) | ❌ |
|
|
570
|
-
|
|
571
|
-
If you're familiar with Zod, you should feel right at home!
|
|
572
|
-
|
|
573
|
-
## API Reference
|
|
574
|
-
|
|
575
|
-
### Factory Functions
|
|
502
|
+
import { SchemaRegistry } from '@vertz/schema';
|
|
576
503
|
|
|
577
|
-
|
|
504
|
+
// Register via .id()
|
|
505
|
+
const userSchema = s.object({ name: s.string() }).id('User');
|
|
578
506
|
|
|
579
|
-
|
|
580
|
-
|
|
507
|
+
// Retrieve
|
|
508
|
+
const schema = SchemaRegistry.get('User');
|
|
509
|
+
SchemaRegistry.has('User'); // true
|
|
510
|
+
SchemaRegistry.getAll(); // Map<string, Schema>
|
|
581
511
|
```
|
|
582
512
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
All schemas inherit these methods:
|
|
586
|
-
|
|
587
|
-
- `.parse(data)` — Parse and return (throws on error)
|
|
588
|
-
- `.safeParse(data)` — Parse and return `{ success, value?, error? }`
|
|
589
|
-
- `.optional()` — Make schema optional (`T | undefined`)
|
|
590
|
-
- `.nullable()` — Make schema nullable (`T | null`)
|
|
591
|
-
- `.nullish()` — Make schema nullish (`T | null | undefined`)
|
|
592
|
-
- `.default(value)` — Provide default value
|
|
593
|
-
- `.transform(fn)` — Transform the value after validation
|
|
594
|
-
- `.refine(fn, opts)` — Add custom validation
|
|
595
|
-
- `.superRefine(fn)` — Add custom validation with context
|
|
596
|
-
- `.brand<Brand>()` — Create branded type
|
|
597
|
-
- `.readonly()` — Mark as readonly
|
|
598
|
-
- `.catch(value)` — Provide fallback on error
|
|
599
|
-
- `.optional()` — Alias for `.or(s.undefined())`
|
|
600
|
-
|
|
601
|
-
## TypeScript Tips
|
|
602
|
-
|
|
603
|
-
### Extracting Types
|
|
513
|
+
## Schema Metadata
|
|
604
514
|
|
|
605
515
|
```typescript
|
|
606
|
-
const schema = s.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
})
|
|
610
|
-
|
|
611
|
-
// Extract output type
|
|
612
|
-
type User = typeof schema._output;
|
|
516
|
+
const schema = s.string()
|
|
517
|
+
.id('Username')
|
|
518
|
+
.describe('The user display name')
|
|
519
|
+
.meta({ deprecated: true })
|
|
520
|
+
.example('alice');
|
|
613
521
|
|
|
614
|
-
//
|
|
615
|
-
|
|
522
|
+
schema.metadata.id // 'Username'
|
|
523
|
+
schema.metadata.description // 'The user display name'
|
|
524
|
+
schema.metadata.meta // { deprecated: true }
|
|
525
|
+
schema.metadata.examples // ['alice']
|
|
616
526
|
```
|
|
617
527
|
|
|
618
|
-
|
|
528
|
+
## Preprocessing
|
|
529
|
+
|
|
530
|
+
Transform raw input before schema validation:
|
|
619
531
|
|
|
620
532
|
```typescript
|
|
621
|
-
|
|
622
|
-
id: s.string().uuid(),
|
|
623
|
-
createdAt: s.date(),
|
|
624
|
-
});
|
|
533
|
+
import { preprocess } from '@vertz/schema';
|
|
625
534
|
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
535
|
+
const schema = preprocess(
|
|
536
|
+
(val) => typeof val === 'string' ? val.trim() : val,
|
|
537
|
+
s.string().min(1),
|
|
538
|
+
);
|
|
629
539
|
```
|
|
630
540
|
|
|
631
|
-
## Performance
|
|
632
|
-
|
|
633
|
-
- Schema definitions are immutable and reusable
|
|
634
|
-
- No code generation — pure runtime validation
|
|
635
|
-
- Optimized for common cases (primitives, objects, arrays)
|
|
636
|
-
- JSON Schema generation is cached
|
|
637
|
-
|
|
638
541
|
## License
|
|
639
542
|
|
|
640
543
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,8 @@ declare class RefTracker {
|
|
|
56
56
|
getDefs(): Record<string, JSONSchemaObject>;
|
|
57
57
|
}
|
|
58
58
|
declare function toJSONSchema2(schema: SchemaAny): JSONSchemaObject;
|
|
59
|
+
import { Err, Ok, Result } from "@vertz/errors";
|
|
60
|
+
import { err, flatMap, isErr, isOk, map, match, matchErr, ok, unwrap } from "@vertz/errors";
|
|
59
61
|
declare enum SchemaType {
|
|
60
62
|
String = "string",
|
|
61
63
|
Number = "number",
|
|
@@ -93,13 +95,6 @@ interface SchemaMetadata {
|
|
|
93
95
|
meta: Record<string, unknown> | undefined;
|
|
94
96
|
examples: unknown[];
|
|
95
97
|
}
|
|
96
|
-
type SafeParseResult<T> = {
|
|
97
|
-
success: true;
|
|
98
|
-
data: T;
|
|
99
|
-
} | {
|
|
100
|
-
success: false;
|
|
101
|
-
error: ParseError;
|
|
102
|
-
};
|
|
103
98
|
type SchemaAny = Schema<any, any>;
|
|
104
99
|
/** Apply Readonly only to object types; leave primitives and `any` unchanged. */
|
|
105
100
|
type ReadonlyOutput<O> = 0 extends 1 & O ? O : O extends object ? Readonly<O> : O;
|
|
@@ -119,8 +114,8 @@ declare abstract class Schema<
|
|
|
119
114
|
abstract _schemaType(): SchemaType;
|
|
120
115
|
abstract _toJSONSchema(tracker: RefTracker): JSONSchemaObject;
|
|
121
116
|
abstract _clone(): Schema<O, I>;
|
|
122
|
-
parse(value: unknown): O
|
|
123
|
-
safeParse(value: unknown):
|
|
117
|
+
parse(value: unknown): Result<O, ParseError>;
|
|
118
|
+
safeParse(value: unknown): Result<O, ParseError>;
|
|
124
119
|
id(name: string): this;
|
|
125
120
|
describe(description: string): this;
|
|
126
121
|
meta(data: Record<string, unknown>): this;
|
|
@@ -786,4 +781,4 @@ declare const s: {
|
|
|
786
781
|
};
|
|
787
782
|
};
|
|
788
783
|
declare const schema: typeof s;
|
|
789
|
-
export { toJSONSchema2 as toJSONSchema, schema, s, preprocess, VoidSchema, ValidationIssue, UuidSchema, UrlSchema, UnknownSchema, UnionSchema, UndefinedSchema, UlidSchema, TupleSchema, TransformSchema, SymbolSchema, SuperRefinedSchema, StringSchema, SetSchema, SchemaType, SchemaRegistry, SchemaMetadata, SchemaAny, Schema,
|
|
784
|
+
export { unwrap, toJSONSchema2 as toJSONSchema, schema, s, preprocess, ok, matchErr, match, map, isOk, isErr, flatMap, err, VoidSchema, ValidationIssue, UuidSchema, UrlSchema, UnknownSchema, UnionSchema, UndefinedSchema, UlidSchema, TupleSchema, TransformSchema, SymbolSchema, SuperRefinedSchema, StringSchema, SetSchema, SchemaType, SchemaRegistry, SchemaMetadata, SchemaAny, Schema, Result, RefinementContext, RefinedSchema, RefTracker, RecordSchema, ReadonlySchema, ReadonlyOutput, PipeSchema, ParseError, ParseContext, Output, OptionalSchema, Ok, ObjectSchema, NumberSchema, NullableSchema, NullSchema, NeverSchema, NanoidSchema, NanSchema, MapSchema, LiteralSchema, LazySchema, JwtSchema, JSONSchemaObject, IsoTimeSchema, IsoDurationSchema, IsoDatetimeSchema, IsoDateSchema, Ipv6Schema, Ipv4Schema, IntersectionSchema, InstanceOfSchema, Input, Infer, HostnameSchema, HexSchema, FileSchema, ErrorCode, Err, EnumSchema, EmailSchema, DiscriminatedUnionSchema, DefaultSchema, DateSchema, CustomSchema, CuidSchema, CoercedStringSchema, CoercedNumberSchema, CoercedDateSchema, CoercedBooleanSchema, CoercedBigIntSchema, CatchSchema, BrandedSchema, BooleanSchema, BigIntSchema, Base64Schema, ArraySchema, AnySchema };
|
package/dist/index.js
CHANGED
|
@@ -102,6 +102,20 @@ function toJSONSchema(schema) {
|
|
|
102
102
|
return schema.toJSONSchema();
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// src/result.ts
|
|
106
|
+
import {
|
|
107
|
+
err,
|
|
108
|
+
flatMap,
|
|
109
|
+
isErr,
|
|
110
|
+
isOk,
|
|
111
|
+
map,
|
|
112
|
+
match,
|
|
113
|
+
matchErr,
|
|
114
|
+
ok,
|
|
115
|
+
unwrap,
|
|
116
|
+
unwrapOr
|
|
117
|
+
} from "@vertz/errors";
|
|
118
|
+
|
|
105
119
|
// src/core/schema.ts
|
|
106
120
|
class Schema {
|
|
107
121
|
_id;
|
|
@@ -115,21 +129,21 @@ class Schema {
|
|
|
115
129
|
const ctx = new ParseContext;
|
|
116
130
|
const result = this._runPipeline(value, ctx);
|
|
117
131
|
if (ctx.hasIssues()) {
|
|
118
|
-
|
|
132
|
+
return err(new ParseError(ctx.issues));
|
|
119
133
|
}
|
|
120
|
-
return result;
|
|
134
|
+
return ok(result);
|
|
121
135
|
}
|
|
122
136
|
safeParse(value) {
|
|
123
137
|
const ctx = new ParseContext;
|
|
124
138
|
try {
|
|
125
139
|
const data = this._runPipeline(value, ctx);
|
|
126
140
|
if (ctx.hasIssues()) {
|
|
127
|
-
return
|
|
141
|
+
return err(new ParseError(ctx.issues));
|
|
128
142
|
}
|
|
129
|
-
return
|
|
143
|
+
return ok(data);
|
|
130
144
|
} catch (e) {
|
|
131
145
|
if (e instanceof ParseError) {
|
|
132
|
-
return
|
|
146
|
+
return err(e);
|
|
133
147
|
}
|
|
134
148
|
throw e;
|
|
135
149
|
}
|
|
@@ -1440,10 +1454,10 @@ var IPV4_RE = /^(0|[1-9]\d{0,2})\.(0|[1-9]\d{0,2})\.(0|[1-9]\d{0,2})\.(0|[1-9]\d
|
|
|
1440
1454
|
class Ipv4Schema extends FormatSchema {
|
|
1441
1455
|
_errorMessage = "Invalid IPv4 address";
|
|
1442
1456
|
_validate(value) {
|
|
1443
|
-
const
|
|
1444
|
-
if (!
|
|
1457
|
+
const match2 = IPV4_RE.exec(value);
|
|
1458
|
+
if (!match2)
|
|
1445
1459
|
return false;
|
|
1446
|
-
return [
|
|
1460
|
+
return [match2[1], match2[2], match2[3], match2[4]].every((o) => Number(o) <= 255);
|
|
1447
1461
|
}
|
|
1448
1462
|
_jsonSchemaExtra() {
|
|
1449
1463
|
return { format: "ipv4" };
|
|
@@ -1619,7 +1633,7 @@ class IntersectionSchema extends Schema {
|
|
|
1619
1633
|
_parse(value, ctx) {
|
|
1620
1634
|
const leftResult = this._left.safeParse(value);
|
|
1621
1635
|
const rightResult = this._right.safeParse(value);
|
|
1622
|
-
if (!leftResult.
|
|
1636
|
+
if (!leftResult.ok || !rightResult.ok) {
|
|
1623
1637
|
ctx.addIssue({
|
|
1624
1638
|
code: "invalid_intersection" /* InvalidIntersection */,
|
|
1625
1639
|
message: "Value does not satisfy intersection"
|
|
@@ -2254,7 +2268,7 @@ class UnionSchema extends Schema {
|
|
|
2254
2268
|
_parse(value, ctx) {
|
|
2255
2269
|
for (const option of this._options) {
|
|
2256
2270
|
const result = option.safeParse(value);
|
|
2257
|
-
if (result.
|
|
2271
|
+
if (result.ok) {
|
|
2258
2272
|
return result.data;
|
|
2259
2273
|
}
|
|
2260
2274
|
}
|
|
@@ -2377,10 +2391,19 @@ var s = {
|
|
|
2377
2391
|
};
|
|
2378
2392
|
var schema = s;
|
|
2379
2393
|
export {
|
|
2394
|
+
unwrap,
|
|
2380
2395
|
toJSONSchema,
|
|
2381
2396
|
schema,
|
|
2382
2397
|
s,
|
|
2383
2398
|
preprocess,
|
|
2399
|
+
ok,
|
|
2400
|
+
matchErr,
|
|
2401
|
+
match,
|
|
2402
|
+
map,
|
|
2403
|
+
isOk,
|
|
2404
|
+
isErr,
|
|
2405
|
+
flatMap,
|
|
2406
|
+
err,
|
|
2384
2407
|
VoidSchema,
|
|
2385
2408
|
UuidSchema,
|
|
2386
2409
|
UrlSchema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/schema",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Type-safe schema definitions for Vertz",
|
|
@@ -26,10 +26,14 @@
|
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "bunup",
|
|
29
|
-
"test": "
|
|
29
|
+
"test": "bun test",
|
|
30
|
+
"test:coverage": "vitest run --coverage",
|
|
30
31
|
"test:watch": "vitest",
|
|
31
32
|
"typecheck": "tsc --noEmit"
|
|
32
33
|
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@vertz/errors": "0.2.1"
|
|
36
|
+
},
|
|
33
37
|
"devDependencies": {
|
|
34
38
|
"@vitest/coverage-v8": "^4.0.18",
|
|
35
39
|
"bunup": "latest",
|