import { SchemeType } from './constants' import { convertModel } from './converter' import { CommonFieldCreator } from './field_declaration' import { AllKeysAre, InKeyOfWithType } from './global_types' import { checkType, error, isObject } from './helpers' import { preparePropDeclarations, PropDeclaration } from './prop_declaration' declare interface SerializedObject { deserialize(): DeserializedObject } declare interface DeserializedObject { [originalField: string]: any } declare type SerializedStructure = InKeyOfWithType< DeclarationsInstance, any > & SerializedObject export declare interface ModelWrapper { modelConfiguration: ModelConfiguration new (originalModel: object): SerializedStructure serialize(originalModel: AllKeysAre): SerializedStructure deserialize(usageModel: AllKeysAre): DeserializedObject getUsagePropertyNames(): string[] getOriginalPropertyNames(): string[] getPropertiesMap(reverseNames?: boolean): AllKeysAre } export declare interface ModelOptions { defaultValues: boolean warnings: boolean } const DEFAULT_MODEL_OPTIONS: ModelOptions = { defaultValues: false, warnings: true } export declare interface ModelConfiguration { options: ModelOptions declarations: PropDeclaration[] } export const createModelConfig = ( objectWithDeclarations: AllKeysAre, modelOptions?: Partial ): ModelConfiguration => ({ declarations: preparePropDeclarations(objectWithDeclarations), options: { ...DEFAULT_MODEL_OPTIONS, ...(modelOptions || {}) } }) const serializeObject = ( modelConfiguration: ModelConfiguration, structure: AllKeysAre ): SerializedStructure => { if (!isObject(structure)) { structure = {} } const serializedObject = Object.create({ deserialize: () => convertModel(serializedObject, modelConfiguration, true) }) return Object.assign( serializedObject, convertModel(structure, modelConfiguration, false) ) as SerializedStructure } const deserializeObject = ( modelConfiguration: ModelConfiguration, structure: AllKeysAre ): DeserializedObject => { checkType(structure, 'object', 'Usage model') if (!modelConfiguration) { error( 'This model is never serialized.' + 'Before using deserialize() needs to call serialize() or create new instance of model' ) } return convertModel(structure, modelConfiguration, true) } const getPropertyNames = ( { declarations }: ModelConfiguration, getOnlyUsageProperties: boolean ) => { return declarations.reduce((names: string[], declaration) => { if (declaration.scheme.schemeType !== SchemeType.SERIALIZERS) { names.push( declaration.scheme[getOnlyUsageProperties ? 'to' : 'from'].name ) } return names }, []) } const getPropertiesMap = ( { declarations }: ModelConfiguration, reverseNames?: boolean ): AllKeysAre => declarations.reduce((namesMap: AllKeysAre, declaration) => { if (declaration.scheme.schemeType !== SchemeType.SERIALIZERS) { const propertiesNames = [ declaration.scheme.to.name, declaration.scheme.from.name ] const [keyName, value] = reverseNames ? propertiesNames.reverse() : propertiesNames namesMap[keyName] = value } return namesMap }, {}) declare type DeclarationsInstance = C extends (new () => any) ? InstanceType : C // TODO: complete it // declare type SerializedData< // C, // D = InKeyOfWithType, PropDeclaration> // > = { // [K in keyof D]: D[K] // } export const createModel = any)>( Model: T, partialModelOptions?: Partial ): ModelWrapper => { const declarationInstance = typeof Model === 'function' ? new (Model as any)() : new (class Model { // it is hack to create unique instances constructor(context: any) { Object.assign(this, { ...context }) } })(Model) const modelConfiguration: ModelConfiguration = createModelConfig( declarationInstance, partialModelOptions ) return class ModelWrapper { static modelConfiguration = modelConfiguration static serialize = (originalModel: object) => serializeObject(modelConfiguration, originalModel) static deserialize = (usageModel: object) => deserializeObject(modelConfiguration, usageModel) static getUsagePropertyNames = () => getPropertyNames(modelConfiguration, true) static getOriginalPropertyNames = () => getPropertyNames(modelConfiguration, false) static getPropertiesMap = (reverseNames?: boolean) => getPropertiesMap(modelConfiguration, reverseNames) // instance methods deserialize: any constructor(originalModel: object) { return serializeObject(modelConfiguration, originalModel) } } as any // TODO: find a good solution to remove this }