@forgehive/schema 0.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.
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ describe('Schema Custom Number Validations', () => {
5
+ it('should validate number min/max constraints', () => {
6
+ const schema = index_1.Schema.from({
7
+ age: {
8
+ type: 'number',
9
+ validations: {
10
+ min: 18,
11
+ max: 100
12
+ }
13
+ },
14
+ score: {
15
+ type: 'number',
16
+ validations: {
17
+ min: 0,
18
+ max: 100
19
+ }
20
+ }
21
+ });
22
+ // Valid cases
23
+ expect(schema.validate({ age: 25, score: 85 })).toBe(true);
24
+ expect(schema.validate({ age: 18, score: 0 })).toBe(true);
25
+ expect(schema.validate({ age: 100, score: 100 })).toBe(true);
26
+ // Invalid cases
27
+ expect(schema.validate({ age: 17, score: 85 })).toBe(false);
28
+ expect(schema.validate({ age: 25, score: -1 })).toBe(false);
29
+ expect(schema.validate({ age: 101, score: 85 })).toBe(false);
30
+ expect(schema.validate({ age: 25, score: 101 })).toBe(false);
31
+ });
32
+ it('should correctly describe number validations', () => {
33
+ const schema = index_1.Schema.from({
34
+ age: {
35
+ type: 'number',
36
+ validations: {
37
+ min: 18,
38
+ max: 100
39
+ }
40
+ }
41
+ });
42
+ const description = schema.describe();
43
+ expect(description.age).toEqual({
44
+ type: 'number',
45
+ validations: {
46
+ min: 18,
47
+ max: 100
48
+ }
49
+ });
50
+ });
51
+ });
52
+ describe('Schema Custom String Validations', () => {
53
+ it('should validate string email format', () => {
54
+ const schema = index_1.Schema.from({
55
+ email: {
56
+ type: 'string',
57
+ validations: {
58
+ email: true
59
+ }
60
+ }
61
+ });
62
+ // Valid cases
63
+ expect(schema.validate({ email: 'user@example.com' })).toBe(true);
64
+ expect(schema.validate({ email: 'test.name@domain.co.uk' })).toBe(true);
65
+ expect(schema.validate({ email: 'user+tag@example.com' })).toBe(true);
66
+ // Invalid cases
67
+ expect(schema.validate({ email: 'not-an-email' })).toBe(false);
68
+ expect(schema.validate({ email: 'missing@domain' })).toBe(false);
69
+ expect(schema.validate({ email: '@domain.com' })).toBe(false);
70
+ });
71
+ it('should validate string length constraints', () => {
72
+ const schema = index_1.Schema.from({
73
+ username: {
74
+ type: 'string',
75
+ validations: {
76
+ minLength: 3,
77
+ maxLength: 20
78
+ }
79
+ }
80
+ });
81
+ // Valid cases
82
+ expect(schema.validate({ username: 'abc' })).toBe(true);
83
+ expect(schema.validate({ username: 'valid_username' })).toBe(true);
84
+ expect(schema.validate({ username: 'a'.repeat(20) })).toBe(true);
85
+ // Invalid cases
86
+ expect(schema.validate({ username: 'ab' })).toBe(false);
87
+ expect(schema.validate({ username: 'a'.repeat(21) })).toBe(false);
88
+ });
89
+ it('should validate string regex pattern', () => {
90
+ const schema = index_1.Schema.from({
91
+ username: {
92
+ type: 'string',
93
+ validations: {
94
+ regex: '^[a-zA-Z0-9_]+$'
95
+ }
96
+ }
97
+ });
98
+ // Valid cases
99
+ expect(schema.validate({ username: 'john_doe123' })).toBe(true);
100
+ expect(schema.validate({ username: 'User123' })).toBe(true);
101
+ expect(schema.validate({ username: '123456' })).toBe(true);
102
+ // Invalid cases
103
+ expect(schema.validate({ username: 'john-doe' })).toBe(false);
104
+ expect(schema.validate({ username: 'user@123' })).toBe(false);
105
+ expect(schema.validate({ username: 'user name' })).toBe(false);
106
+ });
107
+ it('should correctly describe string validations', () => {
108
+ const schema = index_1.Schema.from({
109
+ email: {
110
+ type: 'string',
111
+ validations: {
112
+ email: true,
113
+ minLength: 5,
114
+ maxLength: 100
115
+ }
116
+ }
117
+ });
118
+ const description = schema.describe();
119
+ expect(description.email).toEqual({
120
+ type: 'string',
121
+ validations: {
122
+ email: true,
123
+ minLength: 5,
124
+ maxLength: 100
125
+ }
126
+ });
127
+ });
128
+ it('should correctly describe string regex validation', () => {
129
+ const schema = index_1.Schema.from({
130
+ username: {
131
+ type: 'string',
132
+ validations: {
133
+ regex: '^[a-zA-Z0-9_]+$'
134
+ }
135
+ }
136
+ });
137
+ const description = schema.describe();
138
+ expect(description.username).toEqual({
139
+ type: 'string',
140
+ validations: {
141
+ regex: '^[a-zA-Z0-9_]+$'
142
+ }
143
+ });
144
+ });
145
+ it('should handle regex with other string validations', () => {
146
+ const schema = index_1.Schema.from({
147
+ username: {
148
+ type: 'string',
149
+ validations: {
150
+ regex: '^[a-zA-Z0-9_]+$',
151
+ minLength: 3,
152
+ maxLength: 20
153
+ }
154
+ }
155
+ });
156
+ // Valid cases
157
+ expect(schema.validate({ username: 'john_doe123' })).toBe(true);
158
+ expect(schema.validate({ username: 'User123' })).toBe(true);
159
+ // Invalid cases - regex
160
+ expect(schema.validate({ username: 'john-doe' })).toBe(false);
161
+ expect(schema.validate({ username: 'user@123' })).toBe(false);
162
+ // Invalid cases - length
163
+ expect(schema.validate({ username: 'ab' })).toBe(false);
164
+ expect(schema.validate({ username: 'a'.repeat(21) })).toBe(false);
165
+ });
166
+ it('should handle regex patterns containing forward slashes', () => {
167
+ const schema = index_1.Schema.from({
168
+ path: {
169
+ type: 'string',
170
+ validations: {
171
+ regex: '^/api/v[0-9]+/users/[0-9]+$'
172
+ }
173
+ }
174
+ });
175
+ // Valid cases
176
+ expect(schema.validate({ path: '/api/v1/users/123' })).toBe(true);
177
+ expect(schema.validate({ path: '/api/v2/users/456' })).toBe(true);
178
+ expect(schema.validate({ path: '/api/v10/users/789' })).toBe(true);
179
+ // Invalid cases
180
+ expect(schema.validate({ path: 'api/v1/users/123' })).toBe(false);
181
+ expect(schema.validate({ path: '/api/v1/users' })).toBe(false);
182
+ expect(schema.validate({ path: '/api/v1/users/abc' })).toBe(false);
183
+ // Verify the description preserves the slashes within the pattern
184
+ const description = schema.describe();
185
+ expect(description.path).toEqual({
186
+ type: 'string',
187
+ validations: {
188
+ regex: '^/api/v[0-9]+/users/[0-9]+$'
189
+ }
190
+ });
191
+ });
192
+ it('should handle round trip of regex patterns containing forward slashes', () => {
193
+ const schema = index_1.Schema.from({
194
+ path: {
195
+ type: 'string',
196
+ validations: {
197
+ regex: '^/api/v[0-9]+/users/[0-9]+$'
198
+ }
199
+ }
200
+ });
201
+ const description = schema.describe();
202
+ const cloneSchema = index_1.Schema.from(description);
203
+ // Valid cases
204
+ expect(cloneSchema.validate({ path: '/api/v1/users/123' })).toBe(true);
205
+ expect(cloneSchema.validate({ path: '/api/v2/users/456' })).toBe(true);
206
+ expect(cloneSchema.validate({ path: '/api/v10/users/789' })).toBe(true);
207
+ // Invalid cases
208
+ expect(cloneSchema.validate({ path: 'api/v1/users/123' })).toBe(false);
209
+ expect(cloneSchema.validate({ path: '/api/v1/users' })).toBe(false);
210
+ expect(cloneSchema.validate({ path: '/api/v1/users/abc' })).toBe(false);
211
+ // Verify the description preserves the slashes within the pattern
212
+ const cloneDescription = cloneSchema.describe();
213
+ expect(cloneDescription).toMatchObject(description);
214
+ });
215
+ });
216
+ describe('Schema Roundtrip', () => {
217
+ it('should maintain custom validations through describe/from cycle', () => {
218
+ const originalSchema = index_1.Schema.from({
219
+ age: {
220
+ type: 'number',
221
+ validations: {
222
+ min: 18,
223
+ max: 100
224
+ }
225
+ },
226
+ email: {
227
+ type: 'string',
228
+ validations: {
229
+ email: true
230
+ }
231
+ },
232
+ username: {
233
+ type: 'string',
234
+ validations: {
235
+ regex: '^[a-zA-Z0-9_]+$',
236
+ minLength: 3,
237
+ maxLength: 20
238
+ }
239
+ }
240
+ });
241
+ const description = originalSchema.describe();
242
+ const reconstructedSchema = index_1.Schema.from(description);
243
+ // Both schemas should validate the same data
244
+ const validData = {
245
+ age: 25,
246
+ email: 'user@example.com',
247
+ username: 'john_doe123'
248
+ };
249
+ const invalidData = {
250
+ age: 17,
251
+ email: 'not-an-email',
252
+ username: 'john-doe'
253
+ };
254
+ expect(originalSchema.validate(validData)).toBe(true);
255
+ expect(reconstructedSchema.validate(validData)).toBe(true);
256
+ expect(originalSchema.validate(invalidData)).toBe(false);
257
+ expect(reconstructedSchema.validate(invalidData)).toBe(false);
258
+ });
259
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ describe('Schema single date field', () => {
5
+ const schema = index_1.Schema.from({
6
+ createdAt: { type: 'date' }
7
+ });
8
+ it('should validate a valid date', () => {
9
+ const data = {
10
+ createdAt: new Date()
11
+ };
12
+ expect(schema.validate(data)).toBe(true);
13
+ });
14
+ it('should validate a valid date string', () => {
15
+ const data = {
16
+ createdAt: new Date('2024-03-20T12:00:00Z')
17
+ };
18
+ expect(schema.validate(data)).toBe(true);
19
+ });
20
+ it('should reject invalid date', () => {
21
+ const data = {
22
+ createdAt: 'invalid-date'
23
+ };
24
+ expect(schema.validate(data)).toBe(false);
25
+ });
26
+ it('should reject non-date value', () => {
27
+ const data = {
28
+ createdAt: 123
29
+ };
30
+ expect(schema.validate(data)).toBe(false);
31
+ });
32
+ });
33
+ describe('Schema array of dates', () => {
34
+ const schema = index_1.Schema.from({
35
+ timestamps: { type: 'array', items: { type: 'date' } }
36
+ });
37
+ it('should validate an array of valid dates', () => {
38
+ const data = {
39
+ timestamps: [new Date(), new Date()]
40
+ };
41
+ expect(schema.validate(data)).toBe(true);
42
+ });
43
+ it('should validate an array of valid date strings', () => {
44
+ const data = {
45
+ timestamps: [new Date('2024-03-20T12:00:00Z'), new Date('2024-03-21T12:00:00Z')]
46
+ };
47
+ expect(schema.validate(data)).toBe(true);
48
+ });
49
+ it('should reject array with invalid date', () => {
50
+ const data = {
51
+ timestamps: [new Date(), 'invalid-date']
52
+ };
53
+ expect(schema.validate(data)).toBe(false);
54
+ });
55
+ it('should reject non-array value', () => {
56
+ const data = {
57
+ timestamps: 'not-an-array'
58
+ };
59
+ expect(schema.validate(data)).toBe(false);
60
+ });
61
+ });
62
+ describe('Dates Schema description', () => {
63
+ it('should correctly describe date fields', () => {
64
+ const schema = new index_1.Schema({
65
+ createdAt: index_1.Schema.date(),
66
+ timestamps: index_1.Schema.array(index_1.Schema.date())
67
+ });
68
+ const description = schema.describe();
69
+ expect(description).toEqual({
70
+ createdAt: { type: 'date' },
71
+ timestamps: { type: 'array', items: { type: 'date' } }
72
+ });
73
+ });
74
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const index_1 = __importDefault(require("../index"));
7
+ describe('Schema basic types', () => {
8
+ it('should validate a string', () => {
9
+ const schema = new index_1.default({
10
+ name: index_1.default.string(),
11
+ });
12
+ const result = schema.validate({
13
+ name: 'World',
14
+ });
15
+ expect(result).toBe(true);
16
+ });
17
+ it('should validate a string and number', () => {
18
+ const schema = new index_1.default({
19
+ name: index_1.default.string(),
20
+ age: index_1.default.number(),
21
+ });
22
+ const result = schema.validate({
23
+ name: 'World',
24
+ age: 20,
25
+ });
26
+ expect(result).toEqual(true);
27
+ });
28
+ });
29
+ describe('Schema description', () => {
30
+ it('should describe a string', () => {
31
+ const schema = new index_1.default({
32
+ name: index_1.default.string(),
33
+ });
34
+ const result = schema.describe();
35
+ expect(result).toEqual({
36
+ name: { type: 'string' },
37
+ });
38
+ });
39
+ it('should describe a string and number', () => {
40
+ const schema = new index_1.default({
41
+ name: index_1.default.string(),
42
+ age: index_1.default.number(),
43
+ });
44
+ const result = schema.describe();
45
+ expect(result).toEqual({
46
+ name: { type: 'string' },
47
+ age: { type: 'number' },
48
+ });
49
+ });
50
+ });
51
+ describe('Schema hydrate', () => {
52
+ it('should describe a string', () => {
53
+ const description = {
54
+ name: { type: 'string' },
55
+ age: { type: 'number' },
56
+ };
57
+ const schema = index_1.default.from(description);
58
+ const result = schema.describe();
59
+ expect(result).toEqual({
60
+ name: { type: 'string' },
61
+ age: { type: 'number' },
62
+ });
63
+ });
64
+ });
65
+ describe('Schema validation errors', () => {
66
+ it('should reject invalid string type', () => {
67
+ const schema = new index_1.default({
68
+ name: index_1.default.string(),
69
+ });
70
+ const result = schema.validate({
71
+ name: 123, // number instead of string
72
+ });
73
+ expect(result).toBe(false);
74
+ });
75
+ it('should reject invalid number type', () => {
76
+ const schema = new index_1.default({
77
+ age: index_1.default.number(),
78
+ });
79
+ const result = schema.validate({
80
+ age: 'not a number',
81
+ });
82
+ expect(result).toBe(false);
83
+ });
84
+ });
85
+ // describe('Schema optional fields', () => {
86
+ // it('should validate with missing optional field', () => {
87
+ // const schema = new Schema({
88
+ // name: Schema.string(),
89
+ // age: Schema.number().optional(),
90
+ // })
91
+ // const result = schema.validate({
92
+ // name: 'World',
93
+ // })
94
+ // expect(result).toBe(true)
95
+ // })
96
+ // })
97
+ describe('Schema arrays', () => {
98
+ it('should validate array of strings', () => {
99
+ const schema = new index_1.default({
100
+ tags: index_1.default.array(index_1.default.string()),
101
+ });
102
+ const result = schema.validate({
103
+ tags: ['tag1', 'tag2', 'tag3'],
104
+ });
105
+ expect(result).toBe(true);
106
+ });
107
+ it('should validate array of numbers', () => {
108
+ const schema = new index_1.default({
109
+ scores: index_1.default.array(index_1.default.number()),
110
+ });
111
+ const result = schema.validate({
112
+ scores: [1, 2, 3, 4, 5],
113
+ });
114
+ expect(result).toBe(true);
115
+ });
116
+ it('should reject invalid array types', () => {
117
+ const schema = new index_1.default({
118
+ tags: index_1.default.array(index_1.default.string()),
119
+ });
120
+ const result = schema.validate({
121
+ tags: ['tag1', 123, 'tag3'], // mixed types
122
+ });
123
+ expect(result).toBe(false);
124
+ });
125
+ it('should validate empty arrays', () => {
126
+ const schema = new index_1.default({
127
+ tags: index_1.default.array(index_1.default.string()),
128
+ });
129
+ const result = schema.validate({
130
+ tags: [],
131
+ });
132
+ expect(result).toBe(true);
133
+ });
134
+ it('should describe array schema', () => {
135
+ const schema = new index_1.default({
136
+ tags: index_1.default.array(index_1.default.string()),
137
+ scores: index_1.default.array(index_1.default.number()),
138
+ });
139
+ const result = schema.describe();
140
+ expect(result).toEqual({
141
+ tags: { type: 'array', items: { type: 'string' } },
142
+ scores: { type: 'array', items: { type: 'number' } },
143
+ });
144
+ });
145
+ it('should hydrate array schema from description', () => {
146
+ const description = {
147
+ tags: { type: 'array', items: { type: 'string' } },
148
+ scores: { type: 'array', items: { type: 'number' } },
149
+ };
150
+ const schema = index_1.default.from(description);
151
+ const result = schema.describe();
152
+ expect(result).toEqual({
153
+ tags: { type: 'array', items: { type: 'string' } },
154
+ scores: { type: 'array', items: { type: 'number' } },
155
+ });
156
+ });
157
+ });
158
+ describe('Schema custom validation', () => {
159
+ it('should validate with custom rules', () => {
160
+ const schema = new index_1.default({
161
+ age: index_1.default.number().min(0).max(120),
162
+ email: index_1.default.string().email(),
163
+ });
164
+ const result = schema.validate({
165
+ age: 25,
166
+ email: 'test@example.com',
167
+ });
168
+ expect(result).toBe(true);
169
+ });
170
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ describe('Schema Custom Validations', () => {
5
+ describe('Optional Fields', () => {
6
+ it('should handle optional string fields', () => {
7
+ const schema = index_1.Schema.from({
8
+ name: { type: 'string', optional: true },
9
+ age: { type: 'number' }
10
+ });
11
+ // Valid with optional field present
12
+ expect(schema.validate({ name: 'John', age: 30 })).toBe(true);
13
+ // Valid with optional field missing
14
+ expect(schema.validate({ age: 30 })).toBe(true);
15
+ // Invalid - missing required field
16
+ expect(schema.validate({ name: 'John' })).toBe(false);
17
+ });
18
+ it('should handle optional array fields', () => {
19
+ const schema = index_1.Schema.from({
20
+ tags: { type: 'array', items: { type: 'string' }, optional: true },
21
+ scores: { type: 'array', items: { type: 'number' } }
22
+ });
23
+ // Valid with optional array present
24
+ expect(schema.validate({ tags: ['tag1', 'tag2'], scores: [1, 2, 3] })).toBe(true);
25
+ // Valid with optional array missing
26
+ expect(schema.validate({ scores: [1, 2, 3] })).toBe(true);
27
+ // Invalid - missing required array
28
+ expect(schema.validate({ tags: ['tag1'] })).toBe(false);
29
+ });
30
+ it('should correctly describe optional fields', () => {
31
+ const schema = index_1.Schema.from({
32
+ name: { type: 'string', optional: true },
33
+ age: { type: 'number' },
34
+ tags: { type: 'array', items: { type: 'string' }, optional: true }
35
+ });
36
+ const description = schema.describe();
37
+ expect(description.name).toEqual({ type: 'string', optional: true });
38
+ expect(description.age).toEqual({ type: 'number' });
39
+ expect(description.tags).toEqual({ type: 'array', items: { type: 'string' }, optional: true });
40
+ });
41
+ });
42
+ describe('Schema Roundtrip', () => {
43
+ it('should maintain optional fields through describe/from cycle', () => {
44
+ const originalSchema = index_1.Schema.from({
45
+ name: { type: 'string', optional: true },
46
+ age: { type: 'number' },
47
+ tags: { type: 'array', items: { type: 'string' }, optional: true }
48
+ });
49
+ const description = originalSchema.describe();
50
+ const reconstructedSchema = index_1.Schema.from(description);
51
+ // Both schemas should validate the same data
52
+ const validData = { name: 'John', age: 30, tags: ['tag1'] };
53
+ const dataWithoutOptional = { age: 30 };
54
+ expect(originalSchema.validate(validData)).toBe(true);
55
+ expect(reconstructedSchema.validate(validData)).toBe(true);
56
+ expect(originalSchema.validate(dataWithoutOptional)).toBe(true);
57
+ expect(reconstructedSchema.validate(dataWithoutOptional)).toBe(true);
58
+ });
59
+ });
60
+ });
@@ -0,0 +1 @@
1
+ export {};