functional-models 1.1.20 → 1.2.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 (65) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc +186 -0
  3. package/.github/workflows/feature.yml +26 -0
  4. package/.github/workflows/ut.yml +32 -0
  5. package/.prettierignore +7 -0
  6. package/.prettierrc.json +14 -0
  7. package/LICENSE +674 -0
  8. package/features/arrayFields.feature +52 -0
  9. package/features/functions.feature +11 -0
  10. package/features/model.feature +7 -0
  11. package/features/stepDefinitions/steps.js +193 -0
  12. package/features/validation.feature +12 -0
  13. package/index.js +1 -41
  14. package/package.json +10 -35
  15. package/src/constants.js +19 -0
  16. package/src/errors.js +16 -0
  17. package/src/functions.js +7 -0
  18. package/src/index.js +10 -0
  19. package/src/lazy.js +26 -0
  20. package/src/models.js +152 -0
  21. package/src/properties.js +313 -0
  22. package/src/serialization.js +50 -0
  23. package/src/utils.js +52 -0
  24. package/src/validation.js +285 -0
  25. package/test/base/index.test.js +5 -0
  26. package/test/src/functions.test.js +45 -0
  27. package/test/src/index.test.js +5 -0
  28. package/test/src/lazy.test.js +15 -0
  29. package/test/src/models.test.js +380 -0
  30. package/test/src/properties.test.js +554 -0
  31. package/test/src/serialization.test.js +127 -0
  32. package/test/src/utils.test.js +54 -0
  33. package/test/src/validation.test.js +614 -0
  34. package/constants.d.ts +0 -14
  35. package/constants.js +0 -19
  36. package/constants.js.map +0 -1
  37. package/errors.d.ts +0 -9
  38. package/errors.js +0 -15
  39. package/errors.js.map +0 -1
  40. package/index.d.ts +0 -10
  41. package/index.js.map +0 -1
  42. package/interfaces.d.ts +0 -160
  43. package/interfaces.js +0 -4
  44. package/interfaces.js.map +0 -1
  45. package/lazy.d.ts +0 -2
  46. package/lazy.js +0 -37
  47. package/lazy.js.map +0 -1
  48. package/methods.d.ts +0 -4
  49. package/methods.js +0 -18
  50. package/methods.js.map +0 -1
  51. package/models.d.ts +0 -3
  52. package/models.js +0 -129
  53. package/models.js.map +0 -1
  54. package/properties.d.ts +0 -16
  55. package/properties.js +0 -214
  56. package/properties.js.map +0 -1
  57. package/serialization.d.ts +0 -3
  58. package/serialization.js +0 -49
  59. package/serialization.js.map +0 -1
  60. package/utils.d.ts +0 -4
  61. package/utils.js +0 -44
  62. package/utils.js.map +0 -1
  63. package/validation.d.ts +0 -29
  64. package/validation.js +0 -292
  65. package/validation.js.map +0 -1
@@ -0,0 +1,313 @@
1
+ const identity = require('lodash/identity')
2
+ const get = require('lodash/get')
3
+ const isFunction = require('lodash/isFunction')
4
+ const merge = require('lodash/merge')
5
+ const {
6
+ createPropertyValidator,
7
+ emptyValidator,
8
+ maxTextLength,
9
+ minTextLength,
10
+ minNumber,
11
+ maxNumber,
12
+ isType,
13
+ referenceTypeMatch,
14
+ meetsRegex,
15
+ } = require('./validation')
16
+ const { PROPERTY_TYPES } = require('./constants')
17
+ const { lazyValue } = require('./lazy')
18
+ const { toTitleCase, createUuid } = require('./utils')
19
+
20
+ const createPropertyTitle = key => {
21
+ const goodName = toTitleCase(key)
22
+ return `get${goodName}`
23
+ }
24
+
25
+ const EMAIL_REGEX =
26
+ /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/u
27
+
28
+ const _getValidatorFromConfigElseEmpty = (config, key, validatorGetter) => {
29
+ if (key in config) {
30
+ return validatorGetter(config[key])
31
+ }
32
+ return emptyValidator
33
+ }
34
+
35
+ const _mergeValidators = (config, validators) => {
36
+ return [...validators, ...(config.validators ? config.validators : [])]
37
+ }
38
+
39
+ const Property = (type, config = {}, additionalMetadata = {}) => {
40
+ if (!type && !config.type) {
41
+ throw new Error(`Property type must be provided.`)
42
+ }
43
+ if (config.type) {
44
+ type = config.type
45
+ }
46
+ const getConstantValue = () => config.value !== undefined ? config.value : undefined
47
+ const getDefaultValue = () => config.defaultValue !== undefined ? config.defaultValue : undefined
48
+ const getChoices = () => config.choices ? config.choices : []
49
+ const lazyLoadMethod = config.lazyLoadMethod || false
50
+ const valueSelector = config.valueSelector || identity
51
+ if (typeof valueSelector !== 'function') {
52
+ throw new Error(`valueSelector must be a function`)
53
+ }
54
+
55
+
56
+ return {
57
+ ...additionalMetadata,
58
+ getConfig: () => config,
59
+ getChoices,
60
+ getDefaultValue,
61
+ getConstantValue,
62
+ getPropertyType: () => type,
63
+ createGetter: instanceValue => {
64
+ const value = getConstantValue()
65
+ if (value !== undefined) {
66
+ return () => value
67
+ }
68
+ const defaultValue = getDefaultValue()
69
+ if (
70
+ defaultValue !== undefined &&
71
+ (instanceValue === null || instanceValue === undefined)
72
+ ) {
73
+ return () => defaultValue
74
+ }
75
+ const method = lazyLoadMethod
76
+ ? lazyValue(lazyLoadMethod)
77
+ : typeof instanceValue === 'function'
78
+ ? instanceValue
79
+ : () => instanceValue
80
+ return async () => {
81
+ return valueSelector(await method(instanceValue))
82
+ }
83
+ },
84
+ getValidator: valueGetter => {
85
+ const validator = createPropertyValidator(config)
86
+ const _propertyValidatorWrapper = async (instance, instanceData, options={}) => {
87
+ return validator(await valueGetter(), instance, instanceData, options)
88
+ }
89
+ return _propertyValidatorWrapper
90
+ },
91
+ }
92
+ }
93
+
94
+ const DateProperty = (config = {}, additionalMetadata={}) => Property(PROPERTY_TYPES.DateProperty, {
95
+ ...config,
96
+ lazyLoadMethod: value => {
97
+ if (!value && config.autoNow) {
98
+ return new Date()
99
+ }
100
+ return value
101
+ },
102
+ }, additionalMetadata)
103
+
104
+ const ReferenceProperty = (model, config = {}, additionalMetadata={}) => {
105
+ if (!model) {
106
+ throw new Error('Must include the referenced model')
107
+ }
108
+
109
+ const _getModel = () => {
110
+ if (isFunction(model)) {
111
+ return model()
112
+ }
113
+ return model
114
+ }
115
+
116
+ const validators = _mergeValidators(config, [referenceTypeMatch(model)])
117
+
118
+ const _getId = (instanceValues) => () => {
119
+ if (!instanceValues) {
120
+ return null
121
+ }
122
+ const theModel = _getModel()
123
+ const primaryKey = theModel.getPrimaryKeyName()
124
+ if (instanceValues[primaryKey]) {
125
+ return instanceValues[primaryKey]
126
+ }
127
+ const primaryKeyFunc = get(instanceValues, 'functions.getPrimaryKey')
128
+ if (primaryKeyFunc) {
129
+ return primaryKeyFunc()
130
+ }
131
+ return instanceValues
132
+ }
133
+
134
+ const lazyLoadMethod = async instanceValues => {
135
+
136
+ const valueIsModelInstance =
137
+ Boolean(instanceValues) && Boolean(instanceValues.functions)
138
+
139
+ const _getInstanceReturn = objToUse => {
140
+ // We need to determine if the object we just go is an actual model instance to determine if we need to make one.
141
+ const objIsModelInstance =
142
+ Boolean(objToUse) && Boolean(objToUse.functions)
143
+ const instance = objIsModelInstance
144
+ ? objToUse
145
+ : _getModel().create(objToUse)
146
+ return merge({}, instance, {
147
+ functions: {
148
+ toObj: _getId(instanceValues),
149
+ },
150
+ })
151
+ }
152
+
153
+ if (valueIsModelInstance) {
154
+ return _getInstanceReturn(instanceValues)
155
+ }
156
+ if (config.fetcher) {
157
+ const id = await _getId(instanceValues)()
158
+ const model = _getModel()
159
+ const obj = await config.fetcher(model, id)
160
+ return _getInstanceReturn(obj)
161
+ }
162
+ return _getId(instanceValues)()
163
+ }
164
+
165
+ return Property(
166
+ PROPERTY_TYPES.ReferenceProperty,
167
+ merge({}, config, {
168
+ validators,
169
+ lazyLoadMethod,
170
+ }),
171
+ {
172
+ ...additionalMetadata,
173
+ meta: {
174
+ getReferencedId: (instanceValues) => _getId(instanceValues)(),
175
+ getReferencedModel: _getModel,
176
+ },
177
+ }
178
+ )
179
+ }
180
+
181
+ const ArrayProperty = (config = {}, additionalMetadata={}) =>
182
+ Property(
183
+ PROPERTY_TYPES.ArrayProperty,
184
+ {
185
+ defaultValue: [],
186
+ ...config,
187
+ isArray: true,
188
+ }, additionalMetadata)
189
+
190
+ const ObjectProperty = (config = {}, additionalMetadata={}) =>
191
+ Property(
192
+ PROPERTY_TYPES.ObjectProperty,
193
+ merge(config, {
194
+ validators: _mergeValidators(config, [isType('object')]),
195
+ }),
196
+ additionalMetadata
197
+ )
198
+
199
+ const TextProperty = (config = {}, additionalMetadata={} ) =>
200
+ Property(
201
+ PROPERTY_TYPES.TextProperty,
202
+ merge(config, {
203
+ isString: true,
204
+ validators: _mergeValidators(config, [
205
+ _getValidatorFromConfigElseEmpty(config, 'maxLength', value =>
206
+ maxTextLength(value)
207
+ ),
208
+ _getValidatorFromConfigElseEmpty(config, 'minLength', value =>
209
+ minTextLength(value)
210
+ ),
211
+ ]),
212
+ }),
213
+ additionalMetadata
214
+ )
215
+
216
+ const IntegerProperty = (config = {}, additionalMetadata={}) =>
217
+ Property(
218
+ PROPERTY_TYPES.IntegerProperty,
219
+ merge(config, {
220
+ isInteger: true,
221
+ validators: _mergeValidators(config, [
222
+ _getValidatorFromConfigElseEmpty(config, 'minValue', value =>
223
+ minNumber(value)
224
+ ),
225
+ _getValidatorFromConfigElseEmpty(config, 'maxValue', value =>
226
+ maxNumber(value)
227
+ ),
228
+ ]),
229
+ }),
230
+ additionalMetadata
231
+ )
232
+
233
+ const NumberProperty = (config = {}, additionalMetadata={}) =>
234
+ Property(
235
+ PROPERTY_TYPES.NumberProperty,
236
+ merge(config, {
237
+ isNumber: true,
238
+ validators: _mergeValidators(config, [
239
+ _getValidatorFromConfigElseEmpty(config, 'minValue', value =>
240
+ minNumber(value)
241
+ ),
242
+ _getValidatorFromConfigElseEmpty(config, 'maxValue', value =>
243
+ maxNumber(value)
244
+ ),
245
+ ]),
246
+ }),
247
+ additionalMetadata
248
+ )
249
+
250
+ const ConstantValueProperty = (value, config = {}, additionalMetadata={}) =>
251
+ TextProperty(
252
+ merge(config, {
253
+ type: PROPERTY_TYPES.ConstantValueProperty,
254
+ value,
255
+ }),
256
+ additionalMetadata
257
+ )
258
+
259
+ const EmailProperty = (config = {}, additionalMetadata={}) =>
260
+ TextProperty(
261
+ merge(config, {
262
+ type: PROPERTY_TYPES.EmailProperty,
263
+ validators: _mergeValidators(config, [meetsRegex(EMAIL_REGEX)]),
264
+ }),
265
+ additionalMetadata
266
+ )
267
+
268
+ const BooleanProperty = (config = {}, additionalMetadata={}) => Property(
269
+ PROPERTY_TYPES.BooleanProperty,
270
+ merge(config, {
271
+ isBoolean: true,
272
+ validators: _mergeValidators(config, [
273
+ _getValidatorFromConfigElseEmpty(config, 'minValue', value =>
274
+ minNumber(value)
275
+ ),
276
+ _getValidatorFromConfigElseEmpty(config, 'maxValue', value =>
277
+ maxNumber(value)
278
+ ),
279
+ ]),
280
+ }),
281
+ additionalMetadata
282
+ )
283
+
284
+ const UniqueId = (config = {}, additionalMetadata={}) =>
285
+ Property(
286
+ PROPERTY_TYPES.UniqueId,
287
+ {
288
+ ...config,
289
+ lazyLoadMethod: value => {
290
+ if (!value) {
291
+ return createUuid()
292
+ }
293
+ return value
294
+ },
295
+ }, additionalMetadata)
296
+
297
+
298
+
299
+ module.exports = {
300
+ Property,
301
+ UniqueId,
302
+ DateProperty,
303
+ ArrayProperty,
304
+ ReferenceProperty,
305
+ IntegerProperty,
306
+ TextProperty,
307
+ ConstantValueProperty,
308
+ NumberProperty,
309
+ ObjectProperty,
310
+ EmailProperty,
311
+ BooleanProperty,
312
+ createPropertyTitle,
313
+ }
@@ -0,0 +1,50 @@
1
+ const { loweredTitleCase } = require('./utils')
2
+
3
+ const SIZE_OF_GET = 'get'.length
4
+ const IGNORABLE_KEYS = ['meta', 'functions']
5
+
6
+ const _getValue = async value => {
7
+ if (value === undefined) {
8
+ return null
9
+ }
10
+ if (value === null) {
11
+ return null
12
+ }
13
+ const type = typeof value
14
+ if (type === 'function') {
15
+ return _getValue(await value())
16
+ }
17
+ // Nested Json
18
+ if (type === 'object' && value.functions && value.functions.toObj) {
19
+ return _getValue(await value.functions.toObj())
20
+ }
21
+ // Dates
22
+ if (type === 'object' && value.toISOString) {
23
+ return _getValue(value.toISOString())
24
+ }
25
+ return value
26
+ }
27
+
28
+ const _getKey = key => {
29
+ return key.startsWith('get') ? loweredTitleCase(key.slice(SIZE_OF_GET)) : key
30
+ }
31
+
32
+ const _shouldIgnoreKey = key => {
33
+ return IGNORABLE_KEYS.includes(key)
34
+ }
35
+
36
+ const toObj = keyToFunc => async () => {
37
+ return Object.entries(keyToFunc).reduce(async (acc, [key, value]) => {
38
+ const realAcc = await acc
39
+ if (_shouldIgnoreKey(key)) {
40
+ return realAcc
41
+ }
42
+ const keyToUse = _getKey(key)
43
+ const trueValue = await _getValue(value)
44
+ return { ...realAcc, [keyToUse]: trueValue }
45
+ }, Promise.resolve({}))
46
+ }
47
+
48
+ module.exports = {
49
+ toObj,
50
+ }
package/src/utils.js ADDED
@@ -0,0 +1,52 @@
1
+ const keyBy = require('lodash/keyBy')
2
+
3
+ const HEX = 16
4
+ const FOUR = 4
5
+ const FIFTEEN = 15
6
+
7
+ const getRandomValues = () => {
8
+ const array = new Uint8Array(1)
9
+ if (typeof window !== 'undefined') {
10
+ if (window.crypto) {
11
+ return window.crypto.getRandomValues(array)
12
+ }
13
+ if (window.msCrypto) {
14
+ window.msCrypto.getRandomValues(array)
15
+ }
16
+ return (window.crypto || window.msCrypto).getRandomValues
17
+ }
18
+
19
+ return require('get-random-values')(array)
20
+ }
21
+
22
+ const createUuid = () => {
23
+ // eslint-disable-next-line no-magic-numbers,require-unicode-regexp
24
+ return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => {
25
+ const value = getRandomValues()[0] & (FIFTEEN >> (c / FOUR))
26
+ return (c ^ value).toString(HEX)
27
+ }
28
+ )
29
+ }
30
+
31
+ const toTitleCase = string => {
32
+ return `${string.slice(0, 1).toUpperCase()}${string.slice(1)}`
33
+ }
34
+
35
+ const loweredTitleCase = string => {
36
+ return `${string.slice(0, 1).toLowerCase()}${string.slice(1)}`
37
+ }
38
+
39
+ const getObjToArray = array => {
40
+ const obj = keyBy(array)
41
+ return {
42
+ ...obj,
43
+ toArray: () => array,
44
+ }
45
+ }
46
+
47
+ module.exports = {
48
+ loweredTitleCase,
49
+ toTitleCase,
50
+ createUuid,
51
+ getObjToArray,
52
+ }
@@ -0,0 +1,285 @@
1
+ const isEmpty = require('lodash/isEmpty')
2
+ const merge = require('lodash/merge')
3
+ const isFunction = require('lodash/isFunction')
4
+ const flatMap = require('lodash/flatMap')
5
+ const get = require('lodash/get')
6
+
7
+ const TYPE_PRIMATIVES = {
8
+ boolean: 'boolean',
9
+ string: 'string',
10
+ object: 'object',
11
+ number: 'number',
12
+ integer: 'integer',
13
+ }
14
+
15
+ const _trueOrError = (method, error) => value => {
16
+ if (method(value) === false) {
17
+ return error
18
+ }
19
+ return undefined
20
+ }
21
+
22
+ const _typeOrError = (type, errorMessage) => value => {
23
+ if (typeof value !== type) {
24
+ return errorMessage
25
+ }
26
+ return undefined
27
+ }
28
+
29
+ const isType = type => value => {
30
+ return _typeOrError(type, `Must be a ${type}`)(value)
31
+ }
32
+ const isNumber = isType('number')
33
+ const isInteger = _trueOrError(Number.isInteger, 'Must be an integer')
34
+
35
+ const isBoolean = isType('boolean')
36
+ const isString = isType('string')
37
+ const isArray = _trueOrError(v => Array.isArray(v), 'Value is not an array')
38
+
39
+ const PRIMATIVE_TO_SPECIAL_TYPE_VALIDATOR = {
40
+ [TYPE_PRIMATIVES.boolean]: isBoolean,
41
+ [TYPE_PRIMATIVES.string]: isString,
42
+ [TYPE_PRIMATIVES.integer]: isInteger,
43
+ [TYPE_PRIMATIVES.number]: isNumber,
44
+ }
45
+
46
+ const arrayType = type => value => {
47
+ const arrayError = isArray(value)
48
+ if (arrayError) {
49
+ return arrayError
50
+ }
51
+ const validator = PRIMATIVE_TO_SPECIAL_TYPE_VALIDATOR[type] || isType(type)
52
+ return value.reduce((acc, v) => {
53
+ if (acc) {
54
+ return acc
55
+ }
56
+ return validator(v)
57
+ }, undefined)
58
+ }
59
+
60
+ const multiplePropertiesMustMatch = (getKeyA, getKeyB, errorMessage='Properties do not match') => async (_, instance) => {
61
+ const keyA = await getKeyA(instance)
62
+ const keyB = await getKeyB(instance)
63
+ if (keyA !== keyB) {
64
+ return errorMessage
65
+ }
66
+ return undefined
67
+ }
68
+
69
+ const meetsRegex =
70
+ (regex, flags, errorMessage = 'Format was invalid') =>
71
+ value => {
72
+ const reg = new RegExp(regex, flags)
73
+ return _trueOrError(v => reg.test(v), errorMessage)(value)
74
+ }
75
+
76
+ const choices = choiceArray => value => {
77
+ if (Array.isArray(value)) {
78
+ const bad = value.find(v => !choiceArray.includes(v))
79
+ if (bad) {
80
+ return `${bad} is not a valid choice`
81
+ }
82
+ } else {
83
+ if (choiceArray.includes(value) === false) {
84
+ return `${value} is not a valid choice`
85
+ }
86
+ }
87
+ return undefined
88
+ }
89
+
90
+ const isDate = value => {
91
+ if (!value) {
92
+ return 'Date value is empty'
93
+ }
94
+ if (!value.toISOString) {
95
+ return 'Value is not a date'
96
+ }
97
+ return undefined
98
+ }
99
+
100
+ const isRequired = value => {
101
+ if (value === true || value === false) {
102
+ return undefined
103
+ }
104
+ if (isNumber(value) === undefined) {
105
+ return undefined
106
+ }
107
+ const empty = isEmpty(value)
108
+ if (empty) {
109
+ if (isDate(value)) {
110
+ return 'A value is required'
111
+ }
112
+ }
113
+ return undefined
114
+ }
115
+
116
+ const maxNumber = max => value => {
117
+ const numberError = isNumber(value)
118
+ if (numberError) {
119
+ return numberError
120
+ }
121
+ if (value > max) {
122
+ return `The maximum is ${max}`
123
+ }
124
+ return undefined
125
+ }
126
+
127
+ const minNumber = min => value => {
128
+ const numberError = isNumber(value)
129
+ if (numberError) {
130
+ return numberError
131
+ }
132
+ if (value < min) {
133
+ return `The minimum is ${min}`
134
+ }
135
+ return undefined
136
+ }
137
+
138
+ const maxTextLength = max => value => {
139
+ const stringError = isString(value)
140
+ if (stringError) {
141
+ return stringError
142
+ }
143
+ if (value.length > max) {
144
+ return `The maximum length is ${max}`
145
+ }
146
+ return undefined
147
+ }
148
+
149
+ const minTextLength = min => value => {
150
+ const stringError = isString(value)
151
+ if (stringError) {
152
+ return stringError
153
+ }
154
+ if (value.length < min) {
155
+ return `The minimum length is ${min}`
156
+ }
157
+ return undefined
158
+ }
159
+
160
+ const referenceTypeMatch = referencedModel => {
161
+ return value => {
162
+ // This needs to stay here, as it delays the creation long enough for
163
+ // self referencing types.
164
+ const model = isFunction(referencedModel)
165
+ ? referencedModel()
166
+ : referencedModel
167
+ // Assumption: By the time this is received, value === a model instance.
168
+ const eModel = model.getName()
169
+ const aModel = value.meta.getModel().getName()
170
+ if (eModel !== aModel) {
171
+ return `Model should be ${eModel} instead, recieved ${aModel}`
172
+ }
173
+ return undefined
174
+ }
175
+ }
176
+
177
+ const aggregateValidator = methodOrMethods => {
178
+ const toDo = Array.isArray(methodOrMethods)
179
+ ? methodOrMethods
180
+ : [methodOrMethods]
181
+
182
+ const _aggregativeValidator = async (...args) => {
183
+ const values = await Promise.all(
184
+ toDo.map(method => {
185
+ return method(...args)
186
+ })
187
+ )
188
+ return values.filter(x => x)
189
+ }
190
+ return _aggregativeValidator
191
+ }
192
+
193
+ const emptyValidator = () => []
194
+
195
+ const _boolChoice = method => value => {
196
+ return value ? method : undefined
197
+ }
198
+
199
+ const CONFIG_TO_VALIDATE_METHOD = {
200
+ required: _boolChoice(isRequired),
201
+ isInteger: _boolChoice(isInteger),
202
+ isNumber: _boolChoice(isNumber),
203
+ isString: _boolChoice(isString),
204
+ isArray: _boolChoice(isArray),
205
+ isBoolean: _boolChoice(isBoolean),
206
+ choices,
207
+ }
208
+
209
+ const createPropertyValidator = config => {
210
+ const validators = [
211
+ ...Object.entries(config).map(([key, value]) => {
212
+ return (CONFIG_TO_VALIDATE_METHOD[key] || (() => undefined))(value)
213
+ }),
214
+ ...(config.validators ? config.validators : []),
215
+ ].filter(x => x)
216
+ const isRequiredValue = config.required
217
+ ? true
218
+ : validators.includes(isRequired)
219
+ const validator =
220
+ validators.length > 0 ? aggregateValidator(validators) : emptyValidator
221
+ const _propertyValidator = async (value, instance, instanceData={}, options) => {
222
+ if (!value && !isRequiredValue) {
223
+ return []
224
+ }
225
+ const errors = await validator(value, instance, instanceData, options)
226
+ return [...new Set(flatMap(errors))]
227
+ }
228
+ return _propertyValidator
229
+ }
230
+
231
+ const createModelValidator = (properties, modelValidators = []) => {
232
+ const _modelValidator = async (instance, options) => {
233
+ if (!instance) {
234
+ throw new Error(`Instance cannot be empty`)
235
+ }
236
+ const keysAndFunctions = Object.entries(
237
+ get(properties, 'functions.validators', {})
238
+ )
239
+ const instanceData = await instance.functions.toObj()
240
+ const propertyValidationErrors = await Promise.all(
241
+ keysAndFunctions.map(async ([key, validator]) => {
242
+ return [key, await validator(instance, instanceData, options)]
243
+ })
244
+ )
245
+ const modelValidationErrors = (
246
+ await Promise.all(
247
+ modelValidators.map(validator => validator(instance, instanceData, options))
248
+ )
249
+ ).filter(x => x)
250
+ const propertyErrors = propertyValidationErrors
251
+ .filter(([_, errors]) => Boolean(errors) && errors.length > 0)
252
+ .reduce((acc, [key, errors]) => {
253
+ return { ...acc, [key]: errors }
254
+ }, {})
255
+ return modelValidationErrors.length > 0
256
+ ? merge(propertyErrors, { overall: modelValidationErrors })
257
+ : propertyErrors
258
+ }
259
+ return _modelValidator
260
+ }
261
+
262
+ module.exports = {
263
+ isNumber,
264
+ isBoolean,
265
+ isString,
266
+ isInteger,
267
+ isType,
268
+ isDate,
269
+ isArray,
270
+ isRequired,
271
+ maxNumber,
272
+ minNumber,
273
+ choices,
274
+ maxTextLength,
275
+ minTextLength,
276
+ meetsRegex,
277
+ aggregateValidator,
278
+ emptyValidator,
279
+ createPropertyValidator,
280
+ createModelValidator,
281
+ arrayType,
282
+ referenceTypeMatch,
283
+ multiplePropertiesMustMatch,
284
+ TYPE_PRIMATIVES,
285
+ }
@@ -0,0 +1,5 @@
1
+ describe('/index.js', () => {
2
+ it('should load without exception', () => {
3
+ require('../../index')
4
+ })
5
+ })