@vertz/schema 0.1.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 ADDED
@@ -0,0 +1,543 @@
1
+ # @vertz/schema
2
+
3
+ Type-safe validation with end-to-end type inference. Define schemas with a fluent API, get full TypeScript types automatically.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @vertz/schema
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { s, type Infer } from '@vertz/schema';
15
+
16
+ // Define a schema
17
+ const userSchema = s.object({
18
+ name: s.string().min(1),
19
+ email: s.email(),
20
+ age: s.number().int().min(18),
21
+ });
22
+
23
+ // Infer the type
24
+ type User = Infer<typeof userSchema>;
25
+ // { name: string; email: string; age: number }
26
+
27
+ // Parse (throws on invalid)
28
+ const user = userSchema.parse({
29
+ name: 'Alice',
30
+ email: 'alice@example.com',
31
+ age: 25,
32
+ });
33
+
34
+ // Safe parse (never throws)
35
+ const result = userSchema.safeParse(data);
36
+ if (result.success) {
37
+ console.log(result.data);
38
+ } else {
39
+ console.log(result.error.issues);
40
+ }
41
+ ```
42
+
43
+ ## Schema Types
44
+
45
+ ### Primitives
46
+
47
+ ```typescript
48
+ s.string() // string
49
+ s.number() // number
50
+ s.boolean() // boolean
51
+ s.bigint() // bigint
52
+ s.date() // Date
53
+ s.symbol() // symbol
54
+ s.int() // number (integer-only)
55
+ s.nan() // NaN
56
+ ```
57
+
58
+ ### Special Types
59
+
60
+ ```typescript
61
+ s.any() // any
62
+ s.unknown() // unknown
63
+ s.null() // null
64
+ s.undefined() // undefined
65
+ s.void() // void
66
+ s.never() // never
67
+ ```
68
+
69
+ ### Composites
70
+
71
+ ```typescript
72
+ // Objects
73
+ s.object({
74
+ name: s.string(),
75
+ age: s.number(),
76
+ })
77
+
78
+ // Arrays
79
+ s.array(s.number())
80
+
81
+ // Tuples
82
+ s.tuple([s.string(), s.number()])
83
+
84
+ // Enums
85
+ s.enum(['admin', 'user', 'guest'])
86
+
87
+ // Literals
88
+ s.literal('active')
89
+
90
+ // Unions
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
+ )
104
+
105
+ // Records (dynamic keys)
106
+ s.record(s.string())
107
+
108
+ // Maps
109
+ s.map(s.string(), s.number())
110
+
111
+ // Sets
112
+ s.set(s.string())
113
+
114
+ // Files
115
+ s.file()
116
+
117
+ // Custom validators
118
+ s.custom<number>(
119
+ (val) => typeof val === 'number' && val % 2 === 0,
120
+ 'Must be an even number',
121
+ )
122
+
123
+ // Instance checks
124
+ s.instanceof(Date)
125
+
126
+ // Recursive types
127
+ s.lazy(() => categorySchema)
128
+ ```
129
+
130
+ ### Format Validators
131
+
132
+ Built-in validators for common formats:
133
+
134
+ ```typescript
135
+ s.email() // Email address
136
+ s.uuid() // UUID
137
+ s.url() // HTTP(S) URL
138
+ s.hostname() // Valid hostname
139
+ s.ipv4() // IPv4 address
140
+ s.ipv6() // IPv6 address
141
+ s.base64() // Base64 string
142
+ s.hex() // Hexadecimal string
143
+ s.jwt() // JWT token (format only)
144
+ s.cuid() // CUID
145
+ s.ulid() // ULID
146
+ s.nanoid() // Nano ID
147
+
148
+ // ISO formats
149
+ s.iso.date() // YYYY-MM-DD
150
+ s.iso.time() // HH:MM:SS
151
+ s.iso.datetime() // ISO 8601 datetime
152
+ s.iso.duration() // ISO 8601 duration (P1Y2M3D)
153
+ ```
154
+
155
+ ### Database Enum Bridge
156
+
157
+ ```typescript
158
+ // Convert a @vertz/db enum column to a schema
159
+ s.fromDbEnum(statusColumn)
160
+ ```
161
+
162
+ ## Modifiers
163
+
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
170
+ ```
171
+
172
+ ### Default Values
173
+
174
+ ```typescript
175
+ s.string().default('hello')
176
+ s.number().default(() => Math.random())
177
+
178
+ s.string().default('hello').parse(undefined) // 'hello'
179
+ ```
180
+
181
+ ### Transformations
182
+
183
+ ```typescript
184
+ s.string().transform((val) => val.toUpperCase())
185
+ s.string().trim().transform((s) => s.split(','))
186
+ ```
187
+
188
+ ### Refinements
189
+
190
+ ```typescript
191
+ // Simple predicate
192
+ s.string().refine(
193
+ (val) => val.includes('@'),
194
+ { message: 'Must contain @' },
195
+ )
196
+
197
+ // Multiple refinements
198
+ s.string()
199
+ .min(8)
200
+ .refine((val) => /[A-Z]/.test(val), { message: 'Need uppercase' })
201
+ .refine((val) => /[0-9]/.test(val), { message: 'Need digit' })
202
+ ```
203
+
204
+ ### Super Refine
205
+
206
+ Access the refinement context for cross-field validation:
207
+
208
+ ```typescript
209
+ s.object({
210
+ password: s.string(),
211
+ confirm: s.string(),
212
+ }).superRefine((data, ctx) => {
213
+ if (data.password !== data.confirm) {
214
+ ctx.addIssue({
215
+ code: 'custom',
216
+ path: ['confirm'],
217
+ message: 'Passwords must match',
218
+ });
219
+ }
220
+ })
221
+ ```
222
+
223
+ ### Branded Types
224
+
225
+ ```typescript
226
+ const UserId = s.string().uuid().brand('UserId');
227
+ const PostId = s.string().uuid().brand('PostId');
228
+
229
+ type UserId = Infer<typeof UserId>; // string & { __brand: 'UserId' }
230
+ type PostId = Infer<typeof PostId>; // string & { __brand: 'PostId' }
231
+
232
+ function getUser(id: UserId) { /* ... */ }
233
+ getUser(UserId.parse('...')); // OK
234
+ getUser(PostId.parse('...')); // Type error
235
+ ```
236
+
237
+ ### Catch (Error Recovery)
238
+
239
+ ```typescript
240
+ s.number().catch(0).parse('invalid') // 0
241
+ ```
242
+
243
+ ### Readonly
244
+
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:
253
+
254
+ ```typescript
255
+ s.string().pipe(s.coerce.number())
256
+ ```
257
+
258
+ ## String Validations
259
+
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)
274
+ ```
275
+
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
+ ```
292
+
293
+ ## Array Validations
294
+
295
+ ```typescript
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
+ ```
301
+
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[]]
324
+ ```
325
+
326
+ ## Parsing
327
+
328
+ ### `.parse(data)`
329
+
330
+ Returns the parsed value. Throws `ParseError` on failure:
331
+
332
+ ```typescript
333
+ try {
334
+ const value = schema.parse(data);
335
+ } catch (error) {
336
+ if (error instanceof ParseError) {
337
+ console.log(error.issues);
338
+ }
339
+ }
340
+ ```
341
+
342
+ ### `.safeParse(data)`
343
+
344
+ Returns a result object. Never throws:
345
+
346
+ ```typescript
347
+ const result = schema.safeParse(data);
348
+
349
+ if (result.success) {
350
+ result.data // parsed value
351
+ } else {
352
+ result.error // ParseError
353
+ result.error.issues // ValidationIssue[]
354
+ }
355
+ ```
356
+
357
+ ## Type Inference
358
+
359
+ ```typescript
360
+ import type { Infer, Input, Output } from '@vertz/schema';
361
+
362
+ const schema = s.string().transform((s) => s.length);
363
+
364
+ type In = Input<typeof schema>; // string
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;
371
+ ```
372
+
373
+ ## Coercion
374
+
375
+ Convert values to the target type before validation:
376
+
377
+ ```typescript
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)
383
+
384
+ s.coerce.number().parse('42') // 42
385
+ s.coerce.date().parse('2024-01-01') // Date object
386
+ ```
387
+
388
+ ## Error Handling
389
+
390
+ ### ParseError
391
+
392
+ ```typescript
393
+ import { ParseError } from '@vertz/schema';
394
+
395
+ const result = schema.safeParse(data);
396
+
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
+ }
403
+ }
404
+ ```
405
+
406
+ ### Error Codes
407
+
408
+ ```typescript
409
+ import { ErrorCode } from '@vertz/schema';
410
+
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'
424
+ ```
425
+
426
+ ## Result Type
427
+
428
+ Errors-as-values pattern for explicit error handling without try/catch:
429
+
430
+ ```typescript
431
+ import { ok, err, unwrap, map, flatMap, match, matchErr } from '@vertz/schema';
432
+ import type { Result, Ok, Err } from '@vertz/schema';
433
+
434
+ // Create results
435
+ const success: Result<number, string> = ok(42);
436
+ const failure: Result<number, string> = err('not found');
437
+
438
+ // Check and extract
439
+ if (success.ok) {
440
+ success.data // 42
441
+ }
442
+ if (failure.ok === false) {
443
+ failure.error // 'not found'
444
+ }
445
+
446
+ // Unwrap (throws if Err)
447
+ const value = unwrap(success); // 42
448
+
449
+ // Map success value
450
+ const doubled = map(success, (n) => n * 2); // Ok(84)
451
+
452
+ // Chain Result-returning functions
453
+ const chained = flatMap(success, (n) =>
454
+ n > 0 ? ok(n.toString()) : err('must be positive')
455
+ );
456
+
457
+ // Pattern matching
458
+ const message = match(result, {
459
+ ok: (data) => `Got ${data}`,
460
+ err: (error) => `Failed: ${error}`,
461
+ });
462
+
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
+ });
469
+ ```
470
+
471
+ The `Result` type is used throughout `@vertz/db` for all query methods.
472
+
473
+ ## JSON Schema Generation
474
+
475
+ ```typescript
476
+ import { toJSONSchema } from '@vertz/schema';
477
+
478
+ const schema = s.object({
479
+ name: s.string().min(1),
480
+ age: s.number().int().min(0),
481
+ });
482
+
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
+ // }
492
+
493
+ // Also available as instance method:
494
+ schema.toJSONSchema()
495
+ ```
496
+
497
+ ## Schema Registry
498
+
499
+ Register and retrieve schemas by name:
500
+
501
+ ```typescript
502
+ import { SchemaRegistry } from '@vertz/schema';
503
+
504
+ // Register via .id()
505
+ const userSchema = s.object({ name: s.string() }).id('User');
506
+
507
+ // Retrieve
508
+ const schema = SchemaRegistry.get('User');
509
+ SchemaRegistry.has('User'); // true
510
+ SchemaRegistry.getAll(); // Map<string, Schema>
511
+ ```
512
+
513
+ ## Schema Metadata
514
+
515
+ ```typescript
516
+ const schema = s.string()
517
+ .id('Username')
518
+ .describe('The user display name')
519
+ .meta({ deprecated: true })
520
+ .example('alice');
521
+
522
+ schema.metadata.id // 'Username'
523
+ schema.metadata.description // 'The user display name'
524
+ schema.metadata.meta // { deprecated: true }
525
+ schema.metadata.examples // ['alice']
526
+ ```
527
+
528
+ ## Preprocessing
529
+
530
+ Transform raw input before schema validation:
531
+
532
+ ```typescript
533
+ import { preprocess } from '@vertz/schema';
534
+
535
+ const schema = preprocess(
536
+ (val) => typeof val === 'string' ? val.trim() : val,
537
+ s.string().min(1),
538
+ );
539
+ ```
540
+
541
+ ## License
542
+
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): SafeParseResult<O>;
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;
@@ -380,26 +375,26 @@ declare class StringSchema extends Schema<string> {
380
375
  private _toUpperCase;
381
376
  private _normalize;
382
377
  _parse(value: unknown, ctx: ParseContext): string;
383
- min(n: number, message?: string): StringSchema;
384
- max(n: number, message?: string): StringSchema;
385
- length(n: number, message?: string): StringSchema;
386
- regex(pattern: RegExp): StringSchema;
387
- startsWith(prefix: string): StringSchema;
388
- endsWith(suffix: string): StringSchema;
389
- includes(substring: string): StringSchema;
390
- uppercase(): StringSchema;
391
- lowercase(): StringSchema;
392
- trim(): StringSchema;
393
- toLowerCase(): StringSchema;
394
- toUpperCase(): StringSchema;
395
- normalize(): StringSchema;
378
+ min(n: number, message?: string): this;
379
+ max(n: number, message?: string): this;
380
+ length(n: number, message?: string): this;
381
+ regex(pattern: RegExp): this;
382
+ startsWith(prefix: string): this;
383
+ endsWith(suffix: string): this;
384
+ includes(substring: string): this;
385
+ uppercase(): this;
386
+ lowercase(): this;
387
+ trim(): this;
388
+ toLowerCase(): this;
389
+ toUpperCase(): this;
390
+ normalize(): this;
396
391
  _schemaType(): SchemaType;
397
392
  _toJSONSchema(_tracker: RefTracker): JSONSchemaObject;
398
- _clone(): StringSchema;
393
+ _clone(): this;
399
394
  }
400
395
  declare class CoercedStringSchema extends StringSchema {
401
396
  _parse(value: unknown, ctx: ParseContext): string;
402
- _clone(): CoercedStringSchema;
397
+ _clone(): this;
403
398
  }
404
399
  declare class CoercedNumberSchema extends NumberSchema {
405
400
  _parse(value: unknown, ctx: ParseContext): number;
@@ -465,6 +460,8 @@ declare class DiscriminatedUnionSchema<T extends DiscriminatedOptions> extends S
465
460
  declare class EnumSchema<T extends readonly [string, ...string[]]> extends Schema<T[number]> {
466
461
  private readonly _values;
467
462
  constructor(values: T);
463
+ /** Public accessor for the enum's allowed values. */
464
+ get values(): T;
468
465
  _parse(value: unknown, ctx: ParseContext): T[number];
469
466
  _schemaType(): SchemaType;
470
467
  _toJSONSchema(_tracker: RefTracker): JSONSchemaObject;
@@ -770,6 +767,11 @@ declare const s: {
770
767
  datetime: () => IsoDatetimeSchema;
771
768
  duration: () => IsoDurationSchema;
772
769
  };
770
+ fromDbEnum: <const TValues extends readonly [string, ...string[]]>(column: {
771
+ _meta: {
772
+ enumValues: TValues;
773
+ };
774
+ }) => EnumSchema<TValues>;
773
775
  coerce: {
774
776
  string: () => CoercedStringSchema;
775
777
  number: () => CoercedNumberSchema;
@@ -779,4 +781,4 @@ declare const s: {
779
781
  };
780
782
  };
781
783
  declare const schema: typeof s;
782
- 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, SafeParseResult, RefinementContext, RefinedSchema, RefTracker, RecordSchema, ReadonlySchema, ReadonlyOutput, PipeSchema, ParseError, ParseContext, Output, OptionalSchema, 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, EnumSchema, EmailSchema, DiscriminatedUnionSchema, DefaultSchema, DateSchema, CustomSchema, CuidSchema, CoercedStringSchema, CoercedNumberSchema, CoercedDateSchema, CoercedBooleanSchema, CoercedBigIntSchema, CatchSchema, BrandedSchema, BooleanSchema, BigIntSchema, Base64Schema, ArraySchema, AnySchema };
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
- throw new ParseError(ctx.issues);
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 { success: false, error: new ParseError(ctx.issues) };
141
+ return err(new ParseError(ctx.issues));
128
142
  }
129
- return { success: true, data };
143
+ return ok(data);
130
144
  } catch (e) {
131
145
  if (e instanceof ParseError) {
132
- return { success: false, error: e };
146
+ return err(e);
133
147
  }
134
148
  throw e;
135
149
  }
@@ -1095,7 +1109,8 @@ class StringSchema extends Schema {
1095
1109
  return schema;
1096
1110
  }
1097
1111
  _clone() {
1098
- const clone = this._cloneBase(new StringSchema);
1112
+ const Ctor = this.constructor;
1113
+ const clone = this._cloneBase(new Ctor);
1099
1114
  clone._min = this._min;
1100
1115
  clone._minMessage = this._minMessage;
1101
1116
  clone._max = this._max;
@@ -1122,7 +1137,8 @@ class CoercedStringSchema extends StringSchema {
1122
1137
  return super._parse(value == null ? "" : String(value), ctx);
1123
1138
  }
1124
1139
  _clone() {
1125
- return Object.assign(new CoercedStringSchema, super._clone());
1140
+ const Ctor = this.constructor;
1141
+ return Object.assign(new Ctor, super._clone());
1126
1142
  }
1127
1143
  }
1128
1144
 
@@ -1299,6 +1315,9 @@ class EnumSchema extends Schema {
1299
1315
  super();
1300
1316
  this._values = values;
1301
1317
  }
1318
+ get values() {
1319
+ return this._values;
1320
+ }
1302
1321
  _parse(value, ctx) {
1303
1322
  if (!this._values.includes(value)) {
1304
1323
  ctx.addIssue({
@@ -1435,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
1435
1454
  class Ipv4Schema extends FormatSchema {
1436
1455
  _errorMessage = "Invalid IPv4 address";
1437
1456
  _validate(value) {
1438
- const match = IPV4_RE.exec(value);
1439
- if (!match)
1457
+ const match2 = IPV4_RE.exec(value);
1458
+ if (!match2)
1440
1459
  return false;
1441
- return [match[1], match[2], match[3], match[4]].every((o) => Number(o) <= 255);
1460
+ return [match2[1], match2[2], match2[3], match2[4]].every((o) => Number(o) <= 255);
1442
1461
  }
1443
1462
  _jsonSchemaExtra() {
1444
1463
  return { format: "ipv4" };
@@ -1614,7 +1633,7 @@ class IntersectionSchema extends Schema {
1614
1633
  _parse(value, ctx) {
1615
1634
  const leftResult = this._left.safeParse(value);
1616
1635
  const rightResult = this._right.safeParse(value);
1617
- if (!leftResult.success || !rightResult.success) {
1636
+ if (!leftResult.ok || !rightResult.ok) {
1618
1637
  ctx.addIssue({
1619
1638
  code: "invalid_intersection" /* InvalidIntersection */,
1620
1639
  message: "Value does not satisfy intersection"
@@ -2249,7 +2268,7 @@ class UnionSchema extends Schema {
2249
2268
  _parse(value, ctx) {
2250
2269
  for (const option of this._options) {
2251
2270
  const result = option.safeParse(value);
2252
- if (result.success) {
2271
+ if (result.ok) {
2253
2272
  return result.data;
2254
2273
  }
2255
2274
  }
@@ -2355,6 +2374,13 @@ var s = {
2355
2374
  datetime: () => new IsoDatetimeSchema,
2356
2375
  duration: () => new IsoDurationSchema
2357
2376
  },
2377
+ fromDbEnum: (column) => {
2378
+ const values = column._meta.enumValues;
2379
+ if (!values || values.length === 0) {
2380
+ throw new Error("s.fromDbEnum(): not an enum column — _meta.enumValues is missing or empty");
2381
+ }
2382
+ return new EnumSchema(values);
2383
+ },
2358
2384
  coerce: {
2359
2385
  string: () => new CoercedStringSchema,
2360
2386
  number: () => new CoercedNumberSchema,
@@ -2365,10 +2391,19 @@ var s = {
2365
2391
  };
2366
2392
  var schema = s;
2367
2393
  export {
2394
+ unwrap,
2368
2395
  toJSONSchema,
2369
2396
  schema,
2370
2397
  s,
2371
2398
  preprocess,
2399
+ ok,
2400
+ matchErr,
2401
+ match,
2402
+ map,
2403
+ isOk,
2404
+ isErr,
2405
+ flatMap,
2406
+ err,
2372
2407
  VoidSchema,
2373
2408
  UuidSchema,
2374
2409
  UrlSchema,
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@vertz/schema",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
+ "description": "Type-safe schema definitions for Vertz",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "https://github.com/vertz-dev/vertz.git",
@@ -25,16 +26,22 @@
25
26
  ],
26
27
  "scripts": {
27
28
  "build": "bunup",
28
- "test": "vitest run",
29
+ "test": "bun test",
30
+ "test:coverage": "vitest run --coverage",
29
31
  "test:watch": "vitest",
30
32
  "typecheck": "tsc --noEmit"
31
33
  },
34
+ "dependencies": {
35
+ "@vertz/errors": "0.2.1"
36
+ },
32
37
  "devDependencies": {
38
+ "@vitest/coverage-v8": "^4.0.18",
33
39
  "bunup": "latest",
34
40
  "typescript": "^5.7.0",
35
- "vitest": "^3.0.0"
41
+ "vitest": "^4.0.18"
36
42
  },
37
43
  "engines": {
38
44
  "node": ">=22"
39
- }
45
+ },
46
+ "sideEffects": false
40
47
  }