UNPKG

@pilotlab/data

Version:

A luxurious user experience framework, developed by your friends at Pilot.

281 lines (222 loc) 10.8 kB
import is from "@pilotlab/is"; import { NodeType } from '@pilotlab/nodes'; import { Debug } from '@pilotlab/debug'; import { ProgressTracker } from '@pilotlab/progress'; import { IPromise, Result } from '@pilotlab/result'; import { Signal } from '@pilotlab/signals'; import { DataType } from './attributes/attributeEnums'; import IAttribute from './attributes/interfaces/iAttribute'; import IAttributes from './attributes/interfaces/iAttributes'; import IAttributeChangeOptions from './attributes/interfaces/iAttributeChangeOptions'; import IAttributeUpdateTracker from './attributes/interfaces/iAttributeUpdateTracker'; import AttributeBase from './attributes/attributeBase'; import AttributeEventArgs from './attributes/attributeEventArgs'; import AttributeRoot from './attributes/attributeRoot'; import Attributes from './attributes/attributes'; import AttributeChangeOptions from './attributes/attributeChangeOptions'; import AttributeCreateOptions from './attributes/attributeCreateOptions'; import IData from './interfaces/iData'; import IDataStore from './interfaces/iDataStore'; import { Identifier } from '@pilotlab/ids'; import { KeyAutoGenerationType } from "./dataEnums"; import { SHA1 } from '@pilotlab/sha1'; import { ThrottlingCommand } from '@pilotlab/commands'; Debug.setCategoryMode('data_save', false); export class Data extends AttributeBase<Attributes, AttributeBase<any, any, any, any>, Attributes, AttributeRoot> implements IData { constructor( data?:(Data | IAttributes | Object | string), isStoreKey:boolean = true, dataStore?:IDataStore, isSaveDataTypes:boolean = false, isAutoSave:boolean = false, key?:string, label?:string, progressTracker?:ProgressTracker, isInitialize:boolean = true ) { super(AttributeRoot.create, data, DataType.COLLECTION, label, key, NodeType.ROOT, false); this.isStoreKey = isStoreKey; if (isInitialize) this.initialize(data, dataStore, isSaveDataTypes, isAutoSave, progressTracker); } initialize( data?:(Data | IAttributes | Object | string), dataStore?:IDataStore, isSaveDataTypes:boolean = false, isAutoSave:boolean = false, progressTracker?:ProgressTracker ):IPromise<any> { return super.p_initialize([data, dataStore, isSaveDataTypes, isAutoSave, progressTracker]); } protected p_onInitializeStarted(result:Result<any>, args:any[]):IPromise<any> { const data:(Data | Attributes | Object | string) = args[0]; const dataStore:IDataStore = args[1]; const isSaveDataTypes:boolean = args[2]; const isAutoSave:boolean = args[3]; const progressTracker:ProgressTracker = args[4]; super.p_onInitializeStarted(new Result<any>(), [data, DataType.COLLECTION, NodeType.ROOT]).then(() => { this.p_value.changed.listen((args:AttributeEventArgs<Data>) => { this.p_onChanged(args); }); if (is.notEmpty(dataStore)) this.store = dataStore; if (!this.isStoreKey) { if (this.p_keyAutoGenerationType === KeyAutoGenerationType.SESSION_UNIQUE) { this.p_key = Identifier.getSessionUniqueInteger().toString(); } else this.p_key = Identifier.generate(); } else { this.p_key = Identifier.generate(); } this._isSaveDataTypes = isSaveDataTypes; this._isAutoSave = isAutoSave; this._saveCommand = new ThrottlingCommand( ():IPromise<any> => { let result:Result<boolean> = new Result<boolean>(); let data:any = this.toObject(this.isSaveDataTypes); if (is.empty(data) || is.empty(this.key) || typeof this.key !== 'string') return result.resolve(false); //----- Reset _isAutoSaveQueued this._isAutoSaveQueued = false; if (!this.hash.omitFromDataStore && this.p_isAutoHash) { SHA1.hash(JSON.stringify(data)).then((hash:string) => { data['hash'] = hash; Debug.log('saving to database', 'Data._saveCommand', 'data_save'); Debug.log(data, 'Data._saveCommand', 'data_save'); this._dataStore.save(data, <string>this.key).then((response:any) => { if (is.notEmpty(response) && typeof response === 'string') this._revision = response; result.resolve(response); this.saved.dispatch(response); }); }); } else { Debug.log('saving to database', 'Data._saveCommand', 'data_save'); Debug.log(data, 'Data._saveCommand', 'data_save'); this._dataStore.save(data, <string>this.key).then((response:any) => { if (is.notEmpty(response) && typeof response === 'string') this._revision = response; result.resolve(response); this.saved.dispatch(response); }); } return result; }, null, this, this._saveDebounceDelayInMilliseconds ); this.p_value.update(data, AttributeChangeOptions.signal.durationZero, progressTracker).then(() => result.resolve(data)); }); return result; } /*====================================================================* START: Properties *====================================================================*/ /** * If the key is stored, this will be a globally unique ID, used to identify the data * object associated with this entity in the data store. */ set key(value:string) { this.p_key = value; if (this.isStoreKey) this.save(); } isStoreKey:boolean = true; protected p_keyAutoGenerationType:KeyAutoGenerationType = KeyAutoGenerationType.SESSION_UNIQUE; /** * The revision ID, which may be managed by the database and can be used to determine * if the data associated with this entity is up-to-date. */ get revision():string { return this._revision; } private _revision:string; /** * An SHA-1 hash representing the precise contents of the associated blob or text. * Can be used to check the uniqueness of a data object to avoid storing duplicates. */ get hash():IAttribute { return this.p_value.get('hash', new AttributeCreateOptions('', DataType.STRING, null, null, null, AttributeChangeOptions.zero)); } get isAutoHash():boolean { return this.p_isAutoHash; } set isAutoHash(value:boolean) { this.p_isAutoHash = value; } protected p_isAutoHash:boolean = false; //===== DataStore /** * The data store where the data associated with this entity will be stored. */ get store():IDataStore { return this._dataStore; } set store(value:IDataStore) { this._dataStore = value; this.storeSet.dispatch(value); } private _dataStore:IDataStore; //===== Save /** * Determines whether a dataType property is saved with the data objects, * which can be used to verify the data's type when the object is deserialized. */ get isSaveDataTypes():boolean { return this._isSaveDataTypes; } set isSaveDataTypes(value:boolean) { this._isSaveDataTypes = value; } private _isSaveDataTypes:boolean; /** * Toggles auto-save functionality. * Can be used to pause auto-saving while changes to the data are batched. */ get isAutoSave():boolean { return this._isAutoSave; } set isAutoSave(value:boolean) { if (this._isAutoSave === value) return; this._isAutoSave = value; if (value && this._isAutoSaveQueued) this.save(); } private _isAutoSave:boolean = false; /** * This will be set to true if autoSave() was called while isAutoSave was false. * This triggers a save of the data immediately after isAutoSave is set to true. */ private _isAutoSaveQueued:boolean = false; /*====================================================================* START: Signals *====================================================================*/ storeSet:Signal<IDataStore> = new Signal<IDataStore>(true); /** * Dispatched after a response is received back from the data store when data has been saved. * Completes with the response from the data store. */ saved:Signal<any> = new Signal<any>(false); protected p_onChanged(args:AttributeEventArgs<Data>):void { if (is.empty(args)) return; if (args.changeOptions.isSave && is.notEmpty(this.key)) { this.autoSave(); this.saveTriggered.dispatch(args); } } /*====================================================================* START: Signals *====================================================================*/ saveTriggered:Signal<AttributeEventArgs<Data>> = new Signal<AttributeEventArgs<Data>>(false); /*====================================================================* START: Public Methods *====================================================================*/ update( data:(Data | Attributes | Object | string), changeOptions:IAttributeChangeOptions = AttributeChangeOptions.default, progressTracker?:ProgressTracker ):IAttributeUpdateTracker { return this.p_value.update(data, changeOptions, progressTracker); } //===== Save private _saveDebounceDelayInMilliseconds:number = 125; private _saveCommand:ThrottlingCommand; save():IPromise<any> { if (is.empty(this._dataStore)) { //----- Save immediately after the data store has been set. this.storeSet.listenOnce(() => this.save()); return Result.resolve<any>(null); } return this._saveCommand.debounce(); } autoSave():void { if (!this.isAutoSave) this._isAutoSaveQueued = true; else if (is.notEmpty(this.key)) this.save(); } delete():IPromise<boolean> { if (is.empty(this.key) || typeof this.key !== 'string') return Result.resolve<boolean>(false); let result = new Result<boolean>(); this._dataStore.delete(<string>this.key).then((isSuccess:boolean) => { result.resolve(isSuccess); }, (e:Error) => { result.reject(e); }); return result; } } // End of class export default Data;