Skip to content

Commit

Permalink
fix(modelreferences): add ModelReferences back in for ModelInstances
Browse files Browse the repository at this point in the history
  • Loading branch information
macornwell committed Apr 17, 2024
1 parent be22a87 commit 19f7d21
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 41 deletions.
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": "2.1.2",
"version": "2.1.3",
"description": "A library for creating JavaScript function based models.",
"main": "index.js",
"types": "index.d.ts",
Expand Down
18 changes: 12 additions & 6 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,19 @@ interface ModelReferencePropertyInstance<
T extends FunctionalModel,
TProperty extends Arrayable<FunctionalValue>,
TModel extends Model<T> = Model<T>,
TModelInstance extends ModelInstance<T, TModel> = ModelInstance<T, TModel>,
> extends PropertyInstance<TProperty> {
readonly getReferencedId: (
instanceValues: ModelReference<T>
instanceValues: ModelReference<T, TModel, TModelInstance>
) => Maybe<PrimaryKeyType>
readonly getReferencedModel: () => TModel
}

type ModelReference<T extends FunctionalModel> =
| T
| TypedJsonObj<T>
| PrimaryKeyType
type ModelReference<
T extends FunctionalModel,
TModel extends Model<T> = Model<T>,
TModelInstance extends ModelInstance<T, TModel> = ModelInstance<T, TModel>,
> = T | TModelInstance | TypedJsonObj<T> | PrimaryKeyType

type DefaultPropertyValidators = Readonly<{
required?: boolean
Expand Down Expand Up @@ -204,10 +206,14 @@ type PropertyConfigContents<T extends Arrayable<FunctionalValue>> = Readonly<{
type ModelFetcher = <
T extends FunctionalModel,
TModel extends Model<T> = Model<T>,
TModelInstance extends ModelInstance<T, TModel> = ModelInstance<T, TModel>,
>(
model: TModel,
primaryKey: PrimaryKeyType
) => Promise<T | TypedJsonObj<T>> | Promise<null> | Promise<undefined>
) =>
| Promise<T | TModelInstance | TypedJsonObj<T>>
| Promise<null>
| Promise<undefined>

type PropertyConfig<T extends Arrayable<FunctionalValue>> =
| (PropertyConfigContents<T> & DefaultPropertyValidators)
Expand Down
14 changes: 14 additions & 0 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ const getValueForReferencedModel = async (
return modelInstance.references[head]()
}
const modelReference = await modelInstance.get[head]()
if (modelReference.toObj) {
const [nestedHead, nestedTail] = createHeadAndTail(tail.split('.'), '.')
const value = await modelReference.get[nestedHead]()
if (nestedTail) {
return get(value, nestedTail)
}
return value
}
return get(modelReference, tail)
}

Expand Down Expand Up @@ -91,6 +99,11 @@ const mergeValidators = <T extends Arrayable<FunctionalValue>>(
return [...validators, ...(config?.validators ? config.validators : [])]
}

const isModelInstance = (obj: any): obj is ModelInstance<any, any> => {
// @ts-ignore
return Boolean(obj && obj.getPrimaryKeyName)
}

export {
isReferencedProperty,
getValueForModelInstance,
Expand All @@ -99,4 +112,5 @@ export {
getValidatorFromConfigElseEmpty,
getCommonNumberValidators,
mergeValidators,
isModelInstance,
}
70 changes: 54 additions & 16 deletions src/properties.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import merge from 'lodash/merge'
import { createPropertyValidator, isType, meetsRegex } from './validation'
import {
createPropertyValidator,
isType,
meetsRegex,
referenceTypeMatch,
} from './validation'
import { PROPERTY_TYPES } from './constants'
import { lazyValue } from './lazy'
import { createHeadAndTail, createUuid } from './utils'
Expand All @@ -20,6 +25,7 @@ import {
FunctionalModel,
JsonAble,
PropertyModifier,
TypedJsonObj,
} from './interfaces'
import {
getValueForModelInstance,
Expand All @@ -28,6 +34,7 @@ import {
getCommonTextValidators,
getCommonNumberValidators,
mergeValidators,
isModelInstance,
} from './lib'

const EMAIL_REGEX =
Expand Down Expand Up @@ -279,15 +286,17 @@ const ModelReferenceProperty = <
config: PropertyConfig<TModifier> = {},
additionalMetadata = {}
) =>
AdvancedModelReferenceProperty<T, Model<T>, TModifier>(
model,
config,
additionalMetadata
)
AdvancedModelReferenceProperty<
T,
Model<T>,
ModelInstance<T, Model<T>>,
TModifier
>(model, config, additionalMetadata)

const AdvancedModelReferenceProperty = <
T extends FunctionalModel,
TModel extends Model<T> = Model<T>,
TModelInstance extends ModelInstance<T, TModel> = ModelInstance<T, TModel>,
TModifier extends PropertyModifier<ModelReference<T>> = PropertyModifier<
ModelReference<T>
>,
Expand All @@ -307,10 +316,15 @@ const AdvancedModelReferenceProperty = <
return model
}

const validators = mergeValidators(config, [])
const validator = referenceTypeMatch<T, TModel, TModelInstance>(model)
const validators = mergeValidators(config, [
// @ts-ignore
validator,
])

const _getId =
(instanceValues: ModelReference<T>) => (): Maybe<PrimaryKeyType> => {
(instanceValues: ModelReference<T, TModel> | TModifier) =>
(): Maybe<PrimaryKeyType> => {
if (!instanceValues) {
return null
}
Expand All @@ -320,29 +334,53 @@ const AdvancedModelReferenceProperty = <
if (typeof instanceValues === 'string') {
return instanceValues
}
if ((instanceValues as TModelInstance).getPrimaryKey) {
return (instanceValues as TModelInstance).getPrimaryKey()
}

const theModel = _getModel()
const primaryKey = theModel.getPrimaryKeyName()

return (instanceValues as T)[primaryKey] as PrimaryKeyType
return (instanceValues as TypedJsonObj<T>)[primaryKey] as PrimaryKeyType
}

const lazyLoadMethod = async (instanceValues: T) => {
// Path for returning a TypedJsonObj / T
const lazyLoadMethod = async (instanceValues: TModifier) => {
const valueIsModelInstance = isModelInstance(instanceValues)
const _getInstanceReturn = (objToUse: TModifier) => {
// We need to determine if the object we just got is an actual model instance to determine if we need to make one.
const objIsModelInstance = isModelInstance(objToUse)
// @ts-ignore
const instance = objIsModelInstance
? objToUse
: _getModel().create(objToUse as TypedJsonObj<T>)
// We are replacing the toObj function, because the reference type in the end should be the primary key when serialized.
return merge({}, instance, {
toObj: _getId(instanceValues),
})
}

// @ts-ignore
if (valueIsModelInstance) {
return _getInstanceReturn(instanceValues)
}
if (config?.fetcher) {
const id = await _getId(instanceValues)()
const model = _getModel()
if (id !== null && id !== undefined) {
return config.fetcher<T>(model, id)
const obj = await config.fetcher<T>(model, id)
return _getInstanceReturn(obj as TModifier)
}
return null
}

// This is just an id.
return _getId(instanceValues)()
}

const p: ModelReferencePropertyInstance<T, TModifier> = merge(
const p: ModelReferencePropertyInstance<
T,
TModifier,
TModel,
TModelInstance
> = merge(
Property<TModifier>(
PROPERTY_TYPES.ReferenceProperty,
merge({}, config, {
Expand All @@ -352,7 +390,7 @@ const AdvancedModelReferenceProperty = <
additionalMetadata
),
{
getReferencedId: (instanceValues: ModelReference<T>) =>
getReferencedId: (instanceValues: ModelReference<T, TModel>) =>
_getId(instanceValues)(),
getReferencedModel: _getModel,
}
Expand Down
10 changes: 7 additions & 3 deletions src/serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import {
toObj,
FunctionalModel,
TypedJsonObj,
ModelInstance,
} from './interfaces'

const isModelInstance = (obj: any): obj is ModelInstance<any> => {
return Boolean(obj.toObj)
}

const _getValue = async (value: any): Promise<JsonAble | null> => {
if (value === undefined) {
return null
Expand All @@ -20,9 +25,8 @@ const _getValue = async (value: any): Promise<JsonAble | null> => {
return _getValue(await asFunction())
}
// Nested Object
const asModel = value.toObj
if (asModel) {
return _getValue(await asModel())
if (isModelInstance(value)) {
return _getValue(await value.toObj())
}
// Dates
const asDate = value as Date
Expand Down
35 changes: 35 additions & 0 deletions src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
ValidatorConfiguration,
ValuePropertyValidatorComponent,
ValidationErrors,
MaybeFunction,
PropertyValidatorComponentTypeAdvanced,
} from './interfaces'

const TYPE_PRIMITIVES = {
Expand Down Expand Up @@ -393,6 +395,38 @@ const isValid = <T extends FunctionalModel>(errors: ModelErrors<T>) => {
return Object.keys(errors).length < 1
}

const referenceTypeMatch = <
T extends FunctionalModel,
TModel extends Model<T> = Model<T>,
TModelInstance extends ModelInstance<T, TModel> = ModelInstance<T, TModel>,
>(
referencedModel: MaybeFunction<TModel>
): PropertyValidatorComponentTypeAdvanced<
ModelInstance<T, TModel>,
T,
TModel,
TModelInstance
> => {
return (value?: ModelInstance<T, TModel>) => {
if (!value) {
return 'Must include a value'
}
// This needs to stay here, as it delays the creation long enough for
// self referencing types.
const model =
typeof referencedModel === 'function'
? referencedModel()
: referencedModel
// Assumption: By the time this is received, value === a model instance.
const eModel = model.getName()
const aModel = value.getModel().getName()
if (eModel !== aModel) {
return `Model should be ${eModel} instead, received ${aModel}`
}
return undefined
}
}

export {
isNumber,
isBoolean,
Expand All @@ -415,4 +449,5 @@ export {
arrayType,
isValid,
TYPE_PRIMITIVES,
referenceTypeMatch,
}
Loading

0 comments on commit 19f7d21

Please sign in to comment.