@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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +285 -0
- package/dist/test/custom-validations.test.d.ts +1 -0
- package/dist/test/custom-validations.test.js +259 -0
- package/dist/test/dates.test.d.ts +1 -0
- package/dist/test/dates.test.js +74 -0
- package/dist/test/index.test.d.ts +1 -0
- package/dist/test/index.test.js +170 -0
- package/dist/test/optionals.test.d.ts +1 -0
- package/dist/test/optionals.test.js +60 -0
- package/dist/test/record.test.d.ts +1 -0
- package/dist/test/record.test.js +303 -0
- package/jest.config.js +11 -0
- package/package.json +20 -0
- package/src/index.ts +361 -0
- package/src/test/custom-validations.test.ts +293 -0
- package/src/test/dates.test.ts +85 -0
- package/src/test/index.test.ts +205 -0
- package/src/test/optionals.test.ts +73 -0
- package/src/test/record.test.ts +368 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import Schema, { type SchemaDescription } from '../index'
|
|
2
|
+
|
|
3
|
+
describe('Schema Record Types', () => {
|
|
4
|
+
describe('String Record', () => {
|
|
5
|
+
it('should validate a record with string values', () => {
|
|
6
|
+
const schema = new Schema({
|
|
7
|
+
data: Schema.stringRecord(),
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const result = schema.validate({
|
|
11
|
+
data: {
|
|
12
|
+
firstName: 'John',
|
|
13
|
+
lastName: 'Doe',
|
|
14
|
+
occupation: 'Developer'
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
expect(result).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should reject a record with non-string values', () => {
|
|
22
|
+
const schema = new Schema({
|
|
23
|
+
data: Schema.stringRecord(),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const result = schema.validate({
|
|
27
|
+
data: {
|
|
28
|
+
name: 'John',
|
|
29
|
+
age: 30, // Number is not allowed
|
|
30
|
+
isActive: true // Boolean is not allowed
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
expect(result).toBe(false)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should create a schema from description with string record type', () => {
|
|
38
|
+
const description: SchemaDescription = {
|
|
39
|
+
data: {
|
|
40
|
+
type: 'stringRecord',
|
|
41
|
+
optional: false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const schema = Schema.from(description)
|
|
46
|
+
|
|
47
|
+
const validData = {
|
|
48
|
+
data: {
|
|
49
|
+
firstName: 'John',
|
|
50
|
+
lastName: 'Doe'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = schema.validate(validData)
|
|
55
|
+
expect(result).toBe(true)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('Number Record', () => {
|
|
60
|
+
it('should validate a record with number values', () => {
|
|
61
|
+
const schema = new Schema({
|
|
62
|
+
data: Schema.numberRecord(),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const result = schema.validate({
|
|
66
|
+
data: {
|
|
67
|
+
age: 30,
|
|
68
|
+
experience: 5,
|
|
69
|
+
salary: 100000
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
expect(result).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should reject a record with non-number values', () => {
|
|
77
|
+
const schema = new Schema({
|
|
78
|
+
data: Schema.numberRecord(),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const result = schema.validate({
|
|
82
|
+
data: {
|
|
83
|
+
age: 30,
|
|
84
|
+
name: 'John', // String is not allowed
|
|
85
|
+
isActive: true // Boolean is not allowed
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(result).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should create a schema from description with number record type', () => {
|
|
93
|
+
const description: SchemaDescription = {
|
|
94
|
+
data: {
|
|
95
|
+
type: 'numberRecord',
|
|
96
|
+
optional: false
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const schema = Schema.from(description)
|
|
101
|
+
|
|
102
|
+
const validData = {
|
|
103
|
+
data: {
|
|
104
|
+
age: 30,
|
|
105
|
+
experience: 5
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = schema.validate(validData)
|
|
110
|
+
expect(result).toBe(true)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('Boolean Record', () => {
|
|
115
|
+
it('should validate a record with boolean values', () => {
|
|
116
|
+
const schema = new Schema({
|
|
117
|
+
data: Schema.booleanRecord(),
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const result = schema.validate({
|
|
121
|
+
data: {
|
|
122
|
+
isActive: true,
|
|
123
|
+
isAdmin: false,
|
|
124
|
+
hasAccess: true
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
expect(result).toBe(true)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('should reject a record with non-boolean values', () => {
|
|
132
|
+
const schema = new Schema({
|
|
133
|
+
data: Schema.booleanRecord(),
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const result = schema.validate({
|
|
137
|
+
data: {
|
|
138
|
+
isActive: true,
|
|
139
|
+
name: 'John', // String is not allowed
|
|
140
|
+
age: 30 // Number is not allowed
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
expect(result).toBe(false)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should create a schema from description with boolean record type', () => {
|
|
148
|
+
const description: SchemaDescription = {
|
|
149
|
+
data: {
|
|
150
|
+
type: 'booleanRecord',
|
|
151
|
+
optional: false
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const schema = Schema.from(description)
|
|
156
|
+
|
|
157
|
+
const validData = {
|
|
158
|
+
data: {
|
|
159
|
+
isActive: true,
|
|
160
|
+
isAdmin: false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const result = schema.validate(validData)
|
|
165
|
+
expect(result).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('Mixed Record', () => {
|
|
170
|
+
it('should validate a record with mixed values (string, number, boolean)', () => {
|
|
171
|
+
const schema = new Schema({
|
|
172
|
+
data: Schema.mixedRecord(),
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const result = schema.validate({
|
|
176
|
+
data: {
|
|
177
|
+
name: 'John',
|
|
178
|
+
age: 30,
|
|
179
|
+
isActive: true
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
expect(result).toBe(true)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should validate a record with only string values', () => {
|
|
187
|
+
const schema = new Schema({
|
|
188
|
+
data: Schema.mixedRecord(),
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const result = schema.validate({
|
|
192
|
+
data: {
|
|
193
|
+
firstName: 'John',
|
|
194
|
+
lastName: 'Doe',
|
|
195
|
+
occupation: 'Developer'
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
expect(result).toBe(true)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('should validate a record with only number values', () => {
|
|
203
|
+
const schema = new Schema({
|
|
204
|
+
data: Schema.mixedRecord(),
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const result = schema.validate({
|
|
208
|
+
data: {
|
|
209
|
+
age: 30,
|
|
210
|
+
experience: 5,
|
|
211
|
+
salary: 100000
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
expect(result).toBe(true)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('should validate a record with only boolean values', () => {
|
|
219
|
+
const schema = new Schema({
|
|
220
|
+
data: Schema.mixedRecord(),
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const result = schema.validate({
|
|
224
|
+
data: {
|
|
225
|
+
isActive: true,
|
|
226
|
+
isAdmin: false,
|
|
227
|
+
hasAccess: true
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
expect(result).toBe(true)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('should reject a record with invalid value types', () => {
|
|
235
|
+
const schema = new Schema({
|
|
236
|
+
data: Schema.mixedRecord(),
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const result = schema.validate({
|
|
240
|
+
data: {
|
|
241
|
+
name: 'John',
|
|
242
|
+
createdAt: new Date(), // Date is not allowed
|
|
243
|
+
items: [1, 2, 3] // Array is not allowed
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
expect(result).toBe(false)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should create a schema from description with mixed record type', () => {
|
|
251
|
+
const description: SchemaDescription = {
|
|
252
|
+
data: {
|
|
253
|
+
type: 'mixedRecord',
|
|
254
|
+
optional: false
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const schema = Schema.from(description)
|
|
259
|
+
|
|
260
|
+
const validData = {
|
|
261
|
+
data: {
|
|
262
|
+
name: 'John',
|
|
263
|
+
age: 30,
|
|
264
|
+
isActive: true
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const result = schema.validate(validData)
|
|
269
|
+
expect(result).toBe(true)
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
describe('Common Record Functionality', () => {
|
|
274
|
+
it('should reject a record with non-string keys', () => {
|
|
275
|
+
const schema = new Schema({
|
|
276
|
+
data: Schema.stringRecord(),
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
// TypeScript would catch this at compile time, but we're testing runtime behavior
|
|
280
|
+
const invalidData = {
|
|
281
|
+
data: {
|
|
282
|
+
name: 'John',
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Add a non-string key using Object.defineProperty
|
|
287
|
+
Object.defineProperty(invalidData.data, 123, {
|
|
288
|
+
value: 'test',
|
|
289
|
+
enumerable: true
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
// Note: Zod's record validation doesn't actually check for non-string keys at runtime
|
|
293
|
+
// This is a limitation of JavaScript/TypeScript, as all object keys are converted to strings
|
|
294
|
+
// So this test will actually pass, not fail
|
|
295
|
+
const result = schema.validate(invalidData)
|
|
296
|
+
expect(result).toBe(true)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should create a schema from description with optional record type', () => {
|
|
300
|
+
const description: SchemaDescription = {
|
|
301
|
+
data: {
|
|
302
|
+
type: 'stringRecord',
|
|
303
|
+
optional: true
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const schema = Schema.from(description)
|
|
308
|
+
|
|
309
|
+
// Test with record present
|
|
310
|
+
const validData1 = {
|
|
311
|
+
data: {
|
|
312
|
+
name: 'John'
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Test with record missing
|
|
317
|
+
const validData2 = {}
|
|
318
|
+
|
|
319
|
+
expect(schema.validate(validData1)).toBe(true)
|
|
320
|
+
expect(schema.validate(validData2)).toBe(true)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('should describe a schema with string record type', () => {
|
|
324
|
+
const schema = new Schema({
|
|
325
|
+
data: Schema.stringRecord(),
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const description = schema.describe()
|
|
329
|
+
|
|
330
|
+
expect(description).toHaveProperty('data')
|
|
331
|
+
expect(description.data).toHaveProperty('type', 'stringRecord')
|
|
332
|
+
// The optional property is only included when it's true, not when it's false
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('should describe a schema with number record type', () => {
|
|
336
|
+
const schema = new Schema({
|
|
337
|
+
data: Schema.numberRecord(),
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const description = schema.describe()
|
|
341
|
+
|
|
342
|
+
expect(description).toHaveProperty('data')
|
|
343
|
+
expect(description.data).toHaveProperty('type', 'numberRecord')
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('should describe a schema with boolean record type', () => {
|
|
347
|
+
const schema = new Schema({
|
|
348
|
+
data: Schema.booleanRecord(),
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
const description = schema.describe()
|
|
352
|
+
|
|
353
|
+
expect(description).toHaveProperty('data')
|
|
354
|
+
expect(description.data).toHaveProperty('type', 'booleanRecord')
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('should describe a schema with mixed record type', () => {
|
|
358
|
+
const schema = new Schema({
|
|
359
|
+
data: Schema.mixedRecord(),
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
const description = schema.describe()
|
|
363
|
+
|
|
364
|
+
expect(description).toHaveProperty('data')
|
|
365
|
+
expect(description.data).toHaveProperty('type', 'mixedRecord')
|
|
366
|
+
})
|
|
367
|
+
})
|
|
368
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strictNullChecks": true,
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"resolveJsonModule": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"module": "commonjs",
|
|
9
|
+
"moduleResolution": "node",
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"target": "es2015",
|
|
12
|
+
"lib": ["es2015", "dom"]
|
|
13
|
+
},
|
|
14
|
+
"files": ["src/index.ts"],
|
|
15
|
+
"include": ["src/**/*.ts", "src/**/*.json"],
|
|
16
|
+
"exclude": ["node_modules", "dist/*"]
|
|
17
|
+
}
|
|
18
|
+
|