UNPKG

hyperflow

Version:

A javascript state flow and mutation management toolkit & library for developing universal app.

1,192 lines (1,089 loc) 44.9 kB
/** * Copyright 2015-present Tuan Le. * * Licensed under the MIT License. * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://opensource.org/licenses/mit-license.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *------------------------------------------------------------------------ * * @module DataCursorElement * @description - A data cursor element. * * @author Tuan Le (tuan.t.lei@gmail.com) * * @flow */ 'use strict'; // eslint-disable-line import CommonElement from './common-element'; /* load default constrainable descriptor presets */ import requiredPreset from './descriptors/presets/required-constrainable-preset'; import boundedPreset from './descriptors/presets/bounded-constrainable-preset'; import oneOfValuesPreset from './descriptors/presets/one-of-values-constrainable-preset'; import oneOfTypesPreset from './descriptors/presets/one-of-types-constrainable-preset'; import stronglyTypedPreset from './descriptors/presets/strongly-typed-constrainable-preset'; const Hf = CommonElement(); /** * @description - A data cursor prototypes. * * DataCursorElementPrototype */ const DataCursorElementPrototype = Object.create({}).prototype = { /* ----- Prototype Definitions --------- */ /** * @description - Helper function to create schema from a source object. * * @method _createSchema * @param {object} source * @return {object} * @private */ _createSchema (source) { const cursor = this; if (Hf.DEVELOPMENT) { if (!(Hf.isObject(source) || Hf.isArray(source))) { Hf.log(`error`, `DataCursorElement._createSchema - Input source object is invalid.`); } } return Object.entries(source).reduce((schema, [ key, value ]) => { if (Hf.isObject(value)) { schema[key] = cursor._createSchema(value); } else if (Hf.isArray(value)) { schema[key] = value.map((arrayItem) => { if (Hf.isObject(arrayItem)) { return cursor._createSchema(arrayItem); } return Hf.typeOf(arrayItem); }); } else if (cursor.isItemComputable(key)) { schema[key] = `computable`; } else if (cursor.isItemObservable(key)) { schema[key] = `observable`; } else { schema[key] = Hf.typeOf(value); } return schema; }, {}); }, /** * @description - At cursor, check if there is a data item in content at key. * * @method hasItem * @param {string|number} key * @return {boolean} */ hasItem (key) { const cursor = this; return cursor._content.hasOwnProperty(key); }, /** * @description - At cursor, check if it is immutable. * * @method isImmutable * @return {boolean} */ isImmutable () { const cursor = this; return cursor._immutable; }, /** * @description - At cursor, check that data item is bounded. * * @method isItemBounded * @param {string|number} key * @return {boolean} */ isItemBounded (key) { const cursor = this; if (cursor.isItemConstrainable(key)) { const pathId = `${cursor._pathId}.${key}`; return cursor._descriptor.select(`constrainable`).getDescription(pathId).hasConstraint(`bounded`); } return false; }, /** * @description - At cursor, check that data item is strongly typed. * * @method isItemStronglyTyped * @param {string|number} key * @return {boolean} */ isItemStronglyTyped (key) { const cursor = this; if (cursor.isItemConstrainable(key)) { const pathId = `${cursor._pathId}.${key}`; return cursor._descriptor.select(`constrainable`).getDescription(pathId).hasConstraint(`stronglyTyped`); } return false; }, /** * @description - At cursor, check that data item is one of values. * * @method isItemOneOfValues * @param {string|number} key * @return {boolean} */ isItemOneOfValues (key) { const cursor = this; if (cursor.isItemConstrainable(key)) { const pathId = `${cursor._pathId}.${key}`; return cursor._descriptor.select(`constrainable`).getDescription(pathId).hasConstraint(`oneOf`); } return false; }, /** * @description - At cursor, check that data item is one of types. * * @method isItemOneOfTypes * @param {string|number} key * @return {boolean} */ isItemOneOfTypes (key) { const cursor = this; if (cursor.isItemConstrainable(key)) { const pathId = `${cursor._pathId}.${key}`; return cursor._descriptor.select(`constrainable`).getDescription(pathId).hasConstraint(`oneTypeOf`); } return false; }, /** * @description - At cursor, check that data item is required. * * @method isItemRequired * @param {string|number} key * @return {boolean} */ isItemRequired (key) { const cursor = this; if (cursor.isItemConstrainable(key)) { const pathId = `${cursor._pathId}.${key}`; return cursor._descriptor.select(`constrainable`).getDescription(pathId).hasConstraint(`required`); } return false; }, /** * @description - At cursor, check that data item is constrainable. * * @method isItemConstrainable * @param {string|number} key * @return {boolean} */ isItemConstrainable (key) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; return cursor.hasItem(key) ? cursor._descriptor.select(`constrainable`).hasDescription(pathId) : false; }, /** * @description - At cursor, check that data item is computable. * * @method isItemComputable * @param {string|number} key * @return {boolean} */ isItemComputable (key) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; return cursor.hasItem(key) ? cursor._descriptor.select(`computable`).hasDescription(pathId) : false; }, /** * @description - At cursor, check that data item is observable. * * @method isItemObservable * @param {string|number} key * @return {boolean} */ isItemObservable (key) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; return cursor.hasItem(key) ? cursor._descriptor.select(`observable`).hasDescription(pathId) : false; }, /** * @description - At cursor, get pathId. * * @method getPathId * @return {string} */ getPathId () { const cursor = this; return cursor._pathId; }, /** * @description - At cursor, get key. * * @method getKey * @return {string|number} */ getKey () { const cursor = this; return cursor._key; }, /** * @description - At cursor, get data item accessor. * * @method getAccessor * @param {object} option * @return {object} */ getAccessor (option = {}) { const cursor = this; const data = cursor._data; option = Hf.isObject(option) ? option : {}; return data._getAccessor(cursor._pathId, option); }, /** * @description - At cursor, get data type of content. * * @method getContentType * @return {string} */ getContentType () { const cursor = this; return Hf.typeOf(cursor._content); }, /** * @description - At cursor, get content item keys. * * @method getContentItemKeys * @return {string|number} */ getContentItemKeys () { const cursor = this; return Object.keys(cursor._content); }, /** * @description - At cursor, get data content item value. * * @method getContentItem * @param {string|number} key * @return {*} */ getContentItem (key) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; if (Hf.DEVELOPMENT) { if (!cursor.hasItem(key)) { Hf.log(`error`, `DataCursorElement.getContentItem - Data item key:${key} at pathId:${pathId} is not defined.`); } } const contentItem = cursor._content[key]; return Hf.isObject(contentItem) || Hf.isArray(contentItem) ? Hf.clone(contentItem) : contentItem; }, /** * @description - At cursor, set value to a data item. * * @method setContentItem * @param {*} item * @param {string|number} key * @return void */ setContentItem (item, key) { const cursor = this; const data = cursor._data; const pathId = `${cursor._pathId}.${key}`; if (Hf.DEVELOPMENT) { if (!Hf.isDefined(item)) { Hf.log(`error`, `DataCursorElement.setContentItem - Input data item with key:${key} at pathId:${pathId} is invalid.`); } } if (cursor.hasItem(key)) { /* check that data item is not computable */ if (Hf.DEVELOPMENT) { if (cursor.isItemComputable(key)) { Hf.log(`error`, `DataCursorElement.setContentItem - Data item key:${key} at pathId:${pathId} is already described as a computable.`); } } if (item === null && cursor._content[key] !== null) { cursor._content[key] = null; /* unassign descriptors if data item is strongly typed and/or is required */ if (cursor.isItemStronglyTyped(key) || cursor.isItemRequired(key)) { cursor.unDescribeItem(key).asConstrainable(); } /* save change to mutation record at cursor if immutable */ if (cursor.isImmutable()) { data._recordMutation(pathId); } } else if (Hf.isObject(item) || Hf.isArray(item)) { if (Hf.isObject(item)) { cursor._content[key] = {}; if (!Hf.isEmpty(item)) { const innerCursor = data.select(pathId); Object.entries(item).forEach(([ innerKey, innerItem ]) => { innerCursor.setContentItem(innerItem, innerKey); }); } /* if data item is strongly typed and/or is required reassign strongly typed and/or required descriptor for item and it`s nested items */ if (cursor.isItemStronglyTyped(key)) { cursor.describeItem(key).asStronglyTyped(); } if (cursor.isItemRequired(key)) { cursor.describeItem(key).asRequired(); } } else if (Hf.isArray(item)) { cursor._content[key] = []; if (!Hf.isEmpty(item)) { const innerCursor = data.select(pathId); item.forEach((innerItem, innerKey) => { innerCursor.setContentItem(innerItem, innerKey); }); } /* if data item is strongly typed and/or is required reassign strongly typed and/or required descriptor for item and it`s nested items */ if (cursor.isItemStronglyTyped(key)) { cursor.describeItem(key).asStronglyTyped(); } if (cursor.isItemRequired(key)) { cursor.describeItem(key).asRequired(); } } /* save change to mutation record at cursor if immutable */ if (cursor.isImmutable()) { data._recordMutation(pathId); } } else { if (cursor._content[key] !== item) { cursor._content[key] = item; /* save change to mutation record at cursor if immutable */ if (cursor.isImmutable()) { data._recordMutation(pathId); } } } } else { if (item === null) { cursor._content[key] = null; /* save change to mutation record at cursor if immutable */ if (cursor.isImmutable()) { data._recordMutation(pathId); } } else { if (Hf.isObject(item) || Hf.isArray(item)) { if (Hf.isObject(item)) { cursor._content[key] = {}; if (!Hf.isEmpty(item)) { const innerCursor = data.select(pathId); Object.entries(item).forEach(([ innerKey, innerItem ]) => { innerCursor.setContentItem(innerItem, innerKey); }); } /* set the nested item as strongly typed and/or required if top item is a strongly typed and/or required */ if (cursor._descriptor.select(`constrainable`).hasDescription(cursor._pathId)) { const description = cursor._descriptor.select(`constrainable`).getDescription(cursor._pathId); if (description.hasConstraint(`stronglyTyped`)) { cursor.describeItem(key).asStronglyTyped(); } if (description.hasConstraint(`required`)) { cursor.describeItem(key).asRequired(); } } } else { cursor._content[key] = []; if (!Hf.isEmpty(item)) { const innerCursor = data.select(pathId); item.forEach((innerItem, innerKey) => { innerCursor.setContentItem(innerItem, innerKey); }); } /* set the nested item as strongly typed and/or required if top item is a strongly typed and/or required */ if (cursor._descriptor.select(`constrainable`).hasDescription(cursor._pathId)) { const description = cursor._descriptor.select(`constrainable`).getDescription(cursor._pathId); if (description.hasConstraint(`stronglyTyped`)) { cursor.describeItem(key).asStronglyTyped(); } if (description.hasConstraint(`required`)) { cursor.describeItem(key).asRequired(); } } } } else { cursor._content[key] = item; /* set the nested item as strongly typed and/or required if top item is a strongly typed and/or required */ if (cursor._descriptor.select(`constrainable`).hasDescription(cursor._pathId)) { const description = cursor._descriptor.select(`constrainable`).getDescription(cursor._pathId); if (description.hasConstraint(`stronglyTyped`)) { cursor.describeItem(key).asStronglyTyped(); } if (description.hasConstraint(`required`)) { cursor.describeItem(key).asRequired(); } } } /* save change to mutation record at cursor if immutable */ if (cursor.isImmutable()) { data._recordMutation(pathId); } } } }, /** * @description - At cursor, recall and return the previous content item and its timestamp from mutation history. * * @method recallContentItem * @param {string|number} key * @param {number} timeIndexOffset * @return {object} */ recallContentItem (key, timeIndexOffset) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; if (Hf.DEVELOPMENT) { if (!cursor.hasItem(key)) { Hf.log(`error`, `DataCursorElement.recallContentItem - Data item key:${key} at pathId:${pathId} is not defined.`); } else if (!Hf.isInteger(timeIndexOffset) || timeIndexOffset >= 0) { Hf.log(`error`, `DataCursorElement.recallContentItem - Input time index offset must be non-zero and negative.`); } else if (!cursor.isImmutable()) { Hf.log(`error`, `DataCursorElement.recallContentItem - Data item key:${key} at pathId:${pathId} is mutable and has no mutation history.`); } } const data = cursor._data; const mMap = data._mutation.mMap; const cursorTimestamps = data._mutation.timestamp[cursor._rootKey]; const currentTimeIndex = data._mutation.timeIndex[cursor._rootKey]; const recallTimeIndex = currentTimeIndex + timeIndexOffset; const recallTimeCount = cursorTimestamps.length; const leafType = cursor.getContentType(key) !== `object` || cursor.getContentType(key) !== `array`; let pathIdAtTimeIndex = Hf.stringToArray(pathId, `.`); if (recallTimeCount > recallTimeIndex) { if (leafType) { pathIdAtTimeIndex.pop(); } pathIdAtTimeIndex.shift(); pathIdAtTimeIndex.unshift(`${cursor._rootKey}${recallTimeIndex}`); pathIdAtTimeIndex = Hf.arrayToString(pathIdAtTimeIndex, `.`); if (!mMap.hasNode(pathIdAtTimeIndex)) { if (Hf.DEVELOPMENT) { Hf.log(`warn1`, `DataCursorElement.recallContentItem - Data item key:${key} at pathId:${pathId} is undefine at time index:${recallTimeIndex}.`); } return { timestamp: null, key, recallTimeIndex: null, recallTimeCount: null, content: null }; } return { timestamp: cursorTimestamps[recallTimeIndex], key, recallTimeIndex: -recallTimeIndex, recallTimeCount, content: leafType ? mMap.select(pathIdAtTimeIndex).getContent()[key] : mMap.select(pathIdAtTimeIndex).getContent() }; } else { // eslint-disable-line return { timestamp: null, key, recallTimeIndex: null, recallTimeCount: null, content: null }; } }, /** * @description - At cursor, recall and return all the previous content items and timestamps from mutation history. * * @method recallAllContentItems * @param {string|number} key * @return {object} */ recallAllContentItems (key) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; if (Hf.DEVELOPMENT) { if (!cursor.hasItem(key)) { Hf.log(`error`, `DataCursorElement.recallAllContentItems - Data item key:${key} at pathId:${pathId} is not defined.`); } else if (!cursor.isImmutable()) { Hf.log(`error`, `DataCursorElement.recallAllContentItems - Data item key:${key} at pathId:${pathId} is mutable and has no mutation history.`); } } const data = cursor._data; const mMap = data._mutation.mMap; const cursorTimestamps = data._mutation.timestamp[cursor._rootKey]; const currentTimeIndex = data._mutation.timeIndex[cursor._rootKey]; const recallTimeCount = cursorTimestamps.length; const leafType = cursor.getContentType(key) !== `object` || cursor.getContentType(key) !== `array`; let timeIndexOffset = -1; return cursorTimestamps.slice(1).map((cursorTimestamp) => { const recallTimeIndex = currentTimeIndex + timeIndexOffset; let pathIdAtTimeIndex = Hf.stringToArray(pathId, `.`); timeIndexOffset--; if (leafType) { pathIdAtTimeIndex.pop(); } pathIdAtTimeIndex.shift(); pathIdAtTimeIndex.unshift(`${cursor._rootKey}${recallTimeIndex}`); pathIdAtTimeIndex = Hf.arrayToString(pathIdAtTimeIndex, `.`); return { timestamp: cursorTimestamp, pathIdAtTimeIndex }; }).filter((timeCursor) => mMap.hasNode(timeCursor.pathIdAtTimeIndex)).map((timeCursor) => { return { timestamp: timeCursor.timestamp, key, recallTimeCount, content: leafType ? mMap.select(timeCursor.pathIdAtTimeIndex).getContent()[key] : mMap.select(timeCursor.pathIdAtTimeIndex).getContent() }; }); }, /** * @description - At cursor, get the description of an item. * * @method getItemDescription * @param {string} type * @return {object} */ getItemDescription (key) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; if (Hf.DEVELOPMENT) { if (!cursor.hasItem(key)) { Hf.log(`error`, `DataCursorElement.getItemDescription - Data item key:${key} at pathId:${pathId} is not defined.`); } } return { /** * @description - Get item constrainable description. * * @method getItemDescription.ofConstrainable * @return {object} */ ofConstrainable () { if (Hf.DEVELOPMENT) { if (!cursor.isItemConstrainable(key)) { Hf.log(`error`, `DataCursorElement.getItemDescription.ofConstrainable - Data item key:${key} at pathId:${pathId} does not have a constrainable description.`); } } return cursor._descriptor.select(`constrainable`).getDescription(pathId); }, /** * @description - Get item computable description. * * @method getItemDescription.ofComputable * @return {object} */ ofComputable () { if (Hf.DEVELOPMENT) { if (!cursor.isItemComputable(key)) { Hf.log(`error`, `DataCursorElement.getItemDescription.ofComputable - Data item key:${key} at pathId:${pathId} does not have a computable description.`); } } return cursor._descriptor.select(`computable`).getDescription(pathId); }, /** * @description - Get item observable description. * * @method getItemDescription.ofObservable * @return {object} */ ofObservable () { if (Hf.DEVELOPMENT) { if (!cursor.isItemObservable(key)) { Hf.log(`error`, `DataCursorElement.getItemDescription.ofObservable - Data item key:${key} at pathId:${pathId} does not have an observable description.`); } } return cursor._descriptor.select(`observable`).getDescription(pathId); } }; }, /** * @description - At cursor, give descriptions to an item. * * @method describeItem * @param {string|number} key * @return {object} */ describeItem (key) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; if (Hf.DEVELOPMENT) { if (!cursor.hasItem(key)) { Hf.log(`error`, `DataCursorElement.describeItem - Data item key:${key} at pathId:${pathId} is not defined.`); } } const constrainableItem = cursor.isItemConstrainable(key); const computableItem = cursor.isItemComputable(key); const observableItem = cursor.isItemObservable(key); return { /** * @description - Describe item with a one of values constraint. * * @method describeItem.asOneOfValues * @param {array} values * @return {object} */ asOneOfValues (values) { if (Hf.DEVELOPMENT) { if (!Hf.isArray(values) || !Hf.isNonEmptyArray(values)) { Hf.log(`error`, `DataCursorElement.describeItem.asOneOfValues - Input values are invalid.`); } else if (!values.every((value) => Hf.isString(value) || Hf.isNumeric(value))) { Hf.log(`error`, `DataCursorElement.describeItem.asOneOfValues - Value must be either numeric or string.`); } else if (computableItem) { Hf.log(`error`, `DataCursorElement.describeItem.asOneOfValues - Cannot redescribe computable data item key:${key} at pathId:${pathId}.`); } } const descriptor = cursor._descriptor.select(`constrainable`); const constraint = oneOfValuesPreset(values); if (!descriptor.hasDescription(pathId)) { const descPreset = { key, constraint }; descriptor.addDescription(pathId).assign(descPreset).to(cursor._content); } else { descriptor.getDescription(pathId).addConstraint( constraint.oneOf.constrainer, constraint.oneOf.condition, `oneOf` ); } return this; }, /** * @description - Describe item with a one of types constraint. * * @method describeItem.asOneOfTypes * @param {array} types * @return {object} */ asOneOfTypes (types) { if (Hf.DEVELOPMENT) { if (!Hf.isArray(types) || !Hf.isNonEmptyArray(types)) { Hf.log(`error`, `DataCursorElement.describeItem.asOneOfTypes - Input types are invalid.`); } else if (!types.every((type) => Hf.isString(type))) { Hf.log(`error`, `DataCursorElement.describeItem.asOneOfTypes - Type value must be string.`); } else if (computableItem) { Hf.log(`error`, `DataCursorElement.describeItem.asOneOfTypes - Cannot redescribe computable data item key:${key} at pathId:${pathId}.`); } } const descriptor = cursor._descriptor.select(`constrainable`); const constraint = oneOfTypesPreset(types); if (!descriptor.hasDescription(pathId)) { const descPreset = { key, constraint }; descriptor.addDescription(pathId).assign(descPreset).to(cursor._content); } else { descriptor.getDescription(pathId).addConstraint( constraint.oneTypeOf.constrainer, constraint.oneTypeOf.condition, `oneTypeOf` ); } return this; }, /** * @description - Describe item with a required constraint. * * @method describeItem.asRequired * @return {object} */ asRequired () { function deepAssignDescription (_pathId, _content, _key) { const descriptor = cursor._descriptor.select(`constrainable`); const constraint = requiredPreset(); if (!descriptor.hasDescription(_pathId)) { const descPreset = { key: _key, constraint }; descriptor.addDescription(_pathId).assign(descPreset).to(_content); } else { descriptor.getDescription(_pathId).addConstraint( constraint.required.constrainer, null, `required` ); } /* recursively set requirable description to nested objects */ if (Hf.isNonEmptyObject(_content[_key])) { Object.keys(_content[_key]).forEach((innerKey) => { deepAssignDescription(`${_pathId}.${innerKey}`, _content[_key], innerKey); }); } } if (Hf.DEVELOPMENT) { if (computableItem) { Hf.log(`error`, `DataCursorElement.describeItem.asRequired - Cannot redescribe computable data item key:${key} at pathId:${pathId}.`); } } deepAssignDescription(pathId, cursor._content, key); return this; }, /** * @description - Describe item with a strongly typed constraint. * * @method describeItem.asStronglyTyped * @return void */ asStronglyTyped () { function deepAssignDescription (_pathId, _content, _key) { const descriptor = cursor._descriptor.select(`constrainable`); const constraint = stronglyTypedPreset(); if (!descriptor.hasDescription(_pathId)) { const descPreset = { key: _key, constraint }; descriptor.addDescription(_pathId).assign(descPreset).to(_content); } else { descriptor.getDescription(_pathId).addConstraint( constraint.stronglyTyped.constrainer, null, `stronglyTyped`); } /* recursively set strongly typed description to nested objects */ if (Hf.isNonEmptyObject(_content[_key])) { Object.keys(_content[_key]).forEach((innerKey) => { deepAssignDescription(`${_pathId}.${innerKey}`, _content[_key], innerKey); }); } } if (Hf.DEVELOPMENT) { if (computableItem) { Hf.log(`error`, `DataCursorElement.describeItem.asStronglyTyped - Cannot redescribe computable data item key:${key} at pathId:${pathId}.`); } } deepAssignDescription(pathId, cursor._content, key); return this; }, /** * @description - Describe item with a bounding constraint. * * @method describeItem.asBounded * @param {object} lowerBound * @param {object} upperBound * @return {object} */ asBounded (lowerBound, upperBound) { if (Hf.DEVELOPMENT) { if (!Hf.isNumeric(lowerBound) && !Hf.isNumeric(upperBound)) { Hf.log(`error`, `DataCursorElement.describeItem.asBounded - Input bouding range values are must be numeric.`); } else if (computableItem) { Hf.log(`error`, `DataCursorElement.describeItem.asBounded - Cannot redescribe computable data item key:${key} at pathId:${pathId}.`); } } const descriptor = cursor._descriptor.select(`constrainable`); const constraint = boundedPreset(lowerBound, upperBound); if (!descriptor.hasDescription(pathId)) { const descPreset = { key, constraint }; descriptor.addDescription(pathId).assign(descPreset).to(cursor._content); } else { descriptor.getDescription(pathId).addConstraint( constraint.bounded.constrainer, constraint.bounded.condition, `bounded`); } return this; }, /** * @description - Describe item as a constrainable. * * @method describeItem.asConstrainable * @param {object} constraint * @return void */ asConstrainable (constraint) { if (Hf.DEVELOPMENT) { if (!Hf.isObject(constraint) && Object.key(constraint).forEach((constraintKey) => { return Hf.isSchema({ constrainer: `function` }).of(constraint[constraintKey]); })) { Hf.log(`error`, `DataCursorElement.describeItem.asConstrainable - Input constraint is invalid.`); } else if (computableItem) { Hf.log(`error`, `DataCursorElement.describeItem.asConstrainable - Cannot redescribe computable data item key:${key} at pathId:${pathId}.`); } } const descriptor = cursor._descriptor.select(`constrainable`); if (!descriptor.hasDescription(pathId)) { const descPreset = { key, constraint }; descriptor.addDescription(pathId).assign(descPreset).to(cursor._content); } else { Object.entries(constraint).forEach(([ constraintKey, constraintValue ]) => { descriptor.getDescription(pathId).addConstraint( constraintValue.contrainer, constraintValue.condition, constraintKey ); }); } return this; }, /** * @description - Describe item as an observable. * * @method describeItem.asObservable * @param {object} condition * @param {object} subscriber * @return void */ asObservable (condition, subscriber) { if (Hf.DEVELOPMENT) { if (computableItem) { Hf.log(`error`, `DataCursorElement.describeItem.asObservable - Cannot redescribe computable data item key:${key} at pathId:${pathId}.`); } } condition = Hf.isObject(condition) ? condition : {}; subscriber = Hf.isObject(subscriber) ? subscriber : {}; const descriptor = cursor._descriptor.select(`observable`); const descPreset = { key, condition, subscriber }; if (!descriptor.hasDescription(pathId)) { descriptor.addDescription(pathId).assign(descPreset).to(cursor._content); } else { Hf.forEach(condition, (trigger, conditionKey) => { descriptor.getDescription(pathId).addCondition(trigger, conditionKey); }); Hf.forEach(subscriber, (handler, handlerKey) => { descriptor.getDescription(pathId).addSubscriber(handler, handlerKey); }); } return this; }, /** * @description - Describe item as a computable. * * @method describeItem.asComputable * @param {array} contexts * @param {function} compute * @return void */ asComputable (contexts, compute) { if (Hf.DEVELOPMENT) { if (!Hf.isArray(contexts)) { Hf.log(`error`, `DataCursorElement.describeItem.asComputable - Input computable contexts are invalid.`); } else if (!Hf.isFunction(compute)) { Hf.log(`error`, `DataCursorElement.describeItem.asComputable - Input compute function is invalid.`); } else if (constrainableItem || observableItem) { Hf.log(`error`, `DataCursorElement.describeItem.asComputable - Cannot redescribe computable to data item key:${key} at pathId:${pathId}.`); } } const descriptor = cursor._descriptor.select(`computable`); const descPreset = { key, contexts, compute }; if (descriptor.hasDescription(pathId)) { descriptor.removeDescription(pathId); } descriptor.addDescription(pathId).assign(descPreset).to(cursor._content); return this; } }; }, /** * @description - At cursor, remove descriptions from an item. * * @method unDescribeItem * @param {string|number} key * @return {object} */ unDescribeItem (key) { const cursor = this; const pathId = `${cursor._pathId}.${key}`; if (Hf.DEVELOPMENT) { if (!cursor.hasItem(key)) { Hf.log(`error`, `DataCursorElement.unDescribeItem - Data item key:${key} at pathId:${pathId} is not defined.`); } } const constrainableItem = cursor.isItemComputable(key); const computableItem = cursor.isItemComputable(key); const observableItem = cursor.isItemObservable(key); return { /** * @description - Undescribe item as a constrainable. * * @method unDescribeItem.asConstrainable * @return void */ asConstrainable () { if (constrainableItem) { cursor._descriptor.select(`constrainable`).removeDescription(pathId); } return this; }, /** * @description - Undescribe item as an observable. * * @method unDescribeItem.asObservable * @return void */ asObservable () { if (observableItem) { cursor._descriptor.select(`observable`).removeDescription(pathId); } return this; }, /** * @description - Undescribe item as a computable. * * @method unDescribeItem.asComputable * @return void */ asComputable () { if (computableItem) { cursor._descriptor.select(`computable`).removeDescription(pathId); } return this; } }; }, /** * @description - At cursor, loop through all data values. * * @method forEach * @param {function} iterator - Iterator function. * @param {object} context - Object to become context (`this`) for the iterator function. * @return void */ forEach (iterator, context) { const cursor = this; if (Hf.DEVELOPMENT) { if (!Hf.isFunction(iterator)) { Hf.log(`error`, `DataCursorElement.forEach - Input iterator callback is invalid.`); } } Hf.forEach(cursor._content, (item, key) => { iterator.call(context, item, key); }); }, /** * @description - At cursor, get data item as schema object. * * @method getSchema * @return {object} */ getSchema () { const cursor = this; return cursor._createSchema(cursor._content); }, /** * @description - At cursor, return data item as plain object. * * @method toObject * @return {object} */ toObject () { const cursor = this; return Hf.clone(cursor._content); }, /** * @description - At cursor, return data item as a JSON string. * * @method toString * @param {boolean} beautified * @return {string} */ toString (beautified = true) { const cursor = this; beautified = Hf.isBoolean(beautified) ? beautified : true; return beautified ? JSON.stringify(cursor.toObject(), null, `\t`) : JSON.stringify(cursor.toObject()); } }; /** * @description - A data cursor element module. * @module DataCursorElement * @param {object} data - DataElement object. * @param {string|array} pathId * @return {object} */ export default function DataCursorElement (data, pathId) { if (Hf.DEVELOPMENT) { if (!Hf.isObject(data)) { Hf.log(`error`, `DataCursorElement - Input data element instance is invalid.`); } else if (!(Hf.isString(pathId) || Hf.isArray(pathId))) { Hf.log(`error`, `DataCursorElement - Input pathId is invalid.`); } } let rootKey = ``; let key = ``; /* parsing pathId and retrive content */ const content = Hf.retrieve(pathId, `.`).from(data._rootContent); /* get the keys from pathId */ if (Hf.isString(pathId)) { [ key ] = Hf.stringToArray(pathId, `.`).reverse(); [ rootKey ] = Hf.stringToArray(pathId, `.`); } if (Hf.isArray(pathId)) { [ rootKey ] = pathId; [ key ] = pathId.slice(0).reverse(); pathId = Hf.arrayToString(pathId, `.`); } /* check that content must be an object or array */ if (Hf.DEVELOPMENT) { if (!(Hf.isObject(content) || Hf.isArray(content))) { Hf.log(`error`, `DataCursorElement - Invalid pathId. Last content in pathId must be an object or array.`); } } const element = Object.create(DataCursorElementPrototype, { _pathId: { value: pathId, writable: true, configurable: false, enumerable: false }, _immutable: { get () { return data._mutation.immutableRootKeys.includes(rootKey); }, configurable: false, enumerable: false }, _rootKey: { value: rootKey, writable: true, configurable: false, enumerable: false }, _key: { value: key, writable: true, configurable: false, enumerable: false }, _content: { value: content, writable: true, configurable: false, enumerable: false }, _descriptor: { value: data._descriptor, writable: false, configurable: false, enumerable: false }, _data: { value: data, writable: false, configurable: false, enumerable: false } }); if (Hf.DEVELOPMENT) { if (!Hf.isObject(element)) { Hf.log(`error`, `DataCursorElement - Unable to create a data cursor element instance.`); } } return element; }