recordarray
Version:
Array Class Extension for Records
414 lines (413 loc) • 15.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_RECORD = exports.DEFAULT_OPTIONS = void 0;
exports.DEFAULT_OPTIONS = {};
Object.freeze(exports.DEFAULT_OPTIONS);
exports.DEFAULT_RECORD = {};
Object.freeze(exports.DEFAULT_RECORD);
/** RecordArray
*
* @description: An extension of Array that provides record processing related methods
* @author Francis Carelse
* @version 0.0.11
*/
class RecordArray extends Array {
/**
* @constructor
* @param {Array<Record>} array (optional)
* @param {Object} options (optional)
*/
constructor(array = [], options = exports.DEFAULT_OPTIONS) {
super();
if (!(options instanceof Object))
options = exports.DEFAULT_OPTIONS;
if (options.data instanceof Array)
array = options.data;
if (!(array instanceof Array))
array = [];
array.forEach(record => this.push(Object.assign({}, record)));
}
static new(array = [], options = exports.DEFAULT_OPTIONS) {
return new RecordArray(array, options);
}
findBy(field, value, options = exports.DEFAULT_OPTIONS) {
// Create a RecordArray to be returned
let arr = new RecordArray();
options = options instanceof Object ?
Object.assign({}, exports.DEFAULT_OPTIONS, options) :
exports.DEFAULT_OPTIONS;
// If no parameters then return empty RecordArray.
if (value === undefined) {
if (options.returnIndex)
return -1;
else if (options.returnFirst) {
return this.findByID(0, options) ||
this.findByTag('', options) ||
(options.def !== undefined ?
options.def :
exports.DEFAULT_RECORD);
}
// If value not defined then just return the empty array
return arr;
}
// Force strict option to boolean
options.strict = !!options.strict;
// If null or undefined value to search for then enforce strict equality
if (value === null)
options.strict = true;
// set which result to return;
let nth = options.nth || 1;
// Go through all records
for (let i = 0; i < this.length; i++) {
let record = this[i];
// Find a matching field
field = Object.keys(record).filter(key =>
// Check the trim option
options.trim ?
// Compare field with trimmed key
key.trim() == field :
// Otherwise compare field with key
key == field)[0];
const compared = options.trim ?
String(record[field]).trim() :
record[field];
if (
// field should not be undefined
field !== undefined &&
// stored value is not undefined
compared !== undefined &&
// and apply strictness in comparison as per option between stored value and matching value
((!options.strict && compared == value) || Object.is(compared, value))
// Then append record to return RecordArray
) {
if (!--nth || options.returnFirst) {
if (options.returnIndex)
return i;
else
return record;
}
arr.push(record);
}
}
// Return resultant RecordArray or unfound return value.
if (options.returnIndex)
return -1;
else if (options.returnFirst)
return options.def !== undefined ?
options.def :
{};
else
return arr;
}
new(array) {
return new RecordArray(array);
}
;
async asyncEach(cb) {
for (let i = 0; i < this.length; i++) {
await cb(this[i], i);
}
}
;
findByID(value, options = exports.DEFAULT_OPTIONS) {
return this.findBy("id", value, options);
}
findByTag(value, options = exports.DEFAULT_OPTIONS) {
return this.findBy("tag", value, options);
}
findOne(key, value, options = exports.DEFAULT_OPTIONS) {
return this.findBy(key, value, { ...options, returnFirst: true });
}
findOneByID(value, options = exports.DEFAULT_OPTIONS) {
return this.findBy('id', value, { ...options, returnFirst: true });
}
findOneByTag(value, options = exports.DEFAULT_OPTIONS) {
return this.findBy('tag', value, { ...options, returnFirst: true });
}
indexBy(field, value, options = exports.DEFAULT_OPTIONS) {
return this.findBy(field, value, { ...options, returnIndex: true });
}
indexByID(value, options = exports.DEFAULT_OPTIONS) {
return this.indexBy("id", value, options);
}
indexByTag(value, options = exports.DEFAULT_OPTIONS) {
return this.indexBy("tag", value, options);
}
matchBy(key, values) {
var arr = new RecordArray();
// Undefined values means no matches.
if (values === undefined)
return arr;
// ensure values is an array. Insert into new array and assign if need be.
if (!(values instanceof Array))
values = [values];
// flatten values array;
values = [...values];
for (var i = 0; i < values.length; i++)
arr[i] = this.findOne(key, values[i]);
return arr;
}
sortBy(field, order = 'ASC') {
// Assert field parameter is a string.
if (typeof field !== "string")
throw new TypeError("String expected for first parameter.");
// Assert order parameter is ASC or DESC
if (!['ASC', 'DESC'].includes(order))
throw new TypeError("'ASC' or 'DESC' expected for second parameter.");
// Return sorted using appropriate function
return this.sort(order.toUpperCase() == 'ASC' ? sortFnASC : sortFnDESC);
// Sorting Ascending Strategy
function sortFnASC(a, b) {
return a[field] == b[field] ?
0 : (a[field] > b[field] ? 1 : -1);
}
// Sorting Descending Strategy (just reverse the testing parameters)
function sortFnDESC(a, b) {
return sortFnASC(b, a);
}
}
/**
* Sort this RecordArray by a set of fields in ascending order
* Takes an array of strings or a space separated string of fieldnames
* @param {Array<String> | String} fields
*/
sortASC(fields) {
// If fields parameter is not already an Array
if (!(fields instanceof Array))
// Ensure is string and split space separated fieldnames
fields = fields.toString().split(" ");
// Throw out any non string fields
fields = fields.filter(f => typeof f === "string");
// If no fields left then abort
if (!fields.length)
throw new TypeError('Parameter "fields" needs to be an array of strings or space separated list of field names');
// Return sort using item pair evaluation strategy
return this.sort(function (a, b) {
// Iterate over fields list
for (var i = 0; i < fields.length; i++)
// Sequentially check for the first instance of inequality
if (a[fields[i]] != b[fields[i]])
// If wrong order then pass back 1 otherwise -1
return a[fields[i]] > b[fields[i]] ? 1 : -1;
// All fields are equal so return 0 for matching
return 0;
});
}
/**
* Sort this RecordArray by a set of fields in descending order
* Takes an array of strings or a space separated string of fieldnames
* @param {Array<String> | String} fields
*/
sortDESC(fields) {
// If fields parameter is not already an Array
if (!(fields instanceof Array))
// Ensure is string and split space separated fieldnames
fields = fields.toString().split(" ");
// Throw out any non string fields
fields = fields.filter(f => typeof f === "string");
// If no fields left then abort
if (!fields.length)
throw new TypeError('Parameter "fields" needs to be an array of strings or space separated list of field names');
// Return sort using item pair evaluation strategy
return this.sort(function (a, b) {
// Iterate over fields list
for (var i = 0; i < fields.length; i++)
// Sequentially check for the first instance of inequality
if (a[fields[i]] != b[fields[i]])
// If wrong order then pass back 1 otherwise -1
return a[fields[i]] < b[fields[i]] ? 1 : -1;
// All fields are equal so return 0 for matching;
return 0;
});
}
/**
* Clone this RecordArray or supplied Array of reords to a new RecordArray
* @param {Array} arr
*/
clone(arr) {
// If no source array supplied then use this one
arr = arr || this;
// Create new RecordArray
var clone = new RecordArray();
for (var i = 0; i < arr.length; i++)
clone.push(Object.assign({}, arr[i]));
return clone;
}
/**
* @returns 'Array of cloned records'
*/
toArray() {
// Clone to an Array
return this.map(record => Object.assign({}, record));
}
getName(id) {
var records = this.findBy("id", id);
if (records.length === 0)
return false;
else if (records.length > 0)
return records[0].name;
}
;
getNameByTag(tag) {
var records = this.findBy("tag", tag);
if (records.length === 0)
return false;
else if (records.length > 0)
return records[0].name;
}
;
/**
* List all values of a specified field
* @param field string: Key to use for searching records
* @param options object (optional): Additional parameters for the list operation
* options parameter can be boolean and will be used for the trim option
*/
listValues(field = 'id') {
// Test field is string primitive or string object.
// Create a RecordArray to be returned
var arr = [];
// Use index 'i' for all index values
for (let i = 0; i < this.length; i++) {
// stored value is not undefined
if (!!this[i][field])
// Then append value to returned array
arr.push(this[i][field]);
}
// Return resultant RecordArray
return arr;
}
// @ts-ignore TBD
create(data, options = {}) {
throw Error("Function yet to be developed");
}
// @ts-ignore TBD
read(data, options = {}) {
throw Error("Function yet to be developed");
}
// @ts-ignore TBD
update(data, options = {}) {
throw Error("Function yet to be developed");
}
// @ts-ignore TBD
delete(data, options = {}) {
throw Error("Function yet to be developed");
}
// @ts-ignore TBD
list(data, options = {}) {
throw Error("Function yet to be developed");
}
// faulty. Comparing objects at the moment not keys or value.
static compareRecords(record1, record2, strict) {
// Default "strict" to true
if (strict !== false)
strict = true;
// Compare Keys
let keys1 = Object.keys(record1).sort();
let keys2 = Object.keys(record2).sort();
if (strict && keys1.length !== keys2.length)
return false;
// Compare keys
if (!keys1.every((value, index) => value === keys2[index]))
return false;
// Compare values
if (!keys1.every((key, index) => record1[key] === record2[key]))
return false;
return true;
}
// indexBy(field: string = 'id',value: any, strict: boolean = false){
// return this.findBy(field, value, {returnIndex: true, strict});
// }
unique(field = 'id', strict = false) {
// Compare current index with index of first occurence of record with field with that value)
this.filter((e, i) => this.indexBy(field, e[field], { strict }) == i);
return this;
}
;
uniqueBy(field, strict = false) {
// Compare current index with index of first occurence of record with field with that value)
return this.filter((record, i) => this.indexBy(field, record[field], { strict }) == i);
}
;
uniqueIDs(strict) {
return this.unique('id', strict).listValues('id');
}
;
hasRecord(record) {
if (!!record.id)
return !!this.findOneByID(record.id);
else if (!!record.tag)
return !!this.findOneByTag(record.tag);
else
return false;
}
/**
* Extend the RecordArray array by updating or creating based on matching ID
*/
extend(arr) {
arr.forEach(record => {
if (this.hasRecord(record))
this.update(record);
else
this.push(record);
});
return this.sortASC('id');
}
;
topID() {
// Iterate over this recordArray and reduce all IDs to the largest.
return this.reduce((record, topID) => topID > record.id ?
topID :
record.id, 0);
}
merge(arr) {
arr.forEach(r => this.push(r));
return this;
}
;
/*
* @description: Comparing 2 RecordArrays
* @author: Francis Carelse
* @param RA1: RecordArray
* @param RA2: RecordArray
* @param strict: Boolean will enforce second RecordArray only has the same records
* @param identical: Boolean will enforce each record by index is compared
* @returns: Boolean true if equal
* @note:
*/
static compare = (RA1, RA2, options) => {
// Assert RA1 is an Array
if (!(RA1 instanceof Array))
throw new TypeError("Parameter 1 must be Array or RecordArray");
// Assert RA2 is an Array
if (!(RA2 instanceof Array))
throw new TypeError("Parameter 2 must be Array or RecordArray");
// Ensure there is an options object
if (!(options instanceof Object)) {
// Check if boolean to become the strict option
if (options instanceof Boolean || typeof options == 'boolean')
// Convert options to object with boolean value as strict option.
options = { strict: options };
else
// Set options to new basic parameters object
options = {};
}
// Force strict option to boolean
options.strict = !!options.strict;
// Force identical option to boolean
options.identical = !!options.identical;
// Compare Lengths of unique IDs.
if (options.strict && RA1.unique().length !== RA2.unique().length)
return false;
// Compare records
if (options.identical) {
if (!RA1.every((record, index) => RecordArray.compareRecords(record, RA2[index], options.strict)))
return false;
}
else {
if (!RA1.every(record => RecordArray.compareRecords(record, RA2.findOne("id", record.id), options.strict)))
return false;
}
return true;
};
}
exports.default = RecordArray;