@esmj/schema 0.4.0 → 0.5.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 CHANGED
@@ -1,6 +1,33 @@
1
1
  # Schema
2
2
 
3
- This small library provides a simple schema validation system for JavaScript/TypeScript. The library has basic types with opportunities for extending.
3
+ This small library provides a simple schema validation system for JavaScript/TypeScript. The library has basic types with opportunities for extending.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Why Use @esmj/schema?](#why-use-esmjschema)
10
+ - [Comparison with Similar Libraries](#comparison-with-similar-libraries)
11
+ - [Usage](#usage)
12
+ - [Basic Usage](#basic-usage)
13
+ - [Modular Extensions](#modular-extensions)
14
+ - [String Extensions](#string-extensions-esmjschemastring)
15
+ - [Number Extensions](#number-extensions-esmjschemanumber)
16
+ - [Array Extensions](#array-extensions-esmjschemaarray)
17
+ - [Full Extensions](#full-extensions-esmjschemafull)
18
+ - [API Reference Summary](#api-reference-summary)
19
+ - [Schema Types](#schema-types)
20
+ - [Schema Methods](#schema-methods)
21
+ - [parse](#parsevalue-parseoptions)
22
+ - [safeParse](#safeparsevalue-parseoptions)
23
+ - [Error Collection with abortEarly](#error-collection-with-abortearly-option)
24
+ - [Extending Schemas](#extending-schemas)
25
+ - [More Examples](#more-examples)
26
+ - [Examples Folder](#examples-folder)
27
+ - [Migration Guide](#migration-guide)
28
+ - [From Zod](#from-zod)
29
+ - [From Yup](#from-yup)
30
+ - [License](#license)
4
31
 
5
32
  ## Installation
6
33
 
@@ -8,17 +35,73 @@ This small library provides a simple schema validation system for JavaScript/Typ
8
35
  npm install @esmj/schema
9
36
  ```
10
37
 
38
+ ## Quick Start
39
+
40
+ Get started with `@esmj/schema` in seconds:
41
+
42
+ ```typescript
43
+ import { s } from '@esmj/schema';
44
+
45
+ // Define a schema
46
+ const userSchema = s.object({
47
+ name: s.string(),
48
+ age: s.number(),
49
+ email: s.string().optional()
50
+ });
51
+
52
+ // Parse data
53
+ const user = userSchema.parse({
54
+ name: 'John Doe',
55
+ age: 30
56
+ });
57
+
58
+ console.log(user);
59
+ // { name: 'John Doe', age: 30 }
60
+
61
+ // Safe parse with error handling
62
+ const result = userSchema.safeParse({
63
+ name: 'Jane',
64
+ age: 'invalid'
65
+ });
66
+
67
+ if (result.success) {
68
+ console.log(result.data);
69
+ } else {
70
+ console.error(result.error.message);
71
+ }
72
+ ```
73
+
74
+ **With Extensions:**
75
+
76
+ ```typescript
77
+ import { s } from '@esmj/schema/full';
78
+
79
+ const schema = s.object({
80
+ username: s.string().trim().toLowerCase().min(3).max(20),
81
+ age: s.number().int().positive().min(18),
82
+ tags: s.array(s.string()).min(1).unique()
83
+ });
84
+
85
+ const result = schema.parse({
86
+ username: ' JohnDoe ',
87
+ age: 25,
88
+ tags: ['developer', 'typescript']
89
+ });
90
+ // { username: 'johndoe', age: 25, tags: ['developer', 'typescript'] }
91
+ ```
92
+
11
93
  ## Why Use `@esmj/schema`?
12
94
 
13
95
  `@esmj/schema` is a lightweight and flexible schema validation library designed for developers who need a simple yet powerful way to validate and transform data. Here are some reasons to choose this package:
14
96
 
15
97
  1. **TypeScript First**: Built with TypeScript in mind, it provides strong type inference—even for deeply nested and complex schemas.
16
98
  2. **Extensibility**: Easily extend the library with custom logic, refinements, and preprocessors using the `extend` function.
17
- 3. **Rich Features**: Includes advanced features like preprocessing, transformations, piping, refinements, and robust error collection (`abortEarly`).
18
- 4. **Actionable Error Handling**: Collect all validation errors at once for better debugging and user experience, with clear and consistent error structures (`error` and `errors`).
99
+ 3. **Rich Features**: Includes advanced features like preprocessing, transformations, piping, refinements, and robust error collection (`abortEarly`), which are not always available in similar libraries.
100
+ 4. **Actionable Error Handling**: Collect all validation errors at once for better debugging and user experience, with clear and consistent error structures.
19
101
  5. **Lightweight**: No dependencies and a small footprint make it ideal for projects where performance and simplicity are key.
20
102
  6. **Customizable**: Offers fine-grained control over validation, error handling, and schema composition.
21
103
  7. **Performance**: Optimized for speed, making it one of the fastest schema validation libraries available.
104
+ 8. **Modular**: Import only what you need with separate string, number, and array extension modules to minimize bundle size.
22
105
 
23
106
  ### Performance Highlights
24
107
 
@@ -34,7 +117,7 @@ When choosing a schema validation library, bundle size can be an important facto
34
117
 
35
118
  | Library | Bundle Size (minified + gzipped) |
36
119
  |-------------------|---------------------------------|
37
- | `@esmj/schema` | `~1,2 KB` |
120
+ | `@esmj/schema` | `~1,4 KB` |
38
121
  | Superstruct | ~3.2 KB |
39
122
  | @sinclair/typebox | ~11.7 KB |
40
123
  | Yup | ~12.2 KB |
@@ -139,6 +222,287 @@ console.log(result);
139
222
  // }
140
223
  ```
141
224
 
225
+ ## Modular Extensions
226
+
227
+ `@esmj/schema` provides modular extensions that can be imported individually or all together, allowing you to include only the validation helpers you need.
228
+
229
+ ### Import Options
230
+
231
+ ```typescript
232
+ // Minimal version (core only, ~1.4 KB)
233
+ import { s } from '@esmj/schema';
234
+
235
+ // Full version (all extensions included, ~4 KB)
236
+ import { s } from '@esmj/schema/full';
237
+
238
+ // String extensions only
239
+ import { s } from '@esmj/schema/string';
240
+
241
+ // Number extensions only
242
+ import { s } from '@esmj/schema/number';
243
+
244
+ // Array extensions only
245
+ import { s } from '@esmj/schema/array';
246
+
247
+ // Mix and match (side-effect imports)
248
+ import '@esmj/schema/string';
249
+ import '@esmj/schema/number';
250
+ import { s } from '@esmj/schema';
251
+ ```
252
+
253
+ ### Bundle Size Impact
254
+
255
+ - **Core only** (`@esmj/schema`): ~1.4 KB gzipped
256
+ - **String extensions** (`@esmj/schema/string`): +~0.8 KB
257
+ - **Number extensions** (`@esmj/schema/number`): +~0.6 KB
258
+ - **Array extensions** (`@esmj/schema/array`): +~0.5 KB
259
+ - **Full** (`@esmj/schema/full`): ~4 KB gzipped (all extensions)
260
+
261
+ **Recommendation:** Import only the extensions you need to minimize bundle size.
262
+
263
+ ### String Extensions (`@esmj/schema/string`)
264
+
265
+ String extensions provide common validation and transformation methods for string schemas.
266
+
267
+ ```typescript
268
+ import { s } from '@esmj/schema/string';
269
+
270
+ const userSchema = s.object({
271
+ username: s.string()
272
+ .trim() // Remove whitespace
273
+ .toLowerCase() // Convert to lowercase
274
+ .min(3) // Minimum 3 characters
275
+ .max(20) // Maximum 20 characters
276
+ .startsWith('user_'), // Must start with 'user_'
277
+
278
+ email: s.string()
279
+ .trim()
280
+ .toLowerCase()
281
+ .includes('@') // Must contain '@'
282
+ });
283
+
284
+ userSchema.parse({
285
+ username: ' USER_John ',
286
+ email: ' John@Example.com '
287
+ });
288
+ // ✓ { username: 'user_john', email: 'john@example.com' }
289
+ ```
290
+
291
+ **Available String Methods:**
292
+
293
+ - **Length validations**: `min(length)`, `max(length)`, `length(exact)`, `nonEmpty()`
294
+ - **Pattern validations**: `startsWith(prefix)`, `endsWith(suffix)`, `includes(substring)`
295
+ - **Transformations**: `trim()`, `toLowerCase()`, `toUpperCase()`, `padStart(length, char)`, `padEnd(length, char)`, `replace(search, replace)`
296
+
297
+ ### Number Extensions (`@esmj/schema/number`)
298
+
299
+ Number extensions provide validation methods for number schemas including range checks and type validations.
300
+
301
+ ```typescript
302
+ import { s } from '@esmj/schema/number';
303
+
304
+ const productSchema = s.object({
305
+ price: s.number()
306
+ .positive() // Must be positive
307
+ .min(0.01) // Minimum value
308
+ .max(999999.99), // Maximum value
309
+
310
+ quantity: s.number()
311
+ .int() // Must be integer
312
+ .positive()
313
+ .min(1)
314
+ .max(1000),
315
+
316
+ discount: s.number()
317
+ .min(0)
318
+ .max(100)
319
+ .multipleOf(5) // Must be multiple of 5
320
+ });
321
+
322
+ productSchema.parse({
323
+ price: 29.99,
324
+ quantity: 5,
325
+ discount: 10
326
+ });
327
+ // ✓ { price: 29.99, quantity: 5, discount: 10 }
328
+ ```
329
+
330
+ **Available Number Methods:**
331
+
332
+ - **Range validations**: `min(value)`, `max(value)`, `positive()`, `negative()`
333
+ - **Type validations**: `int()`, `float()`, `multipleOf(value)`, `finite()`
334
+
335
+ ### Array Extensions (`@esmj/schema/array`)
336
+
337
+ Array extensions provide validation and transformation methods for array schemas.
338
+
339
+ ```typescript
340
+ import { s } from '@esmj/schema/array';
341
+
342
+ const tagsSchema = s.object({
343
+ tags: s.array(s.string())
344
+ .min(1) // At least 1 item
345
+ .max(5) // At most 5 items
346
+ .unique() // All items must be unique
347
+ });
348
+
349
+ tagsSchema.parse({
350
+ tags: ['javascript', 'typescript', 'node']
351
+ });
352
+ // ✓ { tags: ['javascript', 'typescript', 'node'] }
353
+ ```
354
+
355
+ **Available Array Methods:**
356
+
357
+ - **Size validations**: `min(length)`, `max(length)`, `length(exact)`, `nonEmpty()`
358
+ - **Content validations**: `unique()`
359
+ - **Transformations**: `sort()`, `reverse()`
360
+
361
+ ### Full Extensions (`@esmj/schema/full`)
362
+
363
+ The full version includes all string, number, and array extensions in a single import.
364
+
365
+ ```typescript
366
+ import { s } from '@esmj/schema/full';
367
+
368
+ const productSchema = s.object({
369
+ // String extensions
370
+ name: s.string()
371
+ .trim()
372
+ .min(3)
373
+ .max(100),
374
+
375
+ sku: s.string()
376
+ .toUpperCase()
377
+ .length(8)
378
+ .startsWith('PROD'),
379
+
380
+ // Number extensions
381
+ price: s.number()
382
+ .positive()
383
+ .min(0.01)
384
+ .max(999999.99),
385
+
386
+ stock: s.number()
387
+ .int()
388
+ .min(0),
389
+
390
+ // Array extensions
391
+ categories: s.array(s.string())
392
+ .min(1)
393
+ .max(5)
394
+ .unique(),
395
+
396
+ dimensions: s.array(s.number().positive())
397
+ .length(3) // [length, width, height]
398
+ });
399
+ ```
400
+
401
+ **Custom Error Messages:**
402
+
403
+ All extension methods support custom error messages:
404
+
405
+ ```typescript
406
+ const schema = s.object({
407
+ username: s.string().min(3, {
408
+ message: 'Username is too short! Please use at least 3 characters.'
409
+ }),
410
+ age: s.number().positive({
411
+ message: 'Age must be a positive number.'
412
+ }),
413
+ tags: s.array(s.string()).unique({
414
+ message: 'Duplicate tags are not allowed.'
415
+ })
416
+ });
417
+ ```
418
+
419
+ ## API Reference Summary
420
+
421
+ ### Core Types
422
+
423
+ - `s.string()` - String validation
424
+ - `s.number()` - Number validation
425
+ - `s.boolean()` - Boolean validation
426
+ - `s.date()` - Date validation
427
+ - `s.object(def)` - Object validation
428
+ - `s.array(def)` - Array validation
429
+ - `s.enum(values)` - Enum validation
430
+ - `s.union(schemas)` - Union validation
431
+ - `s.any()` - Any type
432
+ - `s.null()` - Null type
433
+ - `s.undefined()` - Undefined type
434
+ - `s.unknown()` - Unknown type
435
+
436
+ ### Modifiers
437
+
438
+ - `.optional()` - Makes field optional
439
+ - `.nullable()` - Makes field nullable
440
+ - `.nullish()` - Makes field optional and nullable
441
+ - `.default(value)` - Sets default value
442
+
443
+ ### Transformations
444
+
445
+ - `.transform(fn)` - Transform value
446
+ - `s.preprocess(fn, schema)` - Preprocess before validation
447
+ - `.pipe(schema)` - Pipe to another schema
448
+ - `.refine(fn, opts)` - Custom validation
449
+
450
+ ### String Extensions
451
+
452
+ Available when importing from `@esmj/schema/string` or `@esmj/schema/full`:
453
+
454
+ **Length Validations:**
455
+ - `.min(n)` - Minimum length
456
+ - `.max(n)` - Maximum length
457
+ - `.length(n)` - Exact length
458
+ - `.nonEmpty()` - Non-empty string
459
+
460
+ **Pattern Validations:**
461
+ - `.startsWith(prefix)` - Must start with prefix
462
+ - `.endsWith(suffix)` - Must end with suffix
463
+ - `.includes(substring)` - Must contain substring
464
+
465
+ **Transformations:**
466
+ - `.trim()` - Remove whitespace
467
+ - `.toLowerCase()` - Convert to lowercase
468
+ - `.toUpperCase()` - Convert to uppercase
469
+ - `.padStart(length, char)` - Pad start
470
+ - `.padEnd(length, char)` - Pad end
471
+ - `.replace(search, replace)` - Replace text
472
+
473
+ ### Number Extensions
474
+
475
+ Available when importing from `@esmj/schema/number` or `@esmj/schema/full`:
476
+
477
+ **Range Validations:**
478
+ - `.min(n)` - Minimum value
479
+ - `.max(n)` - Maximum value
480
+ - `.positive()` - Must be positive
481
+ - `.negative()` - Must be negative
482
+
483
+ **Type Validations:**
484
+ - `.int()` - Must be integer
485
+ - `.float()` - Must be float (non-integer)
486
+ - `.multipleOf(n)` - Must be multiple of n
487
+ - `.finite()` - Must be finite
488
+
489
+ ### Array Extensions
490
+
491
+ Available when importing from `@esmj/schema/array` or `@esmj/schema/full`:
492
+
493
+ **Size Validations:**
494
+ - `.min(n)` - Minimum length
495
+ - `.max(n)` - Maximum length
496
+ - `.length(n)` - Exact length
497
+ - `.nonEmpty()` - Non-empty array
498
+
499
+ **Content Validations:**
500
+ - `.unique()` - All items must be unique
501
+
502
+ **Transformations:**
503
+ - `.sort()` - Sort array
504
+ - `.reverse()` - Reverse array
505
+
142
506
  ### Schema Types
143
507
 
144
508
  #### `s.string(options?)`
@@ -467,26 +831,146 @@ This means you get all errors from deeply nested structures when using `{ abortE
467
831
 
468
832
  ### Extending Schemas
469
833
 
470
- You can extend the schema system with custom logic.
834
+ You can extend the schema system with custom validation methods. This is useful for adding domain-specific validations like email or URL formats.
835
+
836
+ #### Basic Extension Example
837
+
838
+ ```typescript
839
+ import { extend, type SchemaType, type StringSchemaInterface } from '@esmj/schema';
840
+
841
+ // First, declare the new methods you want to add
842
+ declare module '@esmj/schema' {
843
+ interface StringSchemaInterface {
844
+ email(): StringSchemaInterface;
845
+ url(): StringSchemaInterface;
846
+ trim(): StringSchemaInterface;
847
+ }
848
+ }
849
+
850
+ // Define validation patterns
851
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
852
+ const URL_REGEX = /^(https?:\/\/[^\s$.?#].[^\s]*)$/;
853
+
854
+ // Extend the schema system
855
+ extend((schema: SchemaType, _, options) => {
856
+ // Only add methods to string schemas
857
+ if (options?.type === 'string') {
858
+ const stringSchema = schema as StringSchemaInterface;
859
+
860
+ // Add email validation
861
+ stringSchema.email = function() {
862
+ return this.refine((value) => EMAIL_REGEX.test(value), {
863
+ message: 'Invalid email format'
864
+ });
865
+ };
866
+
867
+ // Add URL validation
868
+ stringSchema.url = function() {
869
+ return this.refine((value) => URL_REGEX.test(value), {
870
+ message: 'Invalid URL format'
871
+ });
872
+ };
873
+
874
+ // Add string trimming
875
+ stringSchema.trim = function() {
876
+ return this.transform((value) => value.trim());
877
+ };
878
+ }
879
+
880
+ return schema;
881
+ });
882
+ ```
883
+
884
+ #### Usage of Extended Schemas
885
+
886
+ Once extended, you can use your custom methods in schema definitions:
471
887
 
472
888
  ```typescript
473
- import { extend, type StringSchemaInterface } from '@esmj/schema';
889
+ const userSchema = s.object({
890
+ name: s.string().trim(),
891
+ email: s.string().email(),
892
+ website: s.string().url().optional()
893
+ });
894
+
895
+ // Valid data
896
+ userSchema.parse({
897
+ name: ' John Doe ', // Will be trimmed
898
+ email: 'john@example.com'
899
+ });
474
900
 
475
- interface StringSchemaInterface {
476
- customMethod(value: string): string {}
901
+ // Invalid data
902
+ try {
903
+ userSchema.parse({
904
+ name: 'John Doe',
905
+ email: 'not-an-email'
906
+ });
907
+ } catch (error) {
908
+ console.error(error); // "Invalid email format"
477
909
  }
910
+ ```
478
911
 
479
- extend((schema, validation, options) => {
480
- schema.customMethod = (value) => {
481
- // Custom logic
912
+ #### Advanced Extensions
482
913
 
483
- return value;
484
- };
914
+ You can extend any schema type and add complex validations:
485
915
 
916
+ ```typescript
917
+ declare module '@esmj/schema' {
918
+ interface NumberSchemaInterface {
919
+ positive(): NumberSchemaInterface;
920
+ range(min: number, max: number): NumberSchemaInterface;
921
+ }
922
+
923
+ interface ArraySchemaInterface<T> {
924
+ minLength(length: number): ArraySchemaInterface<T>;
925
+ unique(): ArraySchemaInterface<T>;
926
+ }
927
+ }
928
+
929
+ extend((schema: SchemaType, _, options) => {
930
+ if (options?.type === 'number') {
931
+ const numberSchema = schema as NumberSchemaInterface;
932
+
933
+ numberSchema.positive = function() {
934
+ return this.refine((value) => value > 0, {
935
+ message: 'Number must be positive'
936
+ });
937
+ };
938
+
939
+ numberSchema.range = function(min, max) {
940
+ return this.refine((value) => value >= min && value <= max, {
941
+ message: `Number must be between ${min} and ${max}`
942
+ });
943
+ };
944
+ }
945
+
946
+ if (options?.type === 'array') {
947
+ const arraySchema = schema as ArraySchemaInterface<unknown>;
948
+
949
+ arraySchema.minLength = function(length) {
950
+ return this.refine((value) => value.length >= length, {
951
+ message: `Array must contain at least ${length} items`
952
+ });
953
+ };
954
+
955
+ arraySchema.unique = function() {
956
+ return this.refine((value) => {
957
+ const seen = new Set();
958
+ return value.every(item => {
959
+ const serialized = JSON.stringify(item);
960
+ if (seen.has(serialized)) return false;
961
+ seen.add(serialized);
962
+ return true;
963
+ });
964
+ }, { message: 'Array items must be unique' });
965
+ };
966
+ }
967
+
486
968
  return schema;
487
969
  });
488
970
  ```
489
971
 
972
+ This extension system gives you the flexibility to create domain-specific validation rules while maintaining type safety and the fluent API style.
973
+
490
974
  ### More Examples
491
975
 
492
976
  #### Nested Objects
@@ -653,6 +1137,166 @@ console.log(result);
653
1137
  // 'HELLO'
654
1138
  ```
655
1139
 
1140
+ ## Examples Folder
1141
+
1142
+ The `examples/` folder contains comprehensive, runnable examples demonstrating various use cases:
1143
+
1144
+ ### Basic Usage (`examples/basic-usage.ts`)
1145
+
1146
+ Demonstrates the core validation features with strings, numbers, arrays, and unions:
1147
+
1148
+ ```bash
1149
+ node --experimental-strip-types examples/basic-usage.ts
1150
+ ```
1151
+
1152
+ ### Custom Validation (`examples/custom-validation.ts`)
1153
+
1154
+ Shows how to create custom validators for common use cases:
1155
+ - Email validation with regex
1156
+ - URL validation
1157
+ - Age range validation
1158
+ - Password strength validation
1159
+ - Cross-field validation (e.g., password confirmation)
1160
+
1161
+ ```bash
1162
+ node --experimental-strip-types examples/custom-validation.ts
1163
+ ```
1164
+
1165
+ ### Advanced Forms (`examples/advanced-forms.ts`)
1166
+
1167
+ Real-world form validation examples:
1168
+ - User profile schema with nested objects
1169
+ - Address validation with postal codes
1170
+ - Phone number formatting and validation
1171
+ - API response validation
1172
+ - Complex nested structures
1173
+
1174
+ ```bash
1175
+ node --experimental-strip-types examples/advanced-forms.ts
1176
+ ```
1177
+
1178
+ ### Custom Extensions (`examples/custom-extensions.ts`)
1179
+
1180
+ Demonstrates how to extend the library with custom methods:
1181
+ - Email validation extension
1182
+ - URL validation extension
1183
+ - UUID validation extension
1184
+ - Combining custom extensions with built-in validators
1185
+
1186
+ ```bash
1187
+ node --experimental-strip-types examples/custom-extensions.ts
1188
+ ```
1189
+
1190
+ **To run all examples:**
1191
+
1192
+ ```bash
1193
+ # Using Node.js with experimental type stripping (built-in, no dependencies)
1194
+ node --experimental-strip-types examples/basic-usage.ts
1195
+ node --experimental-strip-types examples/custom-validation.ts
1196
+ node --experimental-strip-types examples/advanced-forms.ts
1197
+ node --experimental-strip-types examples/custom-extensions.ts
1198
+
1199
+ # OR using tsx (requires installation)
1200
+ npm install -g tsx # If not already installed
1201
+ npx tsx examples/basic-usage.ts
1202
+ npx tsx examples/custom-validation.ts
1203
+ npx tsx examples/advanced-forms.ts
1204
+ npx tsx examples/custom-extensions.ts
1205
+ ```
1206
+ ## Migration Guide
1207
+
1208
+ ### From Zod
1209
+
1210
+ `@esmj/schema` has a similar API to Zod, making migration straightforward:
1211
+
1212
+ ```typescript
1213
+ // Zod
1214
+ import { z } from 'zod';
1215
+
1216
+ const userSchema = z.object({
1217
+ name: z.string().min(3).max(50),
1218
+ email: z.string().email(),
1219
+ age: z.number().positive().int(),
1220
+ role: z.enum(['admin', 'user']),
1221
+ tags: z.array(z.string()).optional()
1222
+ });
1223
+
1224
+ // @esmj/schema (with extensions)
1225
+ import { s } from '@esmj/schema/full';
1226
+
1227
+ const userSchema = s.object({
1228
+ name: s.string().min(3).max(50),
1229
+ email: s.string(), // Note: email() validation requires custom extension
1230
+ age: s.number().positive().int(),
1231
+ role: s.enum(['admin', 'user']),
1232
+ tags: s.array(s.string()).optional()
1233
+ });
1234
+ ```
1235
+
1236
+ **Key Differences:**
1237
+
1238
+ | Feature | Zod | @esmj/schema |
1239
+ |---------|-----|--------------|
1240
+ | Import | `import { z } from 'zod'` | `import { s } from '@esmj/schema'` |
1241
+ | Extensions | Built-in | Modular (`/string`, `/number`, `/array`, `/full`) |
1242
+ | Bundle size | ~13 KB | ~1.4 KB (core), ~4 KB (full) |
1243
+ | Email validation | `.email()` built-in | Custom extension (see [Extending Schemas](#extending-schemas)) |
1244
+ | Error format | Native Error | Plain object `{ success, error, errors }` |
1245
+
1246
+ **Migration Tips:**
1247
+
1248
+ 1. Replace `z` with `s` in your imports
1249
+ 2. For string methods like `.min()`, `.trim()`, import from `@esmj/schema/full` or `@esmj/schema/string`
1250
+ 3. Add custom extensions for email, URL validation (see examples below)
1251
+ 4. Update error handling to use the plain object structure
1252
+
1253
+ ### From Yup
1254
+
1255
+ Migrating from Yup requires a few adjustments in syntax:
1256
+
1257
+ ```typescript
1258
+ // Yup
1259
+ import * as yup from 'yup';
1260
+
1261
+ const userSchema = yup.object({
1262
+ name: yup.string().required().min(3).max(50),
1263
+ email: yup.string().required().email(),
1264
+ age: yup.number().required().positive().integer(),
1265
+ website: yup.string().url().nullable(),
1266
+ tags: yup.array().of(yup.string()).min(1)
1267
+ });
1268
+
1269
+ // @esmj/schema (with extensions)
1270
+ import { s } from '@esmj/schema/full';
1271
+
1272
+ const userSchema = s.object({
1273
+ name: s.string().min(3).max(50), // Fields are required by default
1274
+ email: s.string(), // Note: email() validation requires custom extension
1275
+ age: s.number().positive().int(),
1276
+ website: s.string().nullable(),
1277
+ tags: s.array(s.string()).min(1)
1278
+ });
1279
+ ```
1280
+
1281
+ **Key Differences:**
1282
+
1283
+ | Feature | Yup | @esmj/schema |
1284
+ |---------|-----|--------------|
1285
+ | Required fields | `.required()` explicit | Required by default |
1286
+ | Optional fields | Default behavior | `.optional()` explicit |
1287
+ | Array of type | `.array().of(type)` | `.array(type)` |
1288
+ | Integer | `.integer()` | `.int()` |
1289
+ | Email validation | `.email()` built-in | Custom extension needed |
1290
+ | Async validation | Supported | Not currently supported |
1291
+
1292
+ **Migration Tips:**
1293
+
1294
+ 1. Remove `.required()` calls (fields are required by default)
1295
+ 2. Add `.optional()` for optional fields
1296
+ 3. Change `.array().of(type)` to `.array(type)`
1297
+ 4. Change `.integer()` to `.int()`
1298
+ 5. Add custom extensions for email, URL validation
1299
+
656
1300
  ## License
657
1301
 
658
1302
  MIT