Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions features/model.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Feature: Models

Scenario: A Model With a 4 fields
Given TestModel1 is used
When TestModel1b data is inserted
Then TestModel1b expected fields are found

47 changes: 39 additions & 8 deletions features/stepDefinitions/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ const assert = require('chai').assert
const flatMap = require('lodash/flatMap')
const { Given, When, Then } = require('@cucumber/cucumber')

const { smartObject, property, named, typed } = require('../../index')
const { createModel, field } = require('../../index')

const MODEL_DEFINITIONS = {
TestModel1: ({ name, type, flag }) =>
smartObject([
named({ required: true })(name),
typed({ required: true, isString: 'true' })(type),
property('flag', { required: true, isNumber: true })(flag),
]),
TestModel1: createModel({
name: field({ required: true }),
type: field({ required: true, isString: true }),
flag: field({ required: true, isNumber: true }),
}),
}

const MODEL_INPUT_VALUES = {
Expand All @@ -26,6 +25,10 @@ const MODEL_INPUT_VALUES = {
},
}

const EXPECTED_FIELDS = {
TestModel1b: ['getName', 'getType', 'getFlag', 'meta', 'functions'],
}

Given(
'the {word} has been created, with {word} inputs provided',
function (modelDefinition, modelInputValues) {
Expand All @@ -42,7 +45,7 @@ Given(
)

When('functions.validate is called', function () {
return this.instance.functions.validate.object().then(x => {
return this.instance.functions.validate.model().then(x => {
this.errors = x
})
})
Expand All @@ -54,3 +57,31 @@ Then('an array of {int} errors is shown', function (errorCount) {
}
assert.equal(errors.length, errorCount)
})

Given('{word} is used', function (modelDefinition) {
const def = MODEL_DEFINITIONS[modelDefinition]
if (!def) {
throw new Error(`${modelDefinition} did not result in a definition`)
}
this.modelDefinition = def
})

When('{word} data is inserted', function (modelInputValues) {
const input = MODEL_INPUT_VALUES[modelInputValues]
if (!input) {
throw new Error(`${modelInputValues} did not result in an input`)
}
this.instance = this.modelDefinition(input)
})

Then('{word} expected fields are found', function (fields) {
const propertyArray = EXPECTED_FIELDS[fields]
if (!propertyArray) {
throw new Error(`${fields} did not result in fields`)
}
propertyArray.forEach(key => {
if (!(key in this.instance)) {
throw new Error(`Did not find ${key} in model`)
}
})
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "functional-models",
"version": "1.0.2",
"version": "1.0.5",
"description": "A library for creating JavaScript function based models.",
"main": "index.js",
"scripts": {
Expand Down
13 changes: 0 additions & 13 deletions src/dates.js

This file was deleted.

106 changes: 106 additions & 0 deletions src/fields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const identity = require('lodash/identity')
const { createFieldValidator } = require('./validation')
const { createUuid } = require('./utils')
const { lazyValue } = require('./lazy')

const field = (config = {}) => {
const value = config.value || undefined
const defaultValue = config.defaultValue || undefined
const lazyLoadMethod = config.lazyLoadMethod || false
const valueSelector = config.valueSelector || identity
if (typeof valueSelector !== 'function') {
throw new Error(`valueSelector must be a function`)
}

return {
createGetter: instanceValue => {
if (value !== undefined) {
return () => value
}
if (
defaultValue !== undefined &&
(instanceValue === null || instanceValue === undefined)
) {
return () => defaultValue
}
const method = lazyLoadMethod
? lazyValue(lazyLoadMethod)
: typeof instanceValue === 'function'
? instanceValue
: () => instanceValue
return async () => {
return valueSelector(await method(instanceValue))
}
},
getValidator: valueGetter => {
return async () => {
return createFieldValidator(config)(await valueGetter())
}
},
}
}

const uniqueId = config =>
field({
...config,
lazyLoadMethod: value => {
if (!value) {
return createUuid()
}
return value
},
})

const dateField = config =>
field({
...config,
lazyLoadMethod: value => {
if (!value && config.autoNow) {
return new Date()
}
return value
},
})

const referenceField = config => {
return field({
...config,
lazyLoadMethod: async smartObj => {
const _getId = () => {
if (!smartObj) {
return null
}
return smartObj && smartObj.id
? smartObj.id
: smartObj.getId
? smartObj.getId()
: smartObj
}
const _getSmartObjReturn = objToUse => {
return {
...objToUse,
functions: {
...(objToUse.functions ? objToUse.functions : {}),
toJson: _getId,
},
}
}
const valueIsSmartObj = smartObj && smartObj.functions
if (valueIsSmartObj) {
return _getSmartObjReturn(smartObj)
}
if (config.fetcher) {
const obj = await config.fetcher(smartObj)
return _getSmartObjReturn(obj)
}
return _getId(smartObj)
},
})
}

module.exports = {
field,
uniqueId,
dateField,
referenceField,
}
8 changes: 3 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module.exports = {
...require('./dates'),
...require('./lazy'),
...require('./objects'),
...require('./properties'),
...require('./references'),
...require('./fields'),
...require('./models'),
validation: require('./validation'),
serialization: require('./serialization'),
}
23 changes: 13 additions & 10 deletions src/lazy.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
const { lazyValue, createPropertyTitle } = require('./utils')
const lazyValue = method => {
/* eslint-disable functional/no-let */
let value = undefined
let called = false
return async (...args) => {
if (!called) {
value = await method(...args)
// eslint-disable-next-line require-atomic-updates
called = true
}

const lazyProperty = (key, method, { selector = null } = {}) => {
const lazy = lazyValue(method)
const propertyKey = createPropertyTitle(key)
return {
[propertyKey]: async () => {
const value = await lazy()
return selector ? selector(value) : value
},
return value
}
/* eslint-enable functional/no-let */
}

module.exports = {
lazyProperty,
lazyValue,
}
55 changes: 55 additions & 0 deletions src/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const merge = require('lodash/merge')
const get = require('lodash/get')
const { toJson } = require('./serialization')
const { createPropertyTitle } = require('./utils')
const { createModelValidator } = require('./validation')

const SYSTEM_KEYS = ['meta', 'functions']

const PROTECTED_KEYS = ['model']

const createModel = keyToField => {
PROTECTED_KEYS.forEach(key => {
if (key in keyToField) {
throw new Error(`Cannot use ${key}. This is a protected value.`)
}
})
const systemProperties = SYSTEM_KEYS.reduce((acc, key) => {
const value = get(keyToField, key, {})
return { ...acc, [key]: value }
}, {})
const nonSystemProperties = Object.entries(keyToField).filter(
([key, _]) => !(key in SYSTEM_KEYS)
)

return instanceValues => {
const loadedInternals = nonSystemProperties.reduce((acc, [key, field]) => {
const fieldGetter = field.createGetter(instanceValues[key])
const fieldValidator = field.getValidator(fieldGetter)
const getFieldKey = createPropertyTitle(key)
const fleshedOutField = {
[getFieldKey]: fieldGetter,
functions: {
validate: {
[key]: fieldValidator,
},
},
}
return merge(acc, fleshedOutField)
}, {})
const allUserData = merge(systemProperties, loadedInternals)
const internalFunctions = {
functions: {
toJson: toJson(loadedInternals),
validate: {
model: createModelValidator(loadedInternals),
},
},
}
return merge(allUserData, internalFunctions)
}
}

module.exports = {
createModel,
}
49 changes: 0 additions & 49 deletions src/objects.js

This file was deleted.

23 changes: 0 additions & 23 deletions src/properties.js

This file was deleted.

Loading