diff --git a/lib/src/support/Rehydratable.ts b/lib/src/support/Rehydratable.ts index de51028..e2214c8 100644 --- a/lib/src/support/Rehydratable.ts +++ b/lib/src/support/Rehydratable.ts @@ -1,5 +1,5 @@ -export type JSONState = { [key: string]: string | boolean | number | JSONState | Array } +export type JSONState = { [key: string]: string | boolean | number | undefined | JSONState | Array } export function isJSONState(what: any): what is JSONState { try { diff --git a/orm/src/model/Model.ts b/orm/src/model/Model.ts index b521d86..91bccf6 100644 --- a/orm/src/model/Model.ts +++ b/orm/src/model/Model.ts @@ -16,6 +16,13 @@ import {JSONState, Rehydratable} from '../../../lib/src/support/Rehydratable.ts' // TODO separate read/write connections // TODO manual dirty flags +export type ModelJSONState = { + key_name: string, + key?: string | number, + fields?: string[], + ephemeral_values?: string, +} + /** * Base class for database models. * @extends Builder @@ -310,6 +317,21 @@ export abstract class Model> extends Builder implements Re return this } + /** + * Given a model object, load the values into this model. + * @param object + * @return Model + */ + public assume(object: { [key: string]: any }) { + get_fields_meta(this).each(field_def => { + // TODO special type casting + if ( field_def.model_key in object ) + // @ts-ignore + this[field_def.model_key] = object[field_def.model_key] + }) + return this + } + /** * If applicable get an object of normalized timestamps. * @return { updated_at?: Date, created_at?: Date } @@ -839,18 +861,55 @@ export abstract class Model> extends Builder implements Re return `${this.table_name()}.${this.key}` } + /** + * Like to_object, but only the fields that have changed. + * @return object + */ + public collect_dirty(): { [key: string]: any } { + const fields = this.dirty_fields() + const values: any = {} + for ( const field of fields ) { + // @ts-ignore + values[field] = this[field] + } + return values + } + /** * Dehydrate the model. Implements Rehydratable interface. */ - public async dehydrate(): Promise { - return this.to_object() + public async dehydrate(): Promise { + const constructor = this.constructor as typeof Model + return { + key_name: constructor.qualified_key_name(), + ...(this.exists() ? { + key: this.key(), + fields: this._loaded_database_fields(), + ephemeral_values: JSON.stringify(this.collect_dirty()), + } : { + ephemeral_values: JSON.stringify(this.to_object()), + }) + } } /** * Rehydrate the model. Implements Rehydratable interface. * @param state */ - public async rehydrate(state: JSONState) { - this.assume_from_source(state) + public async rehydrate(state: ModelJSONState) { + if ( state.key && state.fields ) { + const results = await this.select(state.fields) + .where(state.key_name, '=', state.key) + .limit(1) + .target_operator(make(ObjectResultOperator)) + .results() + + const result = results.first() + if ( result ) this.assume_from_source(result) + } + + if ( state.ephemeral_values ) { + this.assume(JSON.parse(state.ephemeral_values)) + } } }