unischema 1.0.0 → 1.1.0

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.
Files changed (70) hide show
  1. package/README.md +634 -230
  2. package/dist/adapters/backend/index.d.mts +2 -1
  3. package/dist/adapters/backend/index.d.ts +2 -1
  4. package/dist/adapters/backend/index.js +17 -423
  5. package/dist/adapters/backend/index.mjs +9 -415
  6. package/dist/adapters/frontend/index.d.mts +2 -1
  7. package/dist/adapters/frontend/index.d.ts +2 -1
  8. package/dist/adapters/frontend/index.js +10 -403
  9. package/dist/adapters/frontend/index.mjs +8 -401
  10. package/dist/chunk-2JYFKT3R.js +103 -0
  11. package/dist/chunk-3FANCMEF.js +206 -0
  12. package/dist/chunk-3TS35CVJ.mjs +478 -0
  13. package/dist/chunk-ASKTY6EG.js +131 -0
  14. package/dist/chunk-BJLVOIAP.js +491 -0
  15. package/dist/chunk-BNIB23NQ.js +90 -0
  16. package/dist/chunk-BVRXGZLS.js +17 -0
  17. package/dist/chunk-CQYXR2LZ.js +353 -0
  18. package/dist/chunk-ELL7U7IC.mjs +237 -0
  19. package/dist/chunk-FKDWSZIV.mjs +39 -0
  20. package/dist/chunk-FRBZHN4K.mjs +335 -0
  21. package/dist/chunk-FZ7K2PC7.js +248 -0
  22. package/dist/chunk-KHHJD6QK.mjs +85 -0
  23. package/dist/chunk-NUW55QTO.js +48 -0
  24. package/dist/chunk-TTK77YBI.mjs +15 -0
  25. package/dist/chunk-VWP24NYS.mjs +194 -0
  26. package/dist/chunk-XC4DKEXP.mjs +97 -0
  27. package/dist/chunk-XGTUU27F.mjs +124 -0
  28. package/dist/index-BQR7OrY7.d.mts +80 -0
  29. package/dist/index-BQR7OrY7.d.ts +80 -0
  30. package/dist/index.d.mts +3 -2
  31. package/dist/index.d.ts +3 -2
  32. package/dist/index.js +537 -476
  33. package/dist/index.mjs +486 -464
  34. package/dist/{schema-BwQtngae.d.mts → schema-CpAjXgEF.d.ts} +186 -79
  35. package/dist/{schema-BwQtngae.d.ts → schema-DYU1zGVm.d.mts} +186 -79
  36. package/dist/validators/array.d.mts +15 -0
  37. package/dist/validators/array.d.ts +15 -0
  38. package/dist/validators/array.js +31 -0
  39. package/dist/validators/array.mjs +2 -0
  40. package/dist/validators/common.d.mts +13 -0
  41. package/dist/validators/common.d.ts +13 -0
  42. package/dist/validators/common.js +27 -0
  43. package/dist/validators/common.mjs +2 -0
  44. package/dist/validators/date.d.mts +23 -0
  45. package/dist/validators/date.d.ts +23 -0
  46. package/dist/validators/date.js +47 -0
  47. package/dist/validators/date.mjs +2 -0
  48. package/dist/validators/index.d.mts +46 -0
  49. package/dist/validators/index.d.ts +46 -0
  50. package/dist/validators/index.js +256 -0
  51. package/dist/validators/index.mjs +7 -0
  52. package/dist/validators/number.d.mts +25 -0
  53. package/dist/validators/number.d.ts +25 -0
  54. package/dist/validators/number.js +51 -0
  55. package/dist/validators/number.mjs +2 -0
  56. package/dist/validators/object.d.mts +11 -0
  57. package/dist/validators/object.d.ts +11 -0
  58. package/dist/validators/object.js +23 -0
  59. package/dist/validators/object.mjs +2 -0
  60. package/dist/validators/string.d.mts +37 -0
  61. package/dist/validators/string.d.ts +37 -0
  62. package/dist/validators/string.js +75 -0
  63. package/dist/validators/string.mjs +2 -0
  64. package/package.json +36 -1
  65. package/dist/adapters/backend/index.js.map +0 -1
  66. package/dist/adapters/backend/index.mjs.map +0 -1
  67. package/dist/adapters/frontend/index.js.map +0 -1
  68. package/dist/adapters/frontend/index.mjs.map +0 -1
  69. package/dist/index.js.map +0 -1
  70. package/dist/index.mjs.map +0 -1
package/README.md CHANGED
@@ -1,367 +1,771 @@
1
- # FormSchema
1
+ # Unischema
2
2
 
3
- **Schema-Driven, Isomorphic Form & Validation Engine**
3
+ **The Universal Schema-Driven Validation Library**
4
4
 
5
- FormSchema is a TypeScript-first validation library that provides a single source of truth for form validation across your entire stack. Define your schema once, use it everywhere — frontend, backend, and type system.
5
+ [![npm version](https://badge.fury.io/js/unischema.svg)](https://www.npmjs.com/package/unischema)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
7
 
7
- ## The Problem
8
+ Unischema is a TypeScript-first validation library that provides **one schema, everywhere**. Define your validation once, use it on frontend, backend, and get automatic TypeScript types.
8
9
 
9
- In traditional applications:
10
- - Frontend has its own validation (vee-validate, Yup, Zod)
11
- - Backend has its own validation (Joi, AJV, class-validator)
12
- - They often drift apart, causing bugs
13
- - No type safety between the two
14
- - Duplicate code, duplicate bugs
10
+ ## 🚀 Why Unischema?
15
11
 
16
- ## The Solution
12
+ **The Problem:**
13
+ - Frontend has validation (Yup, Zod, vee-validate)
14
+ - Backend has validation (Joi, AJV, class-validator)
15
+ - They drift apart → bugs
16
+ - No type safety between them
17
+ - Code duplication everywhere
17
18
 
18
- FormSchema provides:
19
- - **Single executable schema** that runs unchanged in browser and Node.js
20
- - **Automatic TypeScript types** inferred from your schema
21
- - **Hard + Soft validation** (errors vs warnings) for enterprise patterns
22
- - **Framework-agnostic core** with adapters for Express, serverless, and any frontend
23
- - **Zero duplicated logic** — one schema, everywhere
19
+ **The Solution:**
20
+ ```typescript
21
+ // Define once
22
+ const UserSchema = schema({
23
+ email: field.string().email().required(),
24
+ age: field.number().min(18)
25
+ });
26
+
27
+ // ✅ Use on backend (Express)
28
+ app.post('/users', validateBody(UserSchema), handler);
24
29
 
25
- ## Installation
30
+ // ✅ Use on frontend (any framework)
31
+ const form = createForm(UserSchema, { onSubmit });
32
+
33
+ // ✅ Get TypeScript types automatically
34
+ type User = InferInput<typeof UserSchema>;
35
+ ```
36
+
37
+ ## 📦 Installation
26
38
 
27
39
  ```bash
28
- npm install formschema
40
+ npm install unischema
29
41
  ```
30
42
 
31
- ## Quick Start
43
+ ## Quick Start
32
44
 
33
- ### 1. Define Your Schema (Once)
45
+ ### 1️⃣ Define Your Schema
34
46
 
35
47
  ```typescript
36
- // schemas/user.ts
37
- import { schema, field, type InferInput } from 'formschema';
48
+ import { schema, field } from 'unischema';
38
49
 
39
50
  export const UserSchema = schema({
40
51
  email: field.string()
41
- .email('Invalid email address')
52
+ .email('Invalid email')
42
53
  .required('Email is required'),
43
54
 
44
55
  password: field.string()
45
- .min(8, 'Password must be at least 8 characters')
46
- .required('Password is required'),
56
+ .min(8, 'At least 8 characters')
57
+ .required(),
47
58
 
48
59
  age: field.number()
49
- .min(13, 'Must be at least 13') // Hard validation
50
- .minSoft(18, 'Parental consent required'), // Soft warning
60
+ .min(18, 'Must be 18+')
61
+ .max(120),
51
62
  });
52
-
53
- // TypeScript type is automatically inferred
54
- export type UserInput = InferInput<typeof UserSchema>;
55
- // { email: string; password: string; age: number }
56
63
  ```
57
64
 
58
- ### 2. Use on Backend (Express)
65
+ ### 2️⃣ Use on Backend
59
66
 
60
67
  ```typescript
61
- // server.ts
62
68
  import express from 'express';
63
- import { validateBody, type ValidatedRequest } from 'formschema/backend';
64
- import { UserSchema, type UserInput } from './schemas/user';
69
+ import { validateBody } from 'unischema/backend';
70
+ import { UserSchema } from './schemas';
65
71
 
66
72
  const app = express();
67
- app.use(express.json());
68
-
69
- app.post('/api/users',
70
- validateBody(UserSchema),
71
- (req: ValidatedRequest<UserInput>, res) => {
72
- // req.validatedData is typed and validated!
73
- const { email, password, age } = req.validatedData;
74
-
75
- // Check for warnings
76
- if (req.validationResult.softErrors.length > 0) {
77
- console.log('Warnings:', req.validationResult.softErrors);
78
- }
79
73
 
80
- res.json({ success: true, email });
81
- }
82
- );
74
+ app.post('/register', validateBody(UserSchema), (req, res) => {
75
+ const { email, password, age } = req.validatedData; // ✅ Typed & validated
76
+ res.json({ success: true });
77
+ });
83
78
  ```
84
79
 
85
- ### 3. Use on Frontend
80
+ ### 3️⃣ Use on Frontend
86
81
 
87
82
  ```typescript
88
- // form.ts
89
- import { createForm, focusFirstError } from 'formschema/frontend';
90
- import { UserSchema } from './schemas/user';
83
+ import { createForm } from 'unischema/frontend';
84
+ import { UserSchema } from './schemas';
91
85
 
92
86
  const form = createForm(UserSchema, {
93
87
  onSubmit: async (values) => {
94
- const response = await fetch('/api/users', {
88
+ await fetch('/register', {
95
89
  method: 'POST',
96
- body: JSON.stringify(values),
90
+ body: JSON.stringify(values)
97
91
  });
98
- // Handle response...
99
- },
92
+ }
100
93
  });
101
94
 
102
- // Get field props for your UI
95
+ // Get field props for your UI framework
103
96
  const emailProps = form.getFieldProps('email');
104
- // { name, value, onChange, onBlur, error, hasError, ... }
105
97
  ```
106
98
 
107
- ## Core Concepts
99
+ ## 🎯 Features
100
+
101
+ - ✅ **50+ Built-in Validators** - Email, URL, IPv4/IPv6, phone, coordinates, and more
102
+ - ✅ **Isomorphic** - Same code runs in browser and Node.js
103
+ - ✅ **TypeScript First** - Automatic type inference
104
+ - ✅ **Hard & Soft Validation** - Errors vs warnings for enterprise apps
105
+ - ✅ **Tree-Shakeable** - Only bundle what you use (~2KB min+gzip)
106
+ - ✅ **Framework Agnostic** - Works with React, Vue, Svelte, Angular, etc.
107
+ - ✅ **Zero Dependencies** - Lightweight and fast
108
108
 
109
- ### Hard vs Soft Validation
109
+ ## 📚 All Validators (v1.1.0)
110
110
 
111
- FormSchema supports two-tier validation, essential for enterprise applications:
111
+ ### String Validators (17)
112
112
 
113
113
  ```typescript
114
- const TransactionSchema = schema({
115
- amount: field.number()
116
- .min(0.01, 'Amount must be positive') // Hard: blocks submission
117
- .maxSoft(10000, 'Large transaction - review'), // Soft: warning only
118
- });
114
+ field.string()
115
+ // Basic
116
+ .required() // Required field
117
+ .min(5) // Min length
118
+ .max(100) // Max length
119
+ .length(10) // Exact length
120
+
121
+ // Format validation
122
+ .email() // Valid email
123
+ .url() // Valid URL
124
+ .ipAddress() // IPv4 (validates 0-255)
125
+ .ipv6() // IPv6 address
126
+
127
+ // Character validation
128
+ .alpha() // Only letters (a-zA-Z)
129
+ .alphanumeric() // Letters + numbers
130
+ .numeric() // Only digits
131
+ .lowercase() // Must be lowercase
132
+ .uppercase() // Must be UPPERCASE
133
+
134
+ // Pattern validation
135
+ .slug() // URL-friendly slug
136
+ .hex() // Hexadecimal
137
+ .base64() // Base64 encoded
138
+ .json() // Valid JSON string
139
+ .pattern(/regex/) // Custom regex
140
+
141
+ // Content validation
142
+ .contains('substring') // Must contain text
143
+ .startsWith('prefix') // Must start with
144
+ .endsWith('suffix') // Must end with
145
+ ```
146
+
147
+ **Examples:**
148
+ ```typescript
149
+ // Email with custom message
150
+ email: field.string().email('Please enter a valid email')
151
+
152
+ // Alphanumeric username
153
+ username: field.string()
154
+ .alphanumeric('Only letters and numbers')
155
+ .min(3)
156
+ .max(20)
157
+
158
+ // URL slug
159
+ slug: field.string()
160
+ .slug('Must be URL-friendly')
161
+ .lowercase()
162
+
163
+ // Hex color
164
+ color: field.string()
165
+ .hex('Invalid color code')
166
+ .length(6)
167
+ ```
119
168
 
120
- const result = validate(TransactionSchema.definition, { amount: 50000 });
169
+ ### Number Validators (11)
121
170
 
122
- result.valid; // true - no hard errors
123
- result.hardErrors; // []
124
- result.softErrors; // [{ field: 'amount', message: 'Large transaction...', severity: 'soft' }]
171
+ ```typescript
172
+ field.number()
173
+ // Range validation
174
+ .min(0) // Minimum value
175
+ .max(100) // Maximum value
176
+ .between(10, 20) // Between range
177
+
178
+ // Type validation
179
+ .integer() // Must be integer
180
+ .positive() // Must be > 0
181
+ .negative() // Must be < 0
182
+ .even() // Even number
183
+ .odd() // Odd number
184
+ .safe() // Safe integer
185
+ .finite() // Not Infinity/NaN
186
+
187
+ // Special formats
188
+ .port() // Port (0-65535)
189
+ .latitude() // Latitude (-90 to 90)
190
+ .longitude() // Longitude (-180 to 180)
191
+ .percentage() // Percentage (0-100)
192
+
193
+ // Mathematical
194
+ .divisibleBy(5) // Divisible by N
195
+ .multipleOf(3) // Multiple of N
125
196
  ```
126
197
 
127
- **Hard validations** block form submission. **Soft validations** are warnings that don't block.
198
+ **Examples:**
199
+ ```typescript
200
+ // Port number
201
+ port: field.number()
202
+ .port('Invalid port number')
203
+
204
+ // GPS coordinates
205
+ location: schema({
206
+ latitude: field.number().latitude(),
207
+ longitude: field.number().longitude()
208
+ })
209
+
210
+ // Age with soft warning
211
+ age: field.number()
212
+ .min(13, 'Must be 13+') // Hard error
213
+ .minSoft(18, 'Parental consent') // Soft warning
214
+
215
+ // Even page count
216
+ pages: field.number()
217
+ .integer()
218
+ .even('Must be even number')
219
+ ```
128
220
 
129
- ### Schema Composition
221
+ ### Date Validators (10)
130
222
 
131
- Build complex schemas from reusable parts:
223
+ ```typescript
224
+ field.date()
225
+ // Basic
226
+ .after(date) // After date
227
+ .before(date) // Before date
228
+ .past() // Must be in past
229
+ .future() // Must be in future
230
+
231
+ // Relative validation
232
+ .today() // Must be today
233
+ .yesterday() // Must be yesterday
234
+ .tomorrow() // Must be tomorrow
235
+ .thisWeek() // This week
236
+ .thisMonth() // This month
237
+ .thisYear() // This year
238
+
239
+ // Day validation
240
+ .weekday() // Monday-Friday
241
+ .weekend() // Saturday-Sunday
242
+
243
+ // Age validation
244
+ .age(min, max) // Age range from birthdate
245
+ .between(start, end) // Between two dates
246
+ ```
247
+
248
+ **Examples:**
249
+ ```typescript
250
+ // Birth date (18-65 years old)
251
+ birthDate: field.date()
252
+ .age(18, 65, 'Must be 18-65 years old')
253
+ .past('Cannot be in future')
254
+
255
+ // Event must be in future
256
+ eventDate: field.date()
257
+ .future('Event must be scheduled ahead')
258
+ .weekday('Events only on weekdays')
259
+
260
+ // Today's attendance
261
+ checkIn: field.date()
262
+ .today('Must check in today')
263
+ ```
264
+
265
+ ### Array Validators (6)
132
266
 
133
267
  ```typescript
268
+ field.array()
269
+ // Size validation
270
+ .min(2) // Min items
271
+ .max(10) // Max items
272
+ .unique() // All items unique
273
+
274
+ // Content validation
275
+ .includes(item) // Must include item
276
+ .excludes(item) // Must not include item
277
+ .notEmpty() // At least 1 item
278
+ .empty() // Must be empty
279
+
280
+ // Order validation
281
+ .sorted('asc') // Sorted ascending
282
+ .sorted('desc') // Sorted descending
283
+
284
+ // Quality validation
285
+ .compact() // No falsy values
286
+ ```
287
+
288
+ **Examples:**
289
+ ```typescript
290
+ // Tags (1-5 unique items)
291
+ tags: field.array(field.string())
292
+ .min(1, 'At least one tag')
293
+ .max(5, 'Max 5 tags')
294
+ .unique('Tags must be unique')
295
+
296
+ // Must include required item
297
+ permissions: field.array()
298
+ .includes('read', 'Read permission required')
299
+
300
+ // Sorted numbers
301
+ scores: field.array(field.number())
302
+ .sorted('desc', 'Must be sorted highest first')
303
+ ```
304
+
305
+ ### Boolean Validators
306
+
307
+ ```typescript
308
+ field.boolean()
309
+ .isTrue() // Must be true
310
+ .isFalse() // Must be false
311
+ ```
312
+
313
+ **Examples:**
314
+ ```typescript
315
+ // Terms acceptance
316
+ acceptTerms: field.boolean()
317
+ .isTrue('You must accept terms')
318
+ .required()
319
+
320
+ // Optional newsletter
321
+ newsletter: field.boolean()
322
+ .optional()
323
+ ```
324
+
325
+ ### Object Validators (Nested)
326
+
327
+ ```typescript
328
+ field.object(schema) // Nested schema validation
329
+ ```
330
+
331
+ **Examples:**
332
+ ```typescript
333
+ // Nested address
134
334
  const AddressSchema = schema({
135
335
  street: field.string().required(),
136
336
  city: field.string().required(),
137
- zipCode: field.string().pattern(/^\d{5}$/),
337
+ zipCode: field.string().pattern(/^\d{5}$/)
138
338
  });
139
339
 
140
340
  const UserSchema = schema({
141
341
  name: field.string().required(),
142
- address: field.object(AddressSchema).required(),
342
+ address: field.object(AddressSchema).required()
143
343
  });
344
+ ```
144
345
 
145
- // Extend schemas
146
- const AdminSchema = extend(UserSchema, {
147
- role: field.string().enum(['admin', 'superadmin']),
346
+ ### Cross-Field Validators (5)
347
+
348
+ ```typescript
349
+ field.string()
350
+ .matches('password') // Must match field
351
+ .notMatches('oldPassword') // Must NOT match field
352
+
353
+ field.number()
354
+ .greaterThan('minValue') // > another field
355
+ .lessThan('maxValue') // < another field
356
+
357
+ field.string()
358
+ .dependsOn('country') // Required if field exists
359
+ ```
360
+
361
+ **Examples:**
362
+ ```typescript
363
+ // Password confirmation
364
+ const schema = schema({
365
+ password: field.string().min(8),
366
+ confirmPassword: field.string()
367
+ .matches('password', 'Passwords must match')
148
368
  });
149
369
 
150
- // Pick/Omit fields
151
- const PublicUserSchema = omit(UserSchema, ['password']);
152
- const LoginSchema = pick(UserSchema, ['email', 'password']);
370
+ // New password must differ
371
+ const changePasswordSchema = schema({
372
+ currentPassword: field.string(),
373
+ newPassword: field.string()
374
+ .notMatches('currentPassword', 'Must be different')
375
+ });
376
+
377
+ // Range validation
378
+ const rangeSchema = schema({
379
+ minPrice: field.number(),
380
+ maxPrice: field.number()
381
+ .greaterThan('minPrice', 'Max must be > min')
382
+ });
383
+
384
+ // Conditional requirement
385
+ const locationSchema = schema({
386
+ country: field.string(),
387
+ state: field.string()
388
+ .dependsOn('country', 'State requires country')
389
+ });
153
390
  ```
154
391
 
155
- ### Type Inference
392
+ ## 💡 Hard vs Soft Validation
156
393
 
157
- Types are automatically inferred from your schema:
394
+ Unischema supports two-tier validation for enterprise applications:
158
395
 
159
396
  ```typescript
160
- const UserSchema = schema({
161
- email: field.string().email().required(),
162
- age: field.number().min(0),
163
- tags: field.array(field.string()),
397
+ const TransactionSchema = schema({
398
+ amount: field.number()
399
+ .min(0.01, 'Amount must be positive') // ❌ Hard: blocks submission
400
+ .maxSoft(10000, 'Review required for $10k+') // ⚠️ Soft: warning only
164
401
  });
165
402
 
166
- type User = InferInput<typeof UserSchema>;
167
- // {
168
- // email: string;
169
- // age: number;
170
- // tags: string[];
171
- // }
403
+ const result = validateSchema(TransactionSchema.definition, { amount: 15000 });
404
+
405
+ console.log(result.valid); // true (no hard errors)
406
+ console.log(result.hardErrors); // []
407
+ console.log(result.softErrors); // [{ field: 'amount', message: 'Review required...', severity: 'soft' }]
172
408
  ```
173
409
 
174
- ## API Reference
410
+ **Use cases:**
411
+ - Warnings that don't block submission
412
+ - Age warnings (13+ required, 18+ recommended)
413
+ - Security score suggestions
414
+ - Large transaction reviews
175
415
 
176
- ### Schema Builders
416
+ ## 🔧 Advanced Usage
177
417
 
178
- #### `field.string()`
418
+ ### Schema Composition
179
419
 
180
420
  ```typescript
181
- field.string()
182
- .min(length, message?) // Minimum length
183
- .max(length, message?) // Maximum length
184
- .email(message?) // Email format
185
- .url(message?) // URL format
186
- .pattern(regex, message?) // Regex pattern
187
- .enum(values, message?) // Enum values
188
- .matches(field, message?) // Match another field
189
- .required(message?) // Mark as required
190
- .optional() // Mark as optional
191
- // Soft versions (warnings only)
192
- .minSoft(length, message?)
193
- .maxSoft(length, message?)
421
+ // Extend schemas
422
+ const BaseUser = schema({
423
+ email: field.string().email(),
424
+ name: field.string()
425
+ });
426
+
427
+ const AdminUser = extend(BaseUser, {
428
+ role: field.string().enum(['admin', 'superadmin']),
429
+ permissions: field.array(field.string())
430
+ });
431
+
432
+ // Pick specific fields
433
+ const LoginSchema = pick(BaseUser, ['email']);
434
+
435
+ // Omit fields
436
+ const PublicUser = omit(BaseUser, ['password']);
437
+
438
+ // Merge schemas
439
+ const FullSchema = merge(ProfileSchema, SettingsSchema);
194
440
  ```
195
441
 
196
- #### `field.number()`
442
+ ### TypeScript Integration
197
443
 
198
444
  ```typescript
199
- field.number()
200
- .min(value, message?) // Minimum value
201
- .max(value, message?) // Maximum value
202
- .integer(message?) // Must be integer
203
- .positive(message?) // Must be positive
204
- .negative(message?) // Must be negative
205
- .required(message?)
206
- // Soft versions
207
- .minSoft(value, message?) // or .warnBelow()
208
- .maxSoft(value, message?) // or .warnAbove()
445
+ import { type InferInput, type InferOutput } from 'unischema';
446
+
447
+ const UserSchema = schema({
448
+ email: field.string().email().required(),
449
+ age: field.number().min(0)
450
+ });
451
+
452
+ // Input type (what you pass in)
453
+ type UserInput = InferInput<typeof UserSchema>;
454
+ // { email: string; age: number }
455
+
456
+ // Output type (after validation)
457
+ type UserOutput = InferOutput<typeof UserSchema>;
209
458
  ```
210
459
 
211
- #### `field.boolean()`
460
+ ### Custom Validation
212
461
 
213
462
  ```typescript
214
- field.boolean()
215
- .isTrue(message?) // Must be true
216
- .isFalse(message?) // Must be false
217
- .required(message?)
463
+ const schema = schema({
464
+ password: field.string()
465
+ .custom((value, context) => {
466
+ if (!/[A-Z]/.test(value)) {
467
+ return { valid: false, message: 'Need uppercase letter' };
468
+ }
469
+ return true;
470
+ })
471
+ });
218
472
  ```
219
473
 
220
- #### `field.date()`
474
+ ### Granular Imports (Tree-Shaking)
221
475
 
222
476
  ```typescript
223
- field.date()
224
- .after(date, message?) // After a date
225
- .before(date, message?) // Before a date
226
- .past(message?) // Must be in past
227
- .future(message?) // Must be in future
228
- .required(message?)
477
+ // Import only what you need
478
+ import { emailValidator } from 'unischema/validators/string';
479
+ import { portValidator } from 'unischema/validators/number';
480
+ import { todayValidator } from 'unischema/validators/date';
481
+
482
+ // Or import by category
483
+ import * as stringValidators from 'unischema/validators/string';
484
+ import * as numberValidators from 'unischema/validators/number';
229
485
  ```
230
486
 
231
- #### `field.array(itemBuilder?)`
487
+ ## 🌐 Framework Examples
232
488
 
233
- ```typescript
234
- field.array(field.string())
235
- .min(count, message?) // Minimum items
236
- .max(count, message?) // Maximum items
237
- .length(count, message?) // Exact count
238
- .unique(message?) // All items unique
239
- .required(message?)
489
+ ### React
490
+
491
+ ```tsx
492
+ import { createForm } from 'unischema/frontend';
493
+ import { UserSchema } from './schemas';
494
+
495
+ function RegisterForm() {
496
+ const form = createForm(UserSchema, {
497
+ initialValues: { email: '', password: '' },
498
+ onSubmit: async (values) => {
499
+ await api.register(values);
500
+ }
501
+ });
502
+
503
+ const emailProps = form.getFieldProps('email');
504
+
505
+ return (
506
+ <form onSubmit={form.handleSubmit}>
507
+ <input {...emailProps} />
508
+ {emailProps.hasError && <span>{emailProps.error}</span>}
509
+
510
+ <button type="submit">Register</button>
511
+ </form>
512
+ );
513
+ }
240
514
  ```
241
515
 
242
- #### `field.object(schema)`
516
+ ### Vue
243
517
 
244
- ```typescript
245
- field.object(AddressSchema)
246
- .required(message?)
518
+ ```vue
519
+ <script setup>
520
+ import { createForm } from 'unischema/frontend';
521
+ import { UserSchema } from './schemas';
522
+
523
+ const form = createForm(UserSchema, {
524
+ onSubmit: async (values) => {
525
+ await api.register(values);
526
+ }
527
+ });
528
+
529
+ const emailProps = form.getFieldProps('email');
530
+ </script>
531
+
532
+ <template>
533
+ <form @submit.prevent="form.handleSubmit">
534
+ <input v-bind="emailProps" />
535
+ <span v-if="emailProps.hasError">{{ emailProps.error }}</span>
536
+ </form>
537
+ </template>
247
538
  ```
248
539
 
249
- ### Validation Functions
540
+ ### Express.js
250
541
 
251
542
  ```typescript
252
- import { validate, isValid, assertValid } from 'formschema';
543
+ import express from 'express';
544
+ import { validateBody, validateQuery, validateParams } from 'unischema/backend';
253
545
 
254
- // Returns ValidationResult
255
- const result = validate(schema.definition, data);
256
- // { valid: boolean, hardErrors: [], softErrors: [] }
546
+ const app = express();
257
547
 
258
- // Returns boolean
259
- const valid = isValid(schema.definition, data);
548
+ // Body validation
549
+ app.post('/users', validateBody(UserSchema), (req, res) => {
550
+ const user = req.validatedData; // ✅ Typed and validated
551
+ res.json(user);
552
+ });
553
+
554
+ // Query validation
555
+ app.get('/search', validateQuery(SearchSchema), (req, res) => {
556
+ const { query } = req.validatedData;
557
+ res.json(results);
558
+ });
260
559
 
261
- // Throws if invalid
262
- const data = assertValid(schema.definition, input);
560
+ // Params validation
561
+ app.get('/users/:id', validateParams(IdSchema), (req, res) => {
562
+ const { id } = req.validatedData;
563
+ res.json(user);
564
+ });
263
565
  ```
264
566
 
265
- ### Backend Adapters
567
+ ## 📊 Real-World Examples
568
+
569
+ ### User Registration
266
570
 
267
571
  ```typescript
268
- import {
269
- validateBody,
270
- validateQuery,
271
- validateParams,
272
- withValidation,
273
- createHandler,
274
- } from 'formschema/backend';
275
-
276
- // Express middleware
277
- app.post('/users', validateBody(UserSchema), handler);
278
- app.get('/users', validateQuery(QuerySchema), handler);
279
- app.get('/users/:id', validateParams(ParamsSchema), handler);
572
+ const RegisterSchema = schema({
573
+ email: field.string()
574
+ .email('Invalid email address')
575
+ .required('Email is required'),
576
+
577
+ username: field.string()
578
+ .alphanumeric('Only letters and numbers')
579
+ .min(3, 'At least 3 characters')
580
+ .max(20, 'Max 20 characters')
581
+ .required(),
280
582
 
281
- // Wrapper with typed handler
282
- app.post('/users', ...withValidation(UserSchema, async (req, res) => {
283
- const data = req.validatedData; // Typed!
284
- }));
583
+ password: field.string()
584
+ .min(8, 'At least 8 characters')
585
+ .pattern(/[A-Z]/, 'Need uppercase letter')
586
+ .pattern(/[0-9]/, 'Need a number')
587
+ .required(),
588
+
589
+ confirmPassword: field.string()
590
+ .matches('password', 'Passwords must match')
591
+ .required(),
285
592
 
286
- // Serverless handler
287
- const handler = createHandler(UserSchema, async ({ data }) => {
288
- return { user: await createUser(data) };
593
+ age: field.number()
594
+ .min(13, 'Must be 13+')
595
+ .minSoft(18, 'Parental consent required under 18')
596
+ .max(120, 'Invalid age')
597
+ .integer()
598
+ .required(),
599
+
600
+ acceptTerms: field.boolean()
601
+ .isTrue('You must accept the terms')
602
+ .required()
289
603
  });
290
604
  ```
291
605
 
292
- ### Frontend Adapters
606
+ ### E-Commerce Order
293
607
 
294
608
  ```typescript
295
- import { createForm, parseApiErrors, focusFirstError } from 'formschema/frontend';
609
+ const OrderSchema = schema({
610
+ customerId: field.string()
611
+ .alphanumeric()
612
+ .length(10)
613
+ .required(),
614
+
615
+ items: field.array(field.object(schema({
616
+ productId: field.string().required(),
617
+ quantity: field.number().min(1).integer(),
618
+ price: field.number().positive()
619
+ })))
620
+ .min(1, 'At least one item required')
621
+ .max(50, 'Maximum 50 items per order'),
622
+
623
+ total: field.number()
624
+ .positive()
625
+ .required(),
626
+
627
+ shippingAddress: field.object(schema({
628
+ street: field.string().required(),
629
+ city: field.string().required(),
630
+ state: field.string().uppercase().length(2),
631
+ zipCode: field.string().pattern(/^\d{5}$/)
632
+ })).required(),
633
+
634
+ shippingDate: field.date()
635
+ .future('Must be a future date')
636
+ .weekday('No weekend shipping')
637
+ });
638
+ ```
296
639
 
297
- const form = createForm(UserSchema, {
298
- initialValues: { email: '', password: '' },
299
- validateOnChange: true,
300
- validateOnBlur: true,
301
- onSubmit: async (values, helpers) => {
302
- // Submit logic
303
- },
640
+ ### API Configuration
641
+
642
+ ```typescript
643
+ const ServerConfigSchema = schema({
644
+ host: field.string()
645
+ .ipAddress('Invalid IP address')
646
+ .required(),
647
+
648
+ port: field.number()
649
+ .port('Invalid port number')
650
+ .required(),
651
+
652
+ ssl: field.boolean()
653
+ .required(),
654
+
655
+ maxConnections: field.number()
656
+ .integer()
657
+ .positive()
658
+ .between(1, 10000),
659
+
660
+ timeout: field.number()
661
+ .integer()
662
+ .positive()
663
+ .multipleOf(1000, 'Must be in seconds (1000ms)')
304
664
  });
665
+ ```
305
666
 
306
- // Form methods
307
- form.setFieldValue('email', 'test@example.com');
308
- form.touchField('email');
309
- form.validate();
310
- form.validateField('email');
311
- form.reset();
312
- form.handleSubmit();
667
+ ## 🚀 Migration Guide
313
668
 
314
- // Get field props for binding
315
- const props = form.getFieldProps('email');
316
- // { name, value, onChange, onBlur, error, hasError, warning, hasWarning, ... }
669
+ ### From Yup
317
670
 
318
- // Handle server errors
319
- const result = parseApiErrors(apiResponse);
320
- form.setServerErrors(result.hardErrors);
321
- ```
671
+ ```typescript
672
+ // Yup
673
+ const schema = yup.object({
674
+ email: yup.string().email().required(),
675
+ age: yup.number().min(18)
676
+ });
322
677
 
323
- ## Enterprise Response Format
678
+ // Unischema
679
+ const schema = schema({
680
+ email: field.string().email().required(),
681
+ age: field.number().min(18)
682
+ });
683
+ ```
324
684
 
325
- FormSchema uses a consistent error structure compatible with enterprise systems:
685
+ ### From Zod
326
686
 
327
687
  ```typescript
328
- interface EnterpriseValidationResponse {
329
- status: 'success' | 'validation_error';
330
- data?: unknown;
331
- errors: ValidationError[];
332
- msg: string;
333
- validation: {
334
- hard_validations: ValidationError[];
335
- soft_validations: ValidationError[];
336
- };
337
- }
688
+ // Zod
689
+ const schema = z.object({
690
+ email: z.string().email(),
691
+ age: z.number().min(18)
692
+ });
338
693
 
339
- interface ValidationError {
340
- field: string; // e.g., "email" or "address.city"
341
- code: string; // e.g., "REQUIRED", "MIN_LENGTH"
342
- message: string; // Human-readable message
343
- severity: 'hard' | 'soft';
344
- }
694
+ // Unischema
695
+ const schema = schema({
696
+ email: field.string().email(),
697
+ age: field.number().min(18)
698
+ });
345
699
  ```
346
700
 
347
- ## Incremental Adoption
701
+ ## 🎨 Bundle Size
702
+
703
+ Unischema is optimized for tree-shaking:
704
+
705
+ - **Full library**: ~15KB min+gzip
706
+ - **Core only**: ~5KB min+gzip
707
+ - **Single validator**: ~2KB min+gzip
348
708
 
349
- FormSchema supports incremental adoption in existing systems:
709
+ Import only what you use for minimal bundle size.
710
+
711
+ ## 📖 API Reference
712
+
713
+ ### Core Functions
350
714
 
351
715
  ```typescript
352
- // Use only backend validation
353
- import { validateInput } from 'formschema/backend';
354
- const { valid, data, response } = validateInput(UserSchema, input);
716
+ import {
717
+ schema, // Create schema
718
+ field, // Field builders
719
+ validate, // Validate data
720
+ validateSchema, // Validate with schema
721
+ isValid, // Boolean validation
722
+ assertValid, // Throws if invalid
723
+ extend, // Extend schema
724
+ pick, // Pick fields
725
+ omit, // Omit fields
726
+ merge, // Merge schemas
727
+ partial, // Make all optional
728
+ type InferInput, // Input type
729
+ type InferOutput // Output type
730
+ } from 'unischema';
731
+ ```
355
732
 
356
- // Use only type generation
357
- import { type InferInput } from 'formschema';
358
- type User = InferInput<typeof UserSchema>;
733
+ ### Backend
359
734
 
360
- // Use only frontend validation
361
- import { validate } from 'formschema';
362
- const result = validate(UserSchema.definition, formData);
735
+ ```typescript
736
+ import {
737
+ validateBody, // Validate request body
738
+ validateQuery, // Validate query params
739
+ validateParams, // Validate route params
740
+ withValidation, // Wrapper with validation
741
+ createHandler // Serverless handler
742
+ } from 'unischema/backend';
363
743
  ```
364
744
 
365
- ## License
745
+ ### Frontend
746
+
747
+ ```typescript
748
+ import {
749
+ createForm, // Create form helper
750
+ parseApiErrors, // Parse server errors
751
+ focusFirstError // Focus first error field
752
+ } from 'unischema/frontend';
753
+ ```
754
+
755
+ ## 🤝 Contributing
756
+
757
+ Contributions are welcome! Please check out the [GitHub repository](https://github.com/Gaurav-pasi/unischema).
758
+
759
+ ## 📄 License
760
+
761
+ MIT © [Gaurav Pasi](https://github.com/Gaurav-pasi)
762
+
763
+ ## 🔗 Links
764
+
765
+ - [npm package](https://www.npmjs.com/package/unischema)
766
+ - [GitHub repository](https://github.com/Gaurav-pasi/unischema)
767
+ - [Issue tracker](https://github.com/Gaurav-pasi/unischema/issues)
768
+
769
+ ---
366
770
 
367
- MIT
771
+ **Made with ❤️ for developers who value type safety and code reusability**