functional-models 1.0.24 → 1.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functional-models",
3
- "version": "1.0.24",
3
+ "version": "1.0.28",
4
4
  "description": "A library for creating JavaScript function based models.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -49,6 +49,7 @@
49
49
  "sinon": "^11.1.2"
50
50
  },
51
51
  "dependencies": {
52
+ "async-lock": "^1.3.0",
52
53
  "get-random-values": "^1.2.2",
53
54
  "lodash": "^4.17.21"
54
55
  }
@@ -0,0 +1,19 @@
1
+ const { getObjToArray } = require('./utils')
2
+
3
+ const PROPERTY_TYPES = getObjToArray([
4
+ 'UniqueId',
5
+ 'DateProperty',
6
+ 'ArrayProperty',
7
+ 'ReferenceProperty',
8
+ 'IntegerProperty',
9
+ 'TextProperty',
10
+ 'ConstantValueProperty',
11
+ 'NumberProperty',
12
+ 'ObjectProperty',
13
+ 'EmailProperty',
14
+ 'BooleanProperty',
15
+ ])
16
+
17
+ module.exports = {
18
+ PROPERTY_TYPES,
19
+ }
package/src/errors.js ADDED
@@ -0,0 +1,16 @@
1
+ /* eslint-disable functional/no-this-expression */
2
+ /* eslint-disable functional/no-class */
3
+ class ValidationError extends Error {
4
+ constructor(modelName, keysToErrors) {
5
+ super(`${modelName} did not pass validation`)
6
+ this.name = 'ValidationError'
7
+ this.modelName = modelName
8
+ this.keysToErrors = keysToErrors
9
+ }
10
+ }
11
+ /* eslint-enable functional/no-this-expression */
12
+ /* eslint-enable functional/no-class */
13
+
14
+ module.exports = {
15
+ ValidationError,
16
+ }
package/src/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  module.exports = {
2
+ constants: require('./constants'),
2
3
  ...require('./properties'),
3
4
  ...require('./models'),
4
5
  ...require('./functions'),
6
+ errors: require('./errors'),
5
7
  validation: require('./validation'),
6
8
  serialization: require('./serialization'),
9
+ utils: require('./utils'),
7
10
  }
package/src/lazy.js CHANGED
@@ -1,15 +1,22 @@
1
+ const AsyncLock = require('async-lock')
2
+ const { createUuid } = require('./utils')
3
+
1
4
  const lazyValue = method => {
5
+ const key = createUuid()
6
+ const lock = new AsyncLock()
2
7
  /* eslint-disable functional/no-let */
3
8
  let value = undefined
4
9
  let called = false
5
10
  return async (...args) => {
6
- if (!called) {
7
- value = await method(...args)
8
- // eslint-disable-next-line require-atomic-updates
9
- called = true
10
- }
11
-
12
- return value
11
+ return lock.acquire(key, async () => {
12
+ if (!called) {
13
+ called = true
14
+ value = await method(...args)
15
+ // eslint-disable-next-line require-atomic-updates
16
+ }
17
+ }).then(() => {
18
+ return value
19
+ })
13
20
  }
14
21
  /* eslint-enable functional/no-let */
15
22
  }
package/src/models.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const merge = require('lodash/merge')
2
+ const get = require('lodash/get')
2
3
  const { toObj } = require('./serialization')
3
- const { createPropertyTitle } = require('./utils')
4
4
  const { createModelValidator } = require('./validation')
5
- const { UniqueId } = require('./properties')
5
+ const { UniqueId, createPropertyTitle } = require('./properties')
6
6
 
7
7
  const MODEL_DEF_KEYS = ['meta', 'functions']
8
8
 
@@ -65,12 +65,24 @@ const Model = (
65
65
  const fleshedOutInstanceProperties = {
66
66
  [getPropertyKey]: propertyGetter,
67
67
  functions: {
68
+ getters: {
69
+ [key]: propertyGetter,
70
+ },
68
71
  validators: {
69
72
  [key]: propertyValidator,
70
73
  },
71
74
  },
72
75
  }
73
- return merge(acc, fleshedOutInstanceProperties)
76
+ const referenceProperties = get(property, 'meta.getReferencedId')
77
+ ? {
78
+ meta: {
79
+ references: {
80
+ [createPropertyTitle(`${key}Id`)]: () => property.meta.getReferencedId(instanceValues[key])
81
+ }
82
+ }
83
+ }
84
+ : {}
85
+ return merge(acc, fleshedOutInstanceProperties, referenceProperties)
74
86
  },
75
87
  {}
76
88
  )
@@ -81,11 +93,11 @@ const Model = (
81
93
  functions: {
82
94
  toObj: toObj(loadedInternals),
83
95
  getPrimaryKey: loadedInternals[createPropertyTitle(primaryKey)],
84
- validate: () => {
96
+ validate: (options={}) => {
85
97
  return createModelValidator(
86
98
  loadedInternals,
87
99
  modelValidators
88
- )(instance)
100
+ )(instance, options)
89
101
  },
90
102
  },
91
103
  }
@@ -95,7 +107,7 @@ const Model = (
95
107
  return merge(acc, {
96
108
  functions: {
97
109
  [key]: (...args) => {
98
- return func(instance)(...args)
110
+ return func(...args, instance)
99
111
  },
100
112
  },
101
113
  })
package/src/properties.js CHANGED
@@ -13,8 +13,14 @@ const {
13
13
  referenceTypeMatch,
14
14
  meetsRegex,
15
15
  } = require('./validation')
16
- const { createUuid } = require('./utils')
16
+ const { PROPERTY_TYPES } = require('./constants')
17
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
+ }
18
24
 
19
25
  const EMAIL_REGEX =
20
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
@@ -30,21 +36,36 @@ const _mergeValidators = (config, validators) => {
30
36
  return [...validators, ...(config.validators ? config.validators : [])]
31
37
  }
32
38
 
33
- const Property = (config = {}, additionalMetadata = {}) => {
34
- const value = config.value || undefined
35
- const defaultValue = config.defaultValue || undefined
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 : []
36
49
  const lazyLoadMethod = config.lazyLoadMethod || false
37
50
  const valueSelector = config.valueSelector || identity
38
51
  if (typeof valueSelector !== 'function') {
39
52
  throw new Error(`valueSelector must be a function`)
40
53
  }
41
54
 
55
+
42
56
  return {
43
57
  ...additionalMetadata,
58
+ getConfig: () => config,
59
+ getChoices,
60
+ getDefaultValue,
61
+ getConstantValue,
62
+ getPropertyType: () => type,
44
63
  createGetter: instanceValue => {
64
+ const value = getConstantValue()
45
65
  if (value !== undefined) {
46
66
  return () => value
47
67
  }
68
+ const defaultValue = getDefaultValue()
48
69
  if (
49
70
  defaultValue !== undefined &&
50
71
  (instanceValue === null || instanceValue === undefined)
@@ -62,37 +83,25 @@ const Property = (config = {}, additionalMetadata = {}) => {
62
83
  },
63
84
  getValidator: valueGetter => {
64
85
  const validator = createPropertyValidator(config)
65
- const _propertyValidatorWrapper = async (instance, instanceData) => {
66
- return validator(await valueGetter(), instance, instanceData)
86
+ const _propertyValidatorWrapper = async (instance, instanceData, options={}) => {
87
+ return validator(await valueGetter(), instance, instanceData, options)
67
88
  }
68
89
  return _propertyValidatorWrapper
69
90
  },
70
91
  }
71
92
  }
72
93
 
73
- const UniqueId = (config = {}) =>
74
- Property({
75
- ...config,
76
- lazyLoadMethod: value => {
77
- if (!value) {
78
- return createUuid()
79
- }
80
- return value
81
- },
82
- })
83
-
84
- const DateProperty = (config = {}) =>
85
- Property({
86
- ...config,
87
- lazyLoadMethod: value => {
88
- if (!value && config.autoNow) {
89
- return new Date()
90
- }
91
- return value
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)
94
103
 
95
- const ReferenceProperty = (model, config = {}) => {
104
+ const ReferenceProperty = (model, config = {}, additionalMetadata={}) => {
96
105
  if (!model) {
97
106
  throw new Error('Must include the referenced model')
98
107
  }
@@ -106,22 +115,23 @@ const ReferenceProperty = (model, config = {}) => {
106
115
 
107
116
  const validators = _mergeValidators(config, [referenceTypeMatch(model)])
108
117
 
109
- const lazyLoadMethod = async instanceValues => {
110
- const _getId = () => {
111
- if (!instanceValues) {
112
- return null
113
- }
114
- const theModel = _getModel()
115
- const primaryKey = theModel.getPrimaryKeyName()
116
- if (instanceValues[primaryKey]) {
117
- return instanceValues[primaryKey]
118
- }
119
- const primaryKeyFunc = get(instanceValues, 'functions.getPrimaryKey')
120
- if (primaryKeyFunc) {
121
- return primaryKeyFunc()
122
- }
123
- return instanceValues
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]
124
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 => {
125
135
 
126
136
  const valueIsModelInstance =
127
137
  Boolean(instanceValues) && Boolean(instanceValues.functions)
@@ -135,7 +145,7 @@ const ReferenceProperty = (model, config = {}) => {
135
145
  : _getModel().create(objToUse)
136
146
  return merge({}, instance, {
137
147
  functions: {
138
- toObj: _getId,
148
+ toObj: _getId(instanceValues),
139
149
  },
140
150
  })
141
151
  }
@@ -144,43 +154,51 @@ const ReferenceProperty = (model, config = {}) => {
144
154
  return _getInstanceReturn(instanceValues)
145
155
  }
146
156
  if (config.fetcher) {
147
- const id = await _getId()
157
+ const id = await _getId(instanceValues)()
148
158
  const model = _getModel()
149
159
  const obj = await config.fetcher(model, id)
150
160
  return _getInstanceReturn(obj)
151
161
  }
152
- return _getId(instanceValues)
162
+ return _getId(instanceValues)()
153
163
  }
154
164
 
155
165
  return Property(
166
+ PROPERTY_TYPES.ReferenceProperty,
156
167
  merge({}, config, {
157
168
  validators,
158
169
  lazyLoadMethod,
159
170
  }),
160
171
  {
172
+ ...additionalMetadata,
161
173
  meta: {
174
+ getReferencedId: (instanceValues) => _getId(instanceValues)(),
162
175
  getReferencedModel: _getModel,
163
176
  },
164
177
  }
165
178
  )
166
179
  }
167
180
 
168
- const ArrayProperty = (config = {}) =>
169
- Property({
181
+ const ArrayProperty = (config = {}, additionalMetadata={}) =>
182
+ Property(
183
+ PROPERTY_TYPES.ArrayProperty,
184
+ {
170
185
  defaultValue: [],
171
186
  ...config,
172
187
  isArray: true,
173
- })
188
+ }, additionalMetadata)
174
189
 
175
- const ObjectProperty = (config = {}) =>
190
+ const ObjectProperty = (config = {}, additionalMetadata={}) =>
176
191
  Property(
192
+ PROPERTY_TYPES.ObjectProperty,
177
193
  merge(config, {
178
194
  validators: _mergeValidators(config, [isType('object')]),
179
- })
195
+ }),
196
+ additionalMetadata
180
197
  )
181
198
 
182
- const TextProperty = (config = {}) =>
199
+ const TextProperty = (config = {}, additionalMetadata={} ) =>
183
200
  Property(
201
+ PROPERTY_TYPES.TextProperty,
184
202
  merge(config, {
185
203
  isString: true,
186
204
  validators: _mergeValidators(config, [
@@ -191,11 +209,13 @@ const TextProperty = (config = {}) =>
191
209
  minTextLength(value)
192
210
  ),
193
211
  ]),
194
- })
212
+ }),
213
+ additionalMetadata
195
214
  )
196
215
 
197
- const IntegerProperty = (config = {}) =>
216
+ const IntegerProperty = (config = {}, additionalMetadata={}) =>
198
217
  Property(
218
+ PROPERTY_TYPES.IntegerProperty,
199
219
  merge(config, {
200
220
  isInteger: true,
201
221
  validators: _mergeValidators(config, [
@@ -206,11 +226,13 @@ const IntegerProperty = (config = {}) =>
206
226
  maxNumber(value)
207
227
  ),
208
228
  ]),
209
- })
229
+ }),
230
+ additionalMetadata
210
231
  )
211
232
 
212
- const NumberProperty = (config = {}) =>
233
+ const NumberProperty = (config = {}, additionalMetadata={}) =>
213
234
  Property(
235
+ PROPERTY_TYPES.NumberProperty,
214
236
  merge(config, {
215
237
  isNumber: true,
216
238
  validators: _mergeValidators(config, [
@@ -221,23 +243,59 @@ const NumberProperty = (config = {}) =>
221
243
  maxNumber(value)
222
244
  ),
223
245
  ]),
224
- })
246
+ }),
247
+ additionalMetadata
225
248
  )
226
249
 
227
- const ConstantValueProperty = (value, config = {}) =>
250
+ const ConstantValueProperty = (value, config = {}, additionalMetadata={}) =>
228
251
  TextProperty(
229
252
  merge(config, {
253
+ type: PROPERTY_TYPES.ConstantValueProperty,
230
254
  value,
231
- })
255
+ }),
256
+ additionalMetadata
232
257
  )
233
258
 
234
- const EmailProperty = (config = {}) =>
259
+ const EmailProperty = (config = {}, additionalMetadata={}) =>
235
260
  TextProperty(
236
261
  merge(config, {
262
+ type: PROPERTY_TYPES.EmailProperty,
237
263
  validators: _mergeValidators(config, [meetsRegex(EMAIL_REGEX)]),
238
- })
264
+ }),
265
+ additionalMetadata
239
266
  )
240
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
+
241
299
  module.exports = {
242
300
  Property,
243
301
  UniqueId,
@@ -250,4 +308,6 @@ module.exports = {
250
308
  NumberProperty,
251
309
  ObjectProperty,
252
310
  EmailProperty,
311
+ BooleanProperty,
312
+ createPropertyTitle,
253
313
  }
@@ -4,7 +4,10 @@ const SIZE_OF_GET = 'get'.length
4
4
  const IGNORABLE_KEYS = ['meta', 'functions']
5
5
 
6
6
  const _getValue = async value => {
7
- if (!value) {
7
+ if (value === undefined) {
8
+ return null
9
+ }
10
+ if (value === null) {
8
11
  return null
9
12
  }
10
13
  const type = typeof value
package/src/utils.js CHANGED
@@ -1,42 +1,52 @@
1
+ const keyBy = require('lodash/keyBy')
2
+
1
3
  const HEX = 16
2
4
  const FOUR = 4
3
5
  const FIFTEEN = 15
4
6
 
5
- const toTitleCase = string => {
6
- return `${string.slice(0, 1).toUpperCase()}${string.slice(1)}`
7
- }
8
-
9
- const createPropertyTitle = key => {
10
- const goodName = toTitleCase(key)
11
- return `get${goodName}`
12
- }
13
-
14
- const getCryptoRandomValues = () => {
7
+ const getRandomValues = () => {
8
+ const array = new Uint8Array(1)
15
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
16
  return (window.crypto || window.msCrypto).getRandomValues
17
17
  }
18
18
 
19
- return require('get-random-values')
19
+ return require('get-random-values')(array)
20
20
  }
21
21
 
22
22
  const createUuid = () => {
23
- const getRandomValues = getCryptoRandomValues()
24
23
  // eslint-disable-next-line no-magic-numbers,require-unicode-regexp
25
- return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
26
- (
27
- c ^
28
- (getRandomValues(new Uint8Array(1))[0] & (FIFTEEN >> (c / FOUR)))
29
- ).toString(HEX)
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
+ }
30
28
  )
31
29
  }
32
30
 
31
+ const toTitleCase = string => {
32
+ return `${string.slice(0, 1).toUpperCase()}${string.slice(1)}`
33
+ }
34
+
33
35
  const loweredTitleCase = string => {
34
36
  return `${string.slice(0, 1).toLowerCase()}${string.slice(1)}`
35
37
  }
36
38
 
39
+ const getObjToArray = array => {
40
+ const obj = keyBy(array)
41
+ return {
42
+ ...obj,
43
+ toArray: () => array,
44
+ }
45
+ }
46
+
37
47
  module.exports = {
38
- createUuid,
39
48
  loweredTitleCase,
40
- createPropertyTitle,
41
49
  toTitleCase,
50
+ createUuid,
51
+ getObjToArray,
42
52
  }
package/src/validation.js CHANGED
@@ -57,6 +57,15 @@ const arrayType = type => value => {
57
57
  }, undefined)
58
58
  }
59
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
+
60
69
  const meetsRegex =
61
70
  (regex, flags, errorMessage = 'Format was invalid') =>
62
71
  value => {
@@ -193,6 +202,7 @@ const CONFIG_TO_VALIDATE_METHOD = {
193
202
  isNumber: _boolChoice(isNumber),
194
203
  isString: _boolChoice(isString),
195
204
  isArray: _boolChoice(isArray),
205
+ isBoolean: _boolChoice(isBoolean),
196
206
  choices,
197
207
  }
198
208
 
@@ -208,18 +218,18 @@ const createPropertyValidator = config => {
208
218
  : validators.includes(isRequired)
209
219
  const validator =
210
220
  validators.length > 0 ? aggregateValidator(validators) : emptyValidator
211
- const _propertyValidator = async (value, instance, instanceData) => {
221
+ const _propertyValidator = async (value, instance, instanceData={}, options) => {
212
222
  if (!value && !isRequiredValue) {
213
223
  return []
214
224
  }
215
- const errors = await validator(value, instance, instanceData)
225
+ const errors = await validator(value, instance, instanceData, options)
216
226
  return [...new Set(flatMap(errors))]
217
227
  }
218
228
  return _propertyValidator
219
229
  }
220
230
 
221
231
  const createModelValidator = (properties, modelValidators = []) => {
222
- const _modelValidator = async instance => {
232
+ const _modelValidator = async (instance, options) => {
223
233
  if (!instance) {
224
234
  throw new Error(`Instance cannot be empty`)
225
235
  }
@@ -227,17 +237,17 @@ const createModelValidator = (properties, modelValidators = []) => {
227
237
  get(properties, 'functions.validators', {})
228
238
  )
229
239
  const instanceData = await instance.functions.toObj()
230
- const data = await Promise.all(
240
+ const propertyValidationErrors = await Promise.all(
231
241
  keysAndFunctions.map(async ([key, validator]) => {
232
- return [key, await validator(instance, instanceData)]
242
+ return [key, await validator(instance, instanceData, options)]
233
243
  })
234
244
  )
235
245
  const modelValidationErrors = (
236
246
  await Promise.all(
237
- modelValidators.map(validator => validator(instance, instanceData))
247
+ modelValidators.map(validator => validator(instance, instanceData, options))
238
248
  )
239
249
  ).filter(x => x)
240
- const propertyErrors = data
250
+ const propertyErrors = propertyValidationErrors
241
251
  .filter(([_, errors]) => Boolean(errors) && errors.length > 0)
242
252
  .reduce((acc, [key, errors]) => {
243
253
  return { ...acc, [key]: errors }
@@ -270,5 +280,6 @@ module.exports = {
270
280
  createModelValidator,
271
281
  arrayType,
272
282
  referenceTypeMatch,
283
+ multiplePropertiesMustMatch,
273
284
  TYPE_PRIMATIVES,
274
285
  }
@@ -2,7 +2,9 @@ const _ = require('lodash')
2
2
  const sinon = require('sinon')
3
3
  const assert = require('chai').assert
4
4
  const { Model } = require('../../src/models')
5
- const { Property } = require('../../src/properties')
5
+ const { Property, TextProperty, ReferenceProperty } = require('../../src/properties')
6
+
7
+ const TEST_MODEL_1 = Model('MyModel', )
6
8
 
7
9
  describe('/src/models.js', () => {
8
10
  describe('#Model()', () => {
@@ -12,11 +14,10 @@ describe('/src/models.js', () => {
12
14
  {},
13
15
  {
14
16
  instanceFunctions: {
15
- func1: instance => () => {
16
- console.log(instance)
17
+ func1: instance => {
17
18
  return instance.functions.func2()
18
19
  },
19
- func2: instance => () => {
20
+ func2: instance => {
20
21
  return 'from instance func2'
21
22
  },
22
23
  },
@@ -27,6 +28,25 @@ describe('/src/models.js', () => {
27
28
  const expected = 'from instance func2'
28
29
  assert.deepEqual(actual, expected)
29
30
  })
31
+ it('should the clients arguments before the model is passed', () => {
32
+ const model = Model(
33
+ 'ModelName',
34
+ {},
35
+ {
36
+ modelFunctions: {
37
+ func1: (model) => (input) => {
38
+ return `${input} ${model.func2()}`
39
+ },
40
+ func2: model => () => {
41
+ return 'from func2'
42
+ },
43
+ },
44
+ }
45
+ )
46
+ const actual = model.func1('hello')
47
+ const expected = 'hello from func2'
48
+ assert.deepEqual(actual, expected)
49
+ })
30
50
  it('should pass a functional model to the modelFunction by the time the function is called by a client', () => {
31
51
  const model = Model(
32
52
  'ModelName',
@@ -80,6 +100,16 @@ describe('/src/models.js', () => {
80
100
  })
81
101
  })
82
102
  describe('#create()', () => {
103
+ it('should have a meta.references.getTheReferenceId when the property has meta.getReferencedId and the key is theReference', () => {
104
+ const model = Model(
105
+ 'ModelName',
106
+ {
107
+ theReference: ReferenceProperty(TEST_MODEL_1),
108
+ },
109
+ )
110
+ const instance = model.create({})
111
+ assert.isFunction(instance.meta.references.getTheReferenceId)
112
+ })
83
113
  it('should have an "getId" field when no primaryKey is passed', () => {
84
114
  const model = Model(
85
115
  'ModelName',
@@ -212,7 +242,7 @@ describe('/src/models.js', () => {
212
242
  })
213
243
  it('should use the value for Property.value when even if Property.defaultValue is set and a value is passed in', async () => {
214
244
  const input = {
215
- myProperty: Property({
245
+ myProperty: Property('MyProperty', {
216
246
  value: 'value',
217
247
  defaultValue: 'default-value',
218
248
  }),
@@ -225,7 +255,7 @@ describe('/src/models.js', () => {
225
255
  })
226
256
  it('should use the value for Property.value when even if Property.defaultValue is not set and a value is passed in', async () => {
227
257
  const input = {
228
- myProperty: Property({ value: 'value' }),
258
+ myProperty: Property('MyProperty', { value: 'value' }),
229
259
  }
230
260
  const model = Model('name', input)
231
261
  const instance = model.create({ myProperty: 'passed-in' })
@@ -235,7 +265,7 @@ describe('/src/models.js', () => {
235
265
  })
236
266
  it('should use the value for Property.defaultValue when Property.value is not set and no value is passed in', async () => {
237
267
  const input = {
238
- myProperty: Property({ defaultValue: 'defaultValue' }),
268
+ myProperty: Property('MyProperty', { defaultValue: 'defaultValue' }),
239
269
  }
240
270
  const model = Model('name', input)
241
271
  const instance = model.create({})
@@ -245,7 +275,7 @@ describe('/src/models.js', () => {
245
275
  })
246
276
  it('should use the value for Property.defaultValue when Property.value is not set and null is passed as a value', async () => {
247
277
  const input = {
248
- myProperty: Property({ defaultValue: 'defaultValue' }),
278
+ myProperty: Property('MyProperty', { defaultValue: 'defaultValue' }),
249
279
  }
250
280
  const model = Model('name', input)
251
281
  const instance = model.create({ myProperty: null })
@@ -255,8 +285,8 @@ describe('/src/models.js', () => {
255
285
  })
256
286
  it('should return a model with getId and getType for the provided valid keyToProperty', () => {
257
287
  const input = {
258
- id: Property({ required: true }),
259
- type: Property(),
288
+ id: Property('MyProperty', { required: true }),
289
+ type: Property('MyProperty'),
260
290
  }
261
291
  const model = Model('name', input)
262
292
  const actual = model.create({ id: 'my-id', type: 'my-type' })
@@ -265,8 +295,8 @@ describe('/src/models.js', () => {
265
295
  })
266
296
  it('should return a model where validate returns one error for id', async () => {
267
297
  const input = {
268
- id: Property({ required: true }),
269
- type: Property(),
298
+ id: Property('MyProperty', { required: true }),
299
+ type: Property('MyProperty'),
270
300
  }
271
301
  const model = Model('name', input)
272
302
  const instance = model.create({ type: 'my-type' })
@@ -274,6 +304,17 @@ describe('/src/models.js', () => {
274
304
  const expected = 1
275
305
  assert.equal(Object.values(actual).length, expected)
276
306
  })
307
+ it('should return a model where validate returns one error for the missing text property', async () => {
308
+ const input = {
309
+ id: Property('MyProperty', { required: true }),
310
+ text: TextProperty({required: true}),
311
+ }
312
+ const model = Model('name', input)
313
+ const instance = model.create({ id: 'my-id' })
314
+ const actual = await instance.functions.validate()
315
+ const expected = 1
316
+ assert.equal(Object.values(actual).length, expected)
317
+ })
277
318
  })
278
319
  it('should return an object with a function "create" when called once with valid data', () => {
279
320
  const actual = Model('name', {})
@@ -284,6 +325,20 @@ describe('/src/models.js', () => {
284
325
  Model('name', { model: 'weeee' }).create()
285
326
  })
286
327
  })
328
+ describe('#meta.references.getMyReferencedId()', () => {
329
+ it('should return the id from the ReferenceProperty', () => {
330
+ const model = Model(
331
+ 'ModelName',
332
+ {
333
+ myReference: ReferenceProperty(TEST_MODEL_1),
334
+ },
335
+ )
336
+ const instance = model.create({ myReference: 'unit-test-id' })
337
+ const actual = instance.meta.references.getMyReferenceId()
338
+ const expected = 'unit-test-id'
339
+ assert.deepEqual(actual, expected)
340
+ })
341
+ })
287
342
  describe('#functions.getPrimaryKey()', () => {
288
343
  it('should return the id field when no primaryKey is passed', async () => {
289
344
  const model = Model(
@@ -373,34 +373,34 @@ describe('/src/properties.js', () => {
373
373
  describe('#Property()', () => {
374
374
  it('should throw an exception if config.valueSelector is not a function but is set', () => {
375
375
  assert.throws(() => {
376
- Property({ valueSelector: 'blah' })
376
+ Property('MyProperty', { valueSelector: 'blah' })
377
377
  })
378
378
  })
379
379
  it('should not throw an exception if config.valueSelector is a function', () => {
380
380
  assert.doesNotThrow(() => {
381
- Property({ valueSelector: () => ({}) })
381
+ Property('MyProperty', { valueSelector: () => ({}) })
382
382
  })
383
383
  })
384
384
  describe('#createGetter()', () => {
385
385
  it('should return a function even if config.value is set to a value', () => {
386
- const instance = Property({ value: 'my-value' })
386
+ const instance = Property('MyProperty', { value: 'my-value' })
387
387
  const actual = instance.createGetter('not-my-value')
388
388
  assert.isFunction(actual)
389
389
  })
390
390
  it('should return the value passed into config.value regardless of what is passed into the createGetter', async () => {
391
- const instance = Property({ value: 'my-value' })
391
+ const instance = Property('MyProperty', { value: 'my-value' })
392
392
  const actual = await instance.createGetter('not-my-value')()
393
393
  const expected = 'my-value'
394
394
  assert.deepEqual(actual, expected)
395
395
  })
396
396
  it('should return the value passed into createGetter when config.value is not set', async () => {
397
- const instance = Property()
397
+ const instance = Property('MyProperty')
398
398
  const actual = await instance.createGetter('my-value')()
399
399
  const expected = 'my-value'
400
400
  assert.deepEqual(actual, expected)
401
401
  })
402
402
  it('should return the value of the function passed into createGetter when config.value is not set', async () => {
403
- const instance = Property()
403
+ const instance = Property('MyProperty')
404
404
  const actual = await instance.createGetter(() => 'my-value')()
405
405
  const expected = 'my-value'
406
406
  assert.deepEqual(actual, expected)