functional-models 1.0.25 → 1.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/.eslintignore +1 -0
- package/.eslintrc +9 -15
- package/cucumber.js +10 -0
- package/features/arrayFields.feature +7 -7
- package/features/basic-ts.feature +13 -0
- package/features/functions.feature +2 -3
- package/package.json +34 -10
- package/src/constants.ts +15 -0
- package/src/errors.ts +18 -0
- package/src/index.ts +11 -0
- package/src/interfaces.ts +323 -0
- package/src/lazy.ts +24 -0
- package/src/methods.ts +30 -0
- package/src/models.ts +183 -0
- package/src/properties.ts +375 -0
- package/src/serialization.ts +39 -0
- package/src/utils.ts +42 -0
- package/src/validation.ts +390 -0
- package/{features/stepDefinitions/steps.js → stepDefinitions/oldSteps.ts} +76 -53
- package/stepDefinitions/tssteps.ts +107 -0
- package/test/src/errors.test.ts +31 -0
- package/test/src/{lazy.test.js → lazy.test.ts} +4 -4
- package/test/src/methods.test.ts +45 -0
- package/test/src/models.test.ts +417 -0
- package/test/src/{properties.test.js → properties.test.ts} +257 -64
- package/test/src/serialization.test.ts +80 -0
- package/test/src/{utils.test.js → utils.test.ts} +29 -7
- package/test/src/{validation.test.js → validation.test.ts} +278 -210
- package/tsconfig.json +100 -0
- package/src/functions.js +0 -7
- package/src/index.js +0 -7
- package/src/lazy.js +0 -19
- package/src/models.js +0 -150
- package/src/properties.js +0 -261
- package/src/serialization.js +0 -47
- package/src/utils.js +0 -42
- package/src/validation.js +0 -274
- package/test/base/index.test.js +0 -5
- package/test/src/functions.test.js +0 -45
- package/test/src/index.test.js +0 -5
- package/test/src/models.test.js +0 -380
- package/test/src/serialization.test.js +0 -127
package/src/models.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import merge from 'lodash/merge'
|
|
2
|
+
import { toJsonAble } from './serialization'
|
|
3
|
+
import { createModelValidator } from './validation'
|
|
4
|
+
import { UniqueId } from './properties'
|
|
5
|
+
import {
|
|
6
|
+
Model,
|
|
7
|
+
InstanceMethodGetters,
|
|
8
|
+
Nullable,
|
|
9
|
+
ModelDefinition,
|
|
10
|
+
ModelInstance,
|
|
11
|
+
ModelOptions,
|
|
12
|
+
ReferenceFunctions,
|
|
13
|
+
PropertyGetters,
|
|
14
|
+
OptionalModelOptions,
|
|
15
|
+
ReferencePropertyInstance,
|
|
16
|
+
ReferenceValueType,
|
|
17
|
+
PropertyValidators,
|
|
18
|
+
FunctionalModel,
|
|
19
|
+
ModelInstanceInputData,
|
|
20
|
+
ModelInstanceMethodTyped,
|
|
21
|
+
ModelMethodTyped,
|
|
22
|
+
ModelMethodGetters,
|
|
23
|
+
} from './interfaces'
|
|
24
|
+
|
|
25
|
+
const _defaultOptions = (): ModelOptions => ({
|
|
26
|
+
instanceCreatedCallback: null,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const _convertOptions = (options?: OptionalModelOptions) => {
|
|
30
|
+
const r: ModelOptions = merge({}, _defaultOptions(), options)
|
|
31
|
+
return r
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const _createModelDefWithPrimaryKey = <T extends FunctionalModel>(
|
|
35
|
+
keyToProperty: ModelDefinition<T>
|
|
36
|
+
): ModelDefinition<T> => {
|
|
37
|
+
return {
|
|
38
|
+
getPrimaryKeyName: () => 'id',
|
|
39
|
+
modelMethods: keyToProperty.modelMethods,
|
|
40
|
+
instanceMethods: keyToProperty.instanceMethods,
|
|
41
|
+
properties: {
|
|
42
|
+
id: UniqueId({ required: true }),
|
|
43
|
+
...keyToProperty.properties,
|
|
44
|
+
},
|
|
45
|
+
modelValidators: keyToProperty.modelValidators,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const BaseModel = <T extends FunctionalModel>(
|
|
50
|
+
modelName: string,
|
|
51
|
+
modelDefinition: ModelDefinition<T>,
|
|
52
|
+
//keyToProperty: IModelDefinition2<T>,
|
|
53
|
+
options?: OptionalModelOptions
|
|
54
|
+
) => {
|
|
55
|
+
/*
|
|
56
|
+
* This non-functional approach is specifically used to
|
|
57
|
+
* allow instances to be able to refer back to its parent without
|
|
58
|
+
* having to duplicate it for every instance.
|
|
59
|
+
* This is set at the very end and returned, so it can be referenced
|
|
60
|
+
* throughout instance methods.
|
|
61
|
+
*/
|
|
62
|
+
// eslint-disable-next-line functional/no-let
|
|
63
|
+
let model: Nullable<Model<T>> = null
|
|
64
|
+
const theOptions = _convertOptions(options)
|
|
65
|
+
modelDefinition = !modelDefinition.getPrimaryKeyName
|
|
66
|
+
? _createModelDefWithPrimaryKey(modelDefinition)
|
|
67
|
+
: modelDefinition
|
|
68
|
+
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
const getPrimaryKeyName = () => modelDefinition.getPrimaryKeyName()
|
|
71
|
+
const getPrimaryKey = (t: ModelInstanceInputData<T>) =>
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
t[getPrimaryKeyName()] as string
|
|
74
|
+
|
|
75
|
+
const create = (instanceValues: ModelInstanceInputData<T>) => {
|
|
76
|
+
// eslint-disable-next-line functional/no-let
|
|
77
|
+
let instance: Nullable<ModelInstance<T>> = null
|
|
78
|
+
const startingInternals: {
|
|
79
|
+
readonly get: PropertyGetters<T> & { readonly id: () => string }
|
|
80
|
+
readonly validators: PropertyValidators
|
|
81
|
+
readonly references: ReferenceFunctions
|
|
82
|
+
} = {
|
|
83
|
+
get: {} as PropertyGetters<T> & { readonly id: () => string },
|
|
84
|
+
validators: {},
|
|
85
|
+
references: {},
|
|
86
|
+
}
|
|
87
|
+
const loadedInternals = Object.entries(modelDefinition.properties).reduce(
|
|
88
|
+
(acc, [key, property]) => {
|
|
89
|
+
// @ts-ignore
|
|
90
|
+
const propertyGetter = property.createGetter(instanceValues[key])
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
const propertyValidator = property.getValidator(propertyGetter)
|
|
93
|
+
const fleshedOutInstanceProperties = {
|
|
94
|
+
get: {
|
|
95
|
+
[key]: propertyGetter,
|
|
96
|
+
},
|
|
97
|
+
validators: {
|
|
98
|
+
[key]: propertyValidator,
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
const asReferenced = property as ReferencePropertyInstance<any>
|
|
102
|
+
const referencedProperty = asReferenced.getReferencedId
|
|
103
|
+
? {
|
|
104
|
+
references: {
|
|
105
|
+
[key]: () =>
|
|
106
|
+
asReferenced.getReferencedId(
|
|
107
|
+
// @ts-ignore
|
|
108
|
+
instanceValues[key] as ReferenceValueType<any>
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
: {}
|
|
113
|
+
|
|
114
|
+
return merge(acc, fleshedOutInstanceProperties, referencedProperty)
|
|
115
|
+
},
|
|
116
|
+
startingInternals
|
|
117
|
+
)
|
|
118
|
+
const methods = Object.entries(
|
|
119
|
+
modelDefinition.instanceMethods || {}
|
|
120
|
+
).reduce((acc, [key, func]) => {
|
|
121
|
+
return merge(acc, {
|
|
122
|
+
[key]: (...args: readonly any[]) => {
|
|
123
|
+
return (func as ModelInstanceMethodTyped<T>)(
|
|
124
|
+
instance as ModelInstance<T>,
|
|
125
|
+
...args
|
|
126
|
+
)
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
}, {}) as InstanceMethodGetters<T>
|
|
130
|
+
|
|
131
|
+
const getModel = () => model as Model<T>
|
|
132
|
+
const toObj = toJsonAble(loadedInternals.get)
|
|
133
|
+
const validate = (options = {}) => {
|
|
134
|
+
return createModelValidator(
|
|
135
|
+
loadedInternals.validators,
|
|
136
|
+
modelDefinition.modelValidators || []
|
|
137
|
+
)(instance as ModelInstance<T>, options)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
instance = merge(loadedInternals, {
|
|
141
|
+
getModel,
|
|
142
|
+
toObj,
|
|
143
|
+
getPrimaryKey: () => getPrimaryKey(instanceValues),
|
|
144
|
+
getPrimaryKeyName,
|
|
145
|
+
validate,
|
|
146
|
+
methods,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
if (theOptions.instanceCreatedCallback) {
|
|
150
|
+
const toCall = Array.isArray(theOptions.instanceCreatedCallback)
|
|
151
|
+
? theOptions.instanceCreatedCallback
|
|
152
|
+
: [theOptions.instanceCreatedCallback]
|
|
153
|
+
toCall.map(func => func(instance as ModelInstance<T>))
|
|
154
|
+
}
|
|
155
|
+
return instance
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const fleshedOutModelFunctions = Object.entries(
|
|
159
|
+
modelDefinition.modelMethods || {}
|
|
160
|
+
).reduce((acc, [key, func]) => {
|
|
161
|
+
return merge(acc, {
|
|
162
|
+
[key]: (...args: readonly any[]) => {
|
|
163
|
+
return (func as ModelMethodTyped<T>)(model as Model<T>, ...args)
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
}, {}) as ModelMethodGetters<T>
|
|
167
|
+
|
|
168
|
+
// This sets the model that is used by the instances later.
|
|
169
|
+
model = merge(
|
|
170
|
+
{},
|
|
171
|
+
{
|
|
172
|
+
create,
|
|
173
|
+
getName: () => modelName,
|
|
174
|
+
getModelDefinition: () => modelDefinition,
|
|
175
|
+
getPrimaryKeyName,
|
|
176
|
+
getPrimaryKey,
|
|
177
|
+
methods: fleshedOutModelFunctions,
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
return model as Model<T>
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export { BaseModel }
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import identity from 'lodash/identity'
|
|
2
|
+
import merge from 'lodash/merge'
|
|
3
|
+
import {
|
|
4
|
+
createPropertyValidator,
|
|
5
|
+
emptyValidator,
|
|
6
|
+
maxTextLength,
|
|
7
|
+
minTextLength,
|
|
8
|
+
minNumber,
|
|
9
|
+
maxNumber,
|
|
10
|
+
isType,
|
|
11
|
+
referenceTypeMatch,
|
|
12
|
+
meetsRegex,
|
|
13
|
+
} from './validation'
|
|
14
|
+
import { PROPERTY_TYPES } from './constants'
|
|
15
|
+
import { lazyValue } from './lazy'
|
|
16
|
+
import { createUuid } from './utils'
|
|
17
|
+
import {
|
|
18
|
+
ReferenceValueType,
|
|
19
|
+
ModelInstance,
|
|
20
|
+
MaybeEmpty,
|
|
21
|
+
Nullable,
|
|
22
|
+
PrimaryKeyType,
|
|
23
|
+
Model,
|
|
24
|
+
PropertyInstance,
|
|
25
|
+
FunctionalType,
|
|
26
|
+
PropertyConfig,
|
|
27
|
+
ValueGetter,
|
|
28
|
+
MaybeFunction,
|
|
29
|
+
Arrayable,
|
|
30
|
+
PropertyValidatorComponent,
|
|
31
|
+
PropertyValidator,
|
|
32
|
+
ReferencePropertyInstance,
|
|
33
|
+
ModelInstanceInputData,
|
|
34
|
+
FunctionalModel,
|
|
35
|
+
JsonAble,
|
|
36
|
+
} from './interfaces'
|
|
37
|
+
|
|
38
|
+
const EMAIL_REGEX =
|
|
39
|
+
/[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
|
|
40
|
+
|
|
41
|
+
function _getValidatorFromConfigElseEmpty<T>(
|
|
42
|
+
input: T | undefined,
|
|
43
|
+
// eslint-disable-next-line no-unused-vars
|
|
44
|
+
validatorGetter: (t: T) => PropertyValidatorComponent
|
|
45
|
+
) {
|
|
46
|
+
if (input !== undefined) {
|
|
47
|
+
const validator = validatorGetter(input)
|
|
48
|
+
return validator
|
|
49
|
+
}
|
|
50
|
+
return emptyValidator
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const _mergeValidators = (
|
|
54
|
+
config: PropertyConfig | undefined,
|
|
55
|
+
validators: readonly PropertyValidatorComponent[]
|
|
56
|
+
) => {
|
|
57
|
+
return [...validators, ...(config?.validators ? config.validators : [])]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function Property<T extends Arrayable<FunctionalType>>(
|
|
61
|
+
type: string,
|
|
62
|
+
config: PropertyConfig = {},
|
|
63
|
+
additionalMetadata = {}
|
|
64
|
+
) {
|
|
65
|
+
if (!type && !config?.type) {
|
|
66
|
+
throw new Error(`Property type must be provided.`)
|
|
67
|
+
}
|
|
68
|
+
if (config?.type) {
|
|
69
|
+
type = config.type
|
|
70
|
+
}
|
|
71
|
+
const getConstantValue = () =>
|
|
72
|
+
(config?.value !== undefined ? config.value : undefined) as T
|
|
73
|
+
const getDefaultValue = () =>
|
|
74
|
+
(config?.defaultValue !== undefined ? config.defaultValue : undefined) as T
|
|
75
|
+
const getChoices = () => config?.choices || []
|
|
76
|
+
const lazyLoadMethod = config?.lazyLoadMethod || false
|
|
77
|
+
const valueSelector = config?.valueSelector || identity
|
|
78
|
+
if (typeof valueSelector !== 'function') {
|
|
79
|
+
throw new Error(`valueSelector must be a function`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const r: PropertyInstance<T> = {
|
|
83
|
+
...additionalMetadata,
|
|
84
|
+
getConfig: () => config || {},
|
|
85
|
+
getChoices,
|
|
86
|
+
getDefaultValue,
|
|
87
|
+
getConstantValue,
|
|
88
|
+
getPropertyType: () => type,
|
|
89
|
+
createGetter: (instanceValue: T): ValueGetter => {
|
|
90
|
+
const value = getConstantValue()
|
|
91
|
+
if (value !== undefined) {
|
|
92
|
+
return () => value
|
|
93
|
+
}
|
|
94
|
+
const defaultValue = getDefaultValue()
|
|
95
|
+
if (
|
|
96
|
+
defaultValue !== undefined &&
|
|
97
|
+
(instanceValue === null || instanceValue === undefined)
|
|
98
|
+
) {
|
|
99
|
+
return () => defaultValue
|
|
100
|
+
}
|
|
101
|
+
const method = lazyLoadMethod
|
|
102
|
+
? // eslint-disable-next-line no-unused-vars
|
|
103
|
+
(lazyValue(lazyLoadMethod) as (value: T) => Promise<T>)
|
|
104
|
+
: typeof instanceValue === 'function'
|
|
105
|
+
? (instanceValue as () => T)
|
|
106
|
+
: () => instanceValue
|
|
107
|
+
return () => {
|
|
108
|
+
return valueSelector(method(instanceValue))
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
getValidator: valueGetter => {
|
|
112
|
+
const validator = createPropertyValidator(valueGetter, config)
|
|
113
|
+
const _propertyValidatorWrapper: PropertyValidator = async (
|
|
114
|
+
instance,
|
|
115
|
+
instanceData
|
|
116
|
+
) => {
|
|
117
|
+
return validator(instance, instanceData)
|
|
118
|
+
}
|
|
119
|
+
return _propertyValidatorWrapper
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
return r
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const DateProperty = (config: PropertyConfig = {}, additionalMetadata = {}) =>
|
|
126
|
+
Property<MaybeEmpty<Date>>(
|
|
127
|
+
PROPERTY_TYPES.DateProperty,
|
|
128
|
+
merge(
|
|
129
|
+
{
|
|
130
|
+
lazyLoadMethod: (value: Arrayable<FunctionalType>) => {
|
|
131
|
+
if (!value && config?.autoNow) {
|
|
132
|
+
return new Date()
|
|
133
|
+
}
|
|
134
|
+
return value
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
config
|
|
138
|
+
),
|
|
139
|
+
additionalMetadata
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
const ArrayProperty = <T extends FunctionalType>(
|
|
143
|
+
config = {},
|
|
144
|
+
additionalMetadata = {}
|
|
145
|
+
) =>
|
|
146
|
+
Property<readonly T[]>(
|
|
147
|
+
PROPERTY_TYPES.ArrayProperty,
|
|
148
|
+
{
|
|
149
|
+
defaultValue: [],
|
|
150
|
+
...config,
|
|
151
|
+
isArray: true,
|
|
152
|
+
},
|
|
153
|
+
additionalMetadata
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
const ObjectProperty = (config = {}, additionalMetadata = {}) =>
|
|
157
|
+
Property<{ readonly [s: string]: JsonAble }>(
|
|
158
|
+
PROPERTY_TYPES.ObjectProperty,
|
|
159
|
+
merge(config, {
|
|
160
|
+
validators: _mergeValidators(config, [isType('object')]),
|
|
161
|
+
}),
|
|
162
|
+
additionalMetadata
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const TextProperty = (config: PropertyConfig = {}, additionalMetadata = {}) =>
|
|
166
|
+
Property<MaybeEmpty<string>>(
|
|
167
|
+
PROPERTY_TYPES.TextProperty,
|
|
168
|
+
merge(config, {
|
|
169
|
+
isString: true,
|
|
170
|
+
validators: _mergeValidators(config, [
|
|
171
|
+
_getValidatorFromConfigElseEmpty(config?.maxLength, (value: number) =>
|
|
172
|
+
maxTextLength(value)
|
|
173
|
+
),
|
|
174
|
+
_getValidatorFromConfigElseEmpty(config?.minLength, (value: number) =>
|
|
175
|
+
minTextLength(value)
|
|
176
|
+
),
|
|
177
|
+
]),
|
|
178
|
+
}),
|
|
179
|
+
additionalMetadata
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
const IntegerProperty = (
|
|
183
|
+
config: PropertyConfig = {},
|
|
184
|
+
additionalMetadata = {}
|
|
185
|
+
) =>
|
|
186
|
+
Property<MaybeEmpty<number>>(
|
|
187
|
+
PROPERTY_TYPES.IntegerProperty,
|
|
188
|
+
merge(config, {
|
|
189
|
+
isInteger: true,
|
|
190
|
+
validators: _mergeValidators(config, [
|
|
191
|
+
_getValidatorFromConfigElseEmpty(config?.minValue, value =>
|
|
192
|
+
minNumber(value)
|
|
193
|
+
),
|
|
194
|
+
_getValidatorFromConfigElseEmpty(config?.maxValue, value =>
|
|
195
|
+
maxNumber(value)
|
|
196
|
+
),
|
|
197
|
+
]),
|
|
198
|
+
}),
|
|
199
|
+
additionalMetadata
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
const NumberProperty = (config: PropertyConfig = {}, additionalMetadata = {}) =>
|
|
203
|
+
Property<MaybeEmpty<number>>(
|
|
204
|
+
PROPERTY_TYPES.NumberProperty,
|
|
205
|
+
merge(config, {
|
|
206
|
+
isNumber: true,
|
|
207
|
+
validators: _mergeValidators(config, [
|
|
208
|
+
_getValidatorFromConfigElseEmpty(config?.minValue, value =>
|
|
209
|
+
minNumber(value)
|
|
210
|
+
),
|
|
211
|
+
_getValidatorFromConfigElseEmpty(config?.maxValue, value =>
|
|
212
|
+
maxNumber(value)
|
|
213
|
+
),
|
|
214
|
+
]),
|
|
215
|
+
}),
|
|
216
|
+
additionalMetadata
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
const ConstantValueProperty = (
|
|
220
|
+
value: string,
|
|
221
|
+
config: PropertyConfig = {},
|
|
222
|
+
additionalMetadata = {}
|
|
223
|
+
) =>
|
|
224
|
+
TextProperty(
|
|
225
|
+
merge(config, {
|
|
226
|
+
type: PROPERTY_TYPES.ConstantValueProperty,
|
|
227
|
+
value,
|
|
228
|
+
}),
|
|
229
|
+
additionalMetadata
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
const EmailProperty = (config: PropertyConfig = {}, additionalMetadata = {}) =>
|
|
233
|
+
TextProperty(
|
|
234
|
+
merge(config, {
|
|
235
|
+
type: PROPERTY_TYPES.EmailProperty,
|
|
236
|
+
validators: _mergeValidators(config, [meetsRegex(EMAIL_REGEX)]),
|
|
237
|
+
}),
|
|
238
|
+
additionalMetadata
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const BooleanProperty = (
|
|
242
|
+
config: PropertyConfig = {},
|
|
243
|
+
additionalMetadata = {}
|
|
244
|
+
) =>
|
|
245
|
+
Property<MaybeEmpty<boolean>>(
|
|
246
|
+
PROPERTY_TYPES.BooleanProperty,
|
|
247
|
+
merge(config, {
|
|
248
|
+
isBoolean: true,
|
|
249
|
+
}),
|
|
250
|
+
additionalMetadata
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
const UniqueId = (config: PropertyConfig = {}, additionalMetadata = {}) =>
|
|
254
|
+
Property<string>(
|
|
255
|
+
PROPERTY_TYPES.UniqueId,
|
|
256
|
+
merge(
|
|
257
|
+
{
|
|
258
|
+
lazyLoadMethod: (value: Arrayable<FunctionalType>) => {
|
|
259
|
+
if (!value) {
|
|
260
|
+
return createUuid()
|
|
261
|
+
}
|
|
262
|
+
return value
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
config
|
|
266
|
+
),
|
|
267
|
+
additionalMetadata
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
const ReferenceProperty = <T extends FunctionalModel>(
|
|
271
|
+
model: MaybeFunction<Model<T>>,
|
|
272
|
+
config: PropertyConfig = {},
|
|
273
|
+
additionalMetadata = {}
|
|
274
|
+
) => {
|
|
275
|
+
if (!model) {
|
|
276
|
+
throw new Error('Must include the referenced model')
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const _getModel = () => {
|
|
280
|
+
if (typeof model === 'function') {
|
|
281
|
+
return model()
|
|
282
|
+
}
|
|
283
|
+
return model
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const validators = _mergeValidators(config, [referenceTypeMatch<T>(model)])
|
|
287
|
+
|
|
288
|
+
const _getId =
|
|
289
|
+
(instanceValues: ReferenceValueType<T>) =>
|
|
290
|
+
(): MaybeEmpty<PrimaryKeyType> => {
|
|
291
|
+
if (!instanceValues) {
|
|
292
|
+
return null
|
|
293
|
+
}
|
|
294
|
+
if (typeof instanceValues === 'number') {
|
|
295
|
+
return instanceValues
|
|
296
|
+
}
|
|
297
|
+
if (typeof instanceValues === 'string') {
|
|
298
|
+
return instanceValues
|
|
299
|
+
}
|
|
300
|
+
if ((instanceValues as ModelInstance<T>).getPrimaryKey) {
|
|
301
|
+
return (instanceValues as ModelInstance<T>).getPrimaryKey()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const theModel = _getModel()
|
|
305
|
+
const primaryKey = theModel.getPrimaryKeyName()
|
|
306
|
+
|
|
307
|
+
// @ts-ignore
|
|
308
|
+
return (instanceValues as ModelInstanceInputData<T>)[
|
|
309
|
+
primaryKey
|
|
310
|
+
] as PrimaryKeyType
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const lazyLoadMethod = async (instanceValues: ReferenceValueType<T>) => {
|
|
314
|
+
const valueIsModelInstance =
|
|
315
|
+
instanceValues && (instanceValues as ModelInstance<T>).getPrimaryKeyName
|
|
316
|
+
const _getInstanceReturn = (objToUse: ReferenceValueType<T>) => {
|
|
317
|
+
// We need to determine if the object we just go is an actual model instance to determine if we need to make one.
|
|
318
|
+
const objIsModelInstance =
|
|
319
|
+
instanceValues && (instanceValues as ModelInstance<T>).getPrimaryKeyName
|
|
320
|
+
|
|
321
|
+
const instance = objIsModelInstance
|
|
322
|
+
? objToUse
|
|
323
|
+
: _getModel().create(objToUse as ModelInstanceInputData<T>)
|
|
324
|
+
return merge({}, instance, {
|
|
325
|
+
toObj: _getId(instanceValues),
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (valueIsModelInstance) {
|
|
330
|
+
return _getInstanceReturn(instanceValues)
|
|
331
|
+
}
|
|
332
|
+
if (config?.fetcher) {
|
|
333
|
+
const id = await _getId(instanceValues)()
|
|
334
|
+
const model = _getModel()
|
|
335
|
+
if (id !== null && id !== undefined) {
|
|
336
|
+
const obj = await config.fetcher(model, id)
|
|
337
|
+
return _getInstanceReturn(obj)
|
|
338
|
+
}
|
|
339
|
+
return null
|
|
340
|
+
}
|
|
341
|
+
return _getId(instanceValues)()
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const p: ReferencePropertyInstance<T> = merge(
|
|
345
|
+
Property<ModelInstance<T> | T | MaybeEmpty<PrimaryKeyType>>(
|
|
346
|
+
PROPERTY_TYPES.ReferenceProperty,
|
|
347
|
+
merge({}, config, {
|
|
348
|
+
validators,
|
|
349
|
+
lazyLoadMethod,
|
|
350
|
+
}),
|
|
351
|
+
additionalMetadata
|
|
352
|
+
),
|
|
353
|
+
{
|
|
354
|
+
getReferencedId: (instanceValues: ReferenceValueType<T>) =>
|
|
355
|
+
_getId(instanceValues)(),
|
|
356
|
+
getReferencedModel: _getModel,
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
return p
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export {
|
|
363
|
+
Property,
|
|
364
|
+
UniqueId,
|
|
365
|
+
DateProperty,
|
|
366
|
+
ArrayProperty,
|
|
367
|
+
ReferenceProperty,
|
|
368
|
+
IntegerProperty,
|
|
369
|
+
TextProperty,
|
|
370
|
+
ConstantValueProperty,
|
|
371
|
+
NumberProperty,
|
|
372
|
+
ObjectProperty,
|
|
373
|
+
EmailProperty,
|
|
374
|
+
BooleanProperty,
|
|
375
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import merge from 'lodash/merge'
|
|
2
|
+
import { PropertyGetters, JsonAble, toObj } from './interfaces'
|
|
3
|
+
|
|
4
|
+
const _getValue = async (value: any): Promise<JsonAble> => {
|
|
5
|
+
if (value === undefined) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
if (value === null) {
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
const type = typeof value
|
|
12
|
+
const asFunction = value as Function
|
|
13
|
+
if (type === 'function') {
|
|
14
|
+
return _getValue(await asFunction())
|
|
15
|
+
}
|
|
16
|
+
// Nested Object
|
|
17
|
+
const asModel = value.toObj
|
|
18
|
+
if (asModel) {
|
|
19
|
+
return _getValue(await asModel())
|
|
20
|
+
}
|
|
21
|
+
// Dates
|
|
22
|
+
const asDate = value as Date
|
|
23
|
+
if (type === 'object' && asDate.toISOString) {
|
|
24
|
+
return _getValue(asDate.toISOString())
|
|
25
|
+
}
|
|
26
|
+
return value
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const toJsonAble =
|
|
30
|
+
(keyToFunc: PropertyGetters<any>): toObj =>
|
|
31
|
+
async () => {
|
|
32
|
+
return Object.entries(keyToFunc).reduce(async (acc, [key, value]) => {
|
|
33
|
+
const realAcc = await acc
|
|
34
|
+
const trueValue = await _getValue(await value)
|
|
35
|
+
return merge(realAcc, { [key]: trueValue })
|
|
36
|
+
}, Promise.resolve({}))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { toJsonAble }
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import getRandomValuesFunc from 'get-random-values'
|
|
3
|
+
|
|
4
|
+
const HEX = 16
|
|
5
|
+
const FOUR = 4
|
|
6
|
+
const FIFTEEN = 15
|
|
7
|
+
|
|
8
|
+
const getRandomValues = (): Uint8Array => {
|
|
9
|
+
const array = new Uint8Array(1)
|
|
10
|
+
if (typeof window !== 'undefined') {
|
|
11
|
+
if (window.crypto) {
|
|
12
|
+
return window.crypto.getRandomValues(array)
|
|
13
|
+
}
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
if (window.msCrypto) {
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
return window.msCrypto.getRandomValues(array)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return getRandomValuesFunc(array)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const createUuid = (): string => {
|
|
25
|
+
// eslint-disable-next-line no-magic-numbers,require-unicode-regexp
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
// eslint-disable-next-line no-magic-numbers,require-unicode-regexp
|
|
28
|
+
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c: any) => {
|
|
29
|
+
const value = getRandomValues()[0] & (FIFTEEN >> (c / FOUR))
|
|
30
|
+
return (c ^ value).toString(HEX)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const toTitleCase = (string: string) => {
|
|
35
|
+
return `${string.slice(0, 1).toUpperCase()}${string.slice(1)}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const loweredTitleCase = (string: string) => {
|
|
39
|
+
return `${string.slice(0, 1).toLowerCase()}${string.slice(1)}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { loweredTitleCase, toTitleCase, createUuid }
|