UNPKG

@gmetrixr/rjson

Version:
742 lines (741 loc) 32.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RecordUtils = exports.RecordFactory = void 0; const RecordNode_1 = require("./RecordNode"); const RecordTypes_1 = require("./RecordTypes"); const gdash_1 = require("@gmetrixr/gdash"); const { mapValuesToOrder, deepClone, generateId } = gdash_1.jsUtils; const { getSafeAndUniqueRecordName } = gdash_1.stringUtils; /** * A convenient Factory class to maninpulate a RecordNode object of any type * This class can be extended to provide any recordType specific funcitonality * * Using arrow functions in Classes has a runtime performance cost. The constructor bloats up. * Solution: https://www.typescriptlang.org/docs/handbook/2/classes.html#this-parameters */ class RecordFactory { constructor(json) { this._json = json; if ((0, RecordTypes_1.isRecordType)(json.type)) { this._type = json.type; } else { throw Error(`json.type is not a known RecordType`); } return this; } /** * Returns the value of a property, or it default in case the value isn't defined. * In case there is no default defined, it returns "undefined" */ getValueOrDefault(property) { //In case actual value exists, return that if (this.get(property) !== undefined) { return this.get(property); } else { return this.getDefault(property); } } /** * Returns a clone default value of a property. If no default is found, returns undefined * Note: the returned object is a cloned value to avoid reuse of references across r objects */ getDefault(property) { const defaultValues = RecordTypes_1.recordTypeDefinitions[this._type].defaultValues; if (defaultValues[property] === undefined) return undefined; return deepClone(defaultValues[property]); } json() { return this._json; } getId() { return this._json.id; } getName() { return this._json.name; } /** A list of recordNode.props' keys in the json */ getProps() { return Object.keys(this._json.props); } /** A list of Records this json has (eg: project might have scene, variable, menu) */ getROMTypes() { var _a; return Object.keys((_a = this._json.records) !== null && _a !== void 0 ? _a : {}); } /** A list of props this RecordType is supposed to have */ getRecordTypeProps() { return Object.keys(RecordTypes_1.rtp[this._type]); } /** In case a property isn't defined in the json, this method returns "undefined" */ get(property) { return this._json.props[property]; } set(property, value) { this._json.props[property] = value; return this; } delete(property) { delete (this._json.props)[property]; return this; } /** get RecordOrderedMap for a particular sub record, of the shape {map: {}, order: []} - */ getROM(type) { var _a; return (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[type]; } getRecordMap(type) { var _a, _b, _c; return (_c = (_b = (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[type]) === null || _b === void 0 ? void 0 : _b.map) !== null && _c !== void 0 ? _c : {}; } getRecordOrder(type) { var _a, _b, _c; return (_c = (_b = (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[type]) === null || _b === void 0 ? void 0 : _b.order) !== null && _c !== void 0 ? _c : []; } getRecord(type, id) { var _a, _b, _c; return (_c = (_b = (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[type]) === null || _b === void 0 ? void 0 : _b.map) === null || _c === void 0 ? void 0 : _c[id]; } getRecords(type) { var _a, _b, _c, _d; return mapValuesToOrder((_b = (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[type]) === null || _b === void 0 ? void 0 : _b.map, (_d = (_c = this._json.records) === null || _c === void 0 ? void 0 : _c[type]) === null || _d === void 0 ? void 0 : _d.order); } changeRecordId(type, id, newId) { const record = this.getRecord(type, id); if (record === undefined) return undefined; if (newId === undefined) newId = generateId(); //Need to update at three places. 1) within record. 2) in the RecordOrderedMap.map's key 3) In the RecordOrderedMap.order array //1) Within record record.id = newId; //2) in the RecordOrderedMap.map's key const recordOrderedMap = this.getROM(type); recordOrderedMap.map[newId] = recordOrderedMap.map[id]; delete recordOrderedMap.map[id]; //3) In the RecordOrderedMap.order array const index = recordOrderedMap.order.indexOf(id); recordOrderedMap.order[index] = newId; return record; } changeRecordName(type, id, newName) { const record = this.getRecord(type, id); if (record === undefined) { return undefined; } const defaultName = RecordTypes_1.recordTypeDefinitions[type].defaultName; if (defaultName === undefined) { //This means that this type doesn't use name return undefined; } if (newName === undefined) { newName = defaultName; } const existingNames = this.getRecords(type) .filter(r => r.id !== id) //remove the same record itself .map(n => n.name) //convert records to names .filter(name => name !== undefined); //remove undefined names if (existingNames.includes(newName)) { record.name = getSafeAndUniqueRecordName(newName, existingNames); } else { record.name = newName; } return record; } changeDeepRecordName(type, id, newName) { const deepCAndP = this.getDeepChildAndParent(type, id); if (deepCAndP === undefined) { return undefined; } const parentF = new RecordFactory(deepCAndP.p); return parentF.changeRecordName(type, id, newName); } changePropertyName(propertyName, newPropertyName) { //@ts-ignore if (this._json.props[propertyName] !== undefined) { //@ts-ignore this._json.props[newPropertyName] = this._json.props[propertyName]; //@ts-ignore delete this._json.props[propertyName]; } return this; } deleteProperty(propertyName) { //@ts-ignore if (this._json.props[propertyName]) { //@ts-ignore delete this._json.props[propertyName]; } return this; } addBlankRecord(type, position) { const record = (0, RecordNode_1.createRecord)(type); this.addRecord(record, position); return record; } addRecord(record, position) { var _a, _b; if (!(0, RecordTypes_1.isRecordType)(record.type)) { console.error(`Unable to add record because record type ${record.type} isn't a known record type`); return undefined; } const recordOrderedMap = (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[record.type]; if (recordOrderedMap === undefined) { //Check if this type of sub-record is supposed to exist in this type if (!(0, RecordTypes_1.isTypeChildOf)(this._type, record.type)) { console.log(`The type ${this._json.type} doesn't allow addition of ${record.type} records`); return undefined; } if (this._json.records === undefined) { this._json.records = {}; } this._json.records[record.type] = { order: [record.id], map: { [record.id]: record } }; } else { const defaultName = RecordTypes_1.recordTypeDefinitions[record.type].defaultName; if (defaultName !== undefined) { //This means that this type uses name const existingNames = Object.values(recordOrderedMap.map).map(n => n.name).filter(name => name !== undefined); //If this name is already used, overwite the name with a new one const potentialName = (_b = record.name) !== null && _b !== void 0 ? _b : defaultName; if (existingNames.includes(potentialName)) { record.name = getSafeAndUniqueRecordName(potentialName, existingNames); } } //If a RecordNode with the same id already exists, overwrite the id of the incoming object with a new one if (recordOrderedMap.map[record.id] !== undefined) { record.id = generateId(); } position = position !== undefined ? position : recordOrderedMap.order.length; recordOrderedMap.order.splice(position, 0, record.id); recordOrderedMap.map[record.id] = record; } return record; } duplicateRecord(type, id) { var _a; const recordOrderedMap = (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[type]; if (recordOrderedMap === undefined) return undefined; const orig = recordOrderedMap.map[id]; if (orig === undefined) return undefined; let origPosition = recordOrderedMap.order.indexOf(id); if (origPosition === -1) { origPosition = recordOrderedMap.order.length - 1; } const clonedJson = deepClone(orig); //addRecord makes sure that the id of the record itself isn't duplicate amongst its siblings return this.addRecord(clonedJson, origPosition + 1); } duplicateDeepRecord(type, id) { const deepCAndP = this.getDeepChildAndParent(type, id); if (deepCAndP === undefined) { return undefined; } const parentF = new RecordFactory(deepCAndP.p); return parentF.duplicateRecord(type, id); } deleteRecord(type, id) { var _a; const recordOrderedMap = (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[type]; if (recordOrderedMap === undefined) { return undefined; } const nodeIndex = recordOrderedMap.order.indexOf(id); if (nodeIndex >= 0) { recordOrderedMap.order.splice(nodeIndex, 1); } const deletedRecord = recordOrderedMap.map[id]; delete recordOrderedMap.map[id]; return deletedRecord; } deleteDeepRecord(type, id) { const deepCAndP = this.getDeepChildAndParent(type, id); if (deepCAndP === undefined) { return undefined; } const parentF = new RecordFactory(deepCAndP.p); return parentF.deleteRecord(type, id); } /** * Used in drag-drop operations * Allows moving multiple items at the same time * Changes order in place * * Input: [1, 2, 3, 4, 5, 6] * Operation: nodeIds: [2,4], position: 5 * Output: [1, 3, 5, 6, 2, 4] */ transposeRecords(type, ids, position) { var _a; const recordOrderedMap = (_a = this._json.records) === null || _a === void 0 ? void 0 : _a[type]; if (recordOrderedMap === undefined) { return undefined; } if (ids.length === 0) { return; } const order = recordOrderedMap.order; for (let i = 0; i < ids.length; i++) { const nodeId = ids[i]; const currentIndex = order.indexOf(nodeId); // delete node from position const deletedRecords = order.splice(currentIndex, 1); // if element's current index is > destinationPos, then we need to insert in increasing order if (currentIndex > position) { position += i; } // insert into correct place order.splice(position, 0, deletedRecords[0]); } } getDeepChildAndParent(type, id) { if (this._json.records === undefined) { return; } // getRecords runs over records map, if it is not present, we've hit a leaf node. const child = this.getRecord(type, id); if (child !== undefined) { return { c: child, p: this._json }; } //Search in all records for (const recordType of Object.keys(this._json.records)) { if (!(0, RecordTypes_1.isTypeSubChildOf)(this._type, type)) { continue; } for (const record of this.getRecords(recordType)) { const recordF = new RecordFactory(record); const deepCAndP = recordF.getDeepChildAndParent(type, id); if (deepCAndP !== undefined) { return deepCAndP; } } } } getAllDeepChildrenIds(type) { const idsToReturn = []; if (this._json.records === undefined) { return idsToReturn; } for (const recordType of Object.keys(this._json.records)) { if (recordType === type) { idsToReturn.push(...this.getRecordOrder(type)); } if (!(0, RecordTypes_1.isTypeSubChildOf)(this._type, type)) { continue; } for (const record of this.getRecords(recordType)) { //Go a level deeper if available const recordDeepChildrenIds = new RecordFactory(record).getAllDeepChildrenIds(type); idsToReturn.push(...recordDeepChildrenIds); } } return idsToReturn; } getAllDeepChildren(type) { const recordsToReturn = []; if (this._json.records === undefined) { return recordsToReturn; } for (const recordType of Object.keys(this._json.records)) { if (recordType === type) { recordsToReturn.push(...this.getRecords(type)); } if (!(0, RecordTypes_1.isTypeSubChildOf)(this._type, type)) { continue; } for (const record of this.getRecords(recordType)) { //Go a level deeper if available const recordDeepChildren = new RecordFactory(record).getAllDeepChildren(type); recordsToReturn.push(...recordDeepChildren); } } return recordsToReturn; } /** * Documentation for filter predicate: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#description */ getAllDeepChildrenIdsWithFilter(type, predicate) { const idsToReturn = []; if (this._json.records === undefined) return idsToReturn; for (const recordType of Object.keys(this._json.records)) { if (recordType === type) { idsToReturn.push(...this.getRecords(recordType).filter(predicate).map(r => r.id)); } if (!(0, RecordTypes_1.isTypeSubChildOf)(this._type, type)) { continue; } for (const record of this.getRecords(recordType)) { //Go a level deeper if available const recordDeepChildrenIds = new RecordFactory(record).getAllDeepChildrenIdsWithFilter(type, predicate); idsToReturn.push(...recordDeepChildrenIds); } } return idsToReturn; } getAllDeepChildrenWithFilter(type, predicate) { const recordsToReturn = []; if (this._json.records === undefined) return recordsToReturn; for (const recordType of Object.keys(this._json.records)) { if (recordType === type) { recordsToReturn.push(...this.getRecords(recordType).filter(predicate)); } if (!(0, RecordTypes_1.isTypeSubChildOf)(this._type, type)) { continue; } for (const record of this.getRecords(recordType)) { const recordDeepChildren = new RecordFactory(record).getAllDeepChildrenWithFilter(type, predicate); recordsToReturn.push(...recordDeepChildren); } } return recordsToReturn; } /** * Returns the object that needs to be stringified before sending to clipboard * Needs to be overriden at Project/Scene level to ensure that * 1) Vars are copied correctly (in case of scene copy). Vars pasting will be a bit different - no point changing ids. * 2) element ids don't conflict at any depth while pasting * 3) To ensure Ctrl+V of scene works even within another scene (ie project's paste gets used) * * * For clipboard operation, this function can only be called at the parent level * * For scene copy, call to r.project() * * For element copy, call to r.scene() * * Scene copy logic: * In scene rules, vars can be referenced via ids or via variable names (in templates used in elements) * Copy all variables referenced. * * Scene paste logic: * For EACH variable which was copied * - If the variable id and name doesn't exist - paste it * - If the variable id exists, but name doesn't - ignore it. (To make this work, we need to go to each string template used in rules * and elements in the new scene, and change the templates to use the new name) * - If the variable id doesn't exist, but name does - replace the variable id in the new scene being pasted (in all rules) * with the new variable id * - If both the variable id and name exist - ignore it */ copyToClipboardObject(ids) { //Keep searching for children until we get the same id const romTypes = this.getROMTypes(); const foundRecordNodes = []; for (const romType of romTypes) { foundRecordNodes.push(...this.getAllDeepChildrenWithFilter(romType, (value => ids.includes(value.id)))); } return { parentType: this._type, nodes: foundRecordNodes, }; } /** * Once the clipboard has been converted into ClipboardR, this function can be used to merge into parent RecordNode */ pasteFromClipboardObject({ obj, position }) { if (obj.parentType !== this._type) { console.error(`Can't paste this object into a RecordNode of type of ${this._type}`); return; } for (const rn of obj.nodes) { // * if position is passed, then keep incrementing to insert in order, else add at the end of the list this.addRecord(rn, position ? position++ : position); } } /** * Add moveRecords in RecordFactory. ProjectFactory will override this to add rules logic. * moveRecords(selectIds: [{id: , path: [recordId, parentRecordId, parent2RecordId....]}], destiation: {id: ,path: []}, destinationPosition) */ /** * RFC: https://docs.google.com/document/d/1DVM_i_Go5iX5-EShV5FikfI29k8YEC9cAjzeAY49blc/edit# * get the address of the RecordNode. In case parentAddr is not passed (root levels) then self address is returned */ getSelfRecordAddress(parentAddr) { const selfAddress = `${this._type}:${this.getId()}`; return parentAddr ? `${parentAddr}|${selfAddress}` : selfAddress; } /** * get the address of a reacord node property. incase when index is passed, return address to indexed property * 1. project:1|scene:1|element:2!opacity * 2. project:1|scene:1|element:2!wh>1 */ getPropertyAddress(recordAddress, property, index) { const recordPropertyAddress = `${recordAddress}!${property}`; return (typeof index === "number") ? `${recordPropertyAddress}>${index}` : recordPropertyAddress; } /** * Find the record at a given address. Searches only in child record nodes. * Assumes that 1st entry in addr is self * * examples for ref * 1. project:1|scene:1 * 2. project:1|scene:1|element:2 * 3. project:1|scene:1|element:2!opacity * 4. project:1|scene:1|element:2!wh>1 * */ getRecordAtAddress(addr) { // * sanitize and remove and unwanted cases when addr contains strings for property lookup // * replace everything after a ! with a blank string const recordsArray = addr.replace(/!.*/, "").split("|"); // [project:1, scene:1, element:2] if (recordsArray.length === 0 || this._json.records === undefined) { return null; } //* If the length of the address = 1 and the type matches, return self else return null if (recordsArray.length === 1) { const rootRecord = recordsArray[0].split(":"); // [project, 1] -> RT[T], id return rootRecord[0] === this._type ? this._json : null; } /** * We start the loop from the 2nd entry with index = 1 * Loop through the whole address array to find the correct RecordNode. * At any point if a child record node is not found, return null (cases when addr is not in-sync with json structure) * * This uses a for loop to find all child entries instead of a recursive loop to avoid recomputing the addr passed in the recursive call. * The for loop runs in O(n) and lookups using recordF.getRecord are O(1). Total complexity is O(n) [of address and not records]. */ let currentRecord = this._json; for (let i = 1; i < recordsArray.length; i++) { const record = recordsArray[i]; const [type, id] = record.split(":"); // [scene, 1] const recordF = new RecordFactory(currentRecord); // Number(undefined) = NaN, so this will work. Complexity of checking isNaN and hashmap lookup are same. const child = recordF.getRecord(type, Number(id)); if (!child) { return null; } currentRecord = child; } return currentRecord; } /** * Find the record at a given address. Searches only in child record nodes. * Returns both self and parent * Assumes that 1st entry in addr is self * * examples for ref * 1. project:1|scene:1 * 2. project:1|scene:1|element:2 * 3. project:1|scene:1|element:2!opacity * 4. project:1|scene:1|element:2!wh>1 * */ getRecordAndParentAtAddress(addr) { // * sanitize and remove and unwanted cases when addr contains strings for property lookup // * replace everything after a ! with a blank string const recordsArray = addr.replace(/!.*/, "").split("|"); // [project:1, scene:1, element:2] if (recordsArray.length === 0 || this._json.records === undefined) { return null; } //* If the length of the address = 1 and the type matches, return self else return null if (recordsArray.length === 1) { const rootRecord = recordsArray[0].split(":"); // [project, 1] -> RT[T], id return rootRecord[0] === this._type ? { c: this._json } : null; } /** * We start the loop from the 2nd entry with index = 1 * Loop through the whole address array to find the correct RecordNode. * At any point if a child record node is not found, return null (cases when addr is not in-sync with json structure) * * This uses a for loop to find all child entries instead of a recursive loop to avoid recomputing the addr passed in the recursive call. * The for loop runs in O(n) and lookups using recordF.getRecord are O(1). Total complexity is O(n) [of address and not records]. */ let currentRecord = this._json; let parentRecord = undefined; for (let i = 1; i < recordsArray.length; i++) { const record = recordsArray[i]; const [type, id] = record.split(":"); // [scene, 1] const recordF = new RecordFactory(currentRecord); // Number(undefined) = NaN, so this will work. Complexity of checking isNaN and hashmap lookup are same. const child = recordF.getRecord(type, Number(id)); if (!child) { return null; } parentRecord = currentRecord; currentRecord = child; } return { c: currentRecord, p: parentRecord }; } /** * Update the value of a property at an address * examples for ref * 1. project:1|scene:1|element:2!opacity * 2. project:1|scene:1|element:2!wh>1 * * First find the RecordNode using getRecordAtAddress method * if the property address contains an index, check if the property is an array type * 1. if yes, udpate the value at index * 2. else return false * else update property value directly in the RecordNode * */ updatePropertyAtAddress(addr, value) { var _a, _b; const recordAtAddress = this.getRecordAtAddress(addr); // find the matching property value string and then remove the ! from the lead const propertyAddr = (_b = (_a = addr.match(/!.*/)) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.replace("!", ""); // ex: !scene_yaw_correction if (!recordAtAddress || !propertyAddr) { return false; } const recordF = new RecordFactory(recordAtAddress); const [property, index] = propertyAddr.split(">"); // get the definition of this record type and check if the property is supported or not const supportedProperties = RecordTypes_1.rtp[recordAtAddress.type]; if (!Object.values(supportedProperties).includes(property)) { return false; } const propertyValue = recordF.getValueOrDefault(property); // Only update indexed properties if index value is defined in the address if (Array.isArray(propertyValue)) { // isArray of undefined is false, so for undefined data this will work too if (index !== null && index !== undefined) { propertyValue[Number(index)] = value; recordF.set(property, propertyValue); } else { return false; } } else { recordF.set(property, value); } return true; } /** * This allows re-parenting records between different depths inside a tree using their addresses. * This needs to be called for the record where both sourceParentRecord and destParentRecord are child records of a common super parent record * For almost all operations, we will use a top down approach here for re-parenting, i.e re-parenting process needs to start from the top most parent * in this case, it is almost always project * Example1: moving elements from within a group to 1 level above. * * Scene1 * Element1 <-------| * Element2 | * Element3 (group) | * Element31 -----| * Element32 * * In above scenario we want to move Element31 below Element1 * sourceRecordAddr = [{parentAddr: Scene:1|Element:3, recordAddr: Scene:1|Element:3|Element:31}] * destParentAddr = Scene:1 * destPosition = 0 (we insert at x+1 index) * * Example2: moving elements from one scene to another * * Scene1 * Element1 * Element2 * Element3 (group) * Element31 * Element32 <-| * Scene2 | * Element4 ------| * Element5 * * In above scenario we want to move Element4 from Scene2 to Element3(Group) * sourceRecordAddr = [{parentAddr: Scene:2, recordAddr: Scene:2|Element:4}] * destParentAddr = Scene:1|Element:3 * destPosition = 1 (we insert at x+1 index) * */ reParentRecordsWithAddress(destParentAddr, sourceRecordAddr, destPosition) { const destParentRecord = this.getRecordAtAddress(destParentAddr); const reParentedRecords = []; const failedReParentedRecords = []; if (destParentRecord === null) { console.error(`[reParentRecordsWithAddress]: Error in re-parenting. destParentAddr: ${destParentAddr}`); return [reParentedRecords, failedReParentedRecords]; } const destinationParentRecordF = new RecordFactory(destParentRecord); for (const s of sourceRecordAddr) { const sourceRecord = this.getRecordAtAddress(s.recordAddr); const sourceParentRecord = this.getRecordAtAddress(s.parentAddr); if (sourceRecord === null || sourceParentRecord === null) { console.error(`[delete-sourceRecordAddresses]: can't find record/parent for : recordAddr: ${s.recordAddr} parentAddr: ${s.recordAddr}`); continue; } /** * The order of operations here is very important * 1. add the record in the new parent * 2. delete the record from older parent * * We do this in this order and not reverse since there can be records that don't qualify to be child of a parent * for ex: a scene can't be a child of a group * in this case, we don't re-parent the record at all and addRecord function returns undefined. * A better UX for this would be to restrict the user to be able to do that at all in the UI */ // * Add to destination parent // * addRecord takes care of name clashes and id clashes const addedRecord = destinationParentRecordF.addRecord(sourceRecord, destPosition); // * Record was added correctly to the appropriate parent if (addedRecord !== undefined) { // * delete the record from resp parents const sourceParentRecordF = new RecordFactory(sourceParentRecord); sourceParentRecordF.deleteRecord(sourceRecord.type, sourceRecord.id); reParentedRecords.push(addedRecord); } else { failedReParentedRecords.push(sourceRecord); } } return [reParentedRecords, failedReParentedRecords]; } /** * This returns all the nodes in the path from a parent to a leaf */ getBreadcrumbs(id, type) { const recordsToReturn = []; const deepCAndP = this.getDeepChildAndParent(type, id); if (deepCAndP === undefined) { recordsToReturn.push(this.json()); return recordsToReturn; } // we've reached the parent if (deepCAndP.p.id === this.getId() && deepCAndP.p.type === this._type) { recordsToReturn.push(deepCAndP.p); recordsToReturn.push(deepCAndP.c); } else { // this is a different parent, lets find it's parent recordsToReturn.push(...this.getBreadcrumbs(deepCAndP.p.id, deepCAndP.p.type)); recordsToReturn.push(deepCAndP.c); } return recordsToReturn; } /** * Get address for a deep record */ getDeepRecordAddress({ id, type, parentAddr }) { const breadcrumbs = this.getBreadcrumbs(id, type); let address = ""; for (let i = 0; i < breadcrumbs.length; i++) { const b = breadcrumbs[i]; address += `${b.type}:${b.id}`; // if this isnt the last entry, then append `|` if (i !== breadcrumbs.length - 1) { address += "|"; } } return parentAddr ? `${parentAddr}|${address}` : address; } /** * Get record + parent address for a given record */ getDeepChildAndParentAddress(id, type) { const deepChildAndParent = this.getDeepChildAndParent(type, id); if (deepChildAndParent === undefined) { return undefined; } const recordAddress = this.getDeepRecordAddress({ id, type }); const parentAddress = this.getDeepRecordAddress({ id: deepChildAndParent.p.id, type: deepChildAndParent.p.type }); return [recordAddress, parentAddress]; } } exports.RecordFactory = RecordFactory; class RecordUtils { } exports.RecordUtils = RecordUtils; RecordUtils.getDefaultValues = (type) => RecordTypes_1.recordTypeDefinitions[type].defaultValues;