huddle-record
Version:
Record is a Object-Relational Mapping (ORM) TypeScript plugin that makes it easy to work with data.
303 lines (302 loc) • 10.7 kB
JavaScript
import Query from './query.js';
export default class Repository {
model;
// by default all data is saved to the 'all' key
// however, you can pass in a key to save the data to a different key
data = {
all: [],
};
constructor(model) {
this.model = model;
}
// find an item in the store by id
find(id, options) {
if (!this.datasetExists(options?.dataset)) {
return null;
}
const match = this.data[options?.dataset ?? 'all'].find((item) => item.id === id);
return match ?? null;
}
// return all data in the store
all(options) {
if (!this.datasetExists(options?.dataset)) {
return [];
}
return this.data[options?.dataset ?? 'all'];
}
// return the first item in the store
first(options) {
if (!this.datasetExists(options?.dataset)) {
return null;
}
const key = options?.dataset ?? 'all';
if (this.data[key].length === 0) {
return null;
}
return this.data[key][0];
}
// return the last item in the store
last(options) {
if (!this.datasetExists(options?.dataset)) {
return null;
}
const key = options?.dataset ?? 'all';
if (this.data[key].length === 0) {
return null;
}
return this.data[key][this.data[key].length - 1];
}
// take n number of items from the store
take(amount, options) {
if (!this.datasetExists(options?.dataset)) {
return [];
}
const key = options?.dataset ?? 'all';
if (this.data[key].length === 0) {
return [];
}
return this.data[key].slice(0, amount);
}
// return the count of the data in the store
count(options) {
if (!this.datasetExists(options?.dataset)) {
return 0;
}
return this.data[options?.dataset ?? 'all'].length;
}
// return whether model data exists in the store based on an id
exists(id, options) {
if (!this.datasetExists(options?.dataset)) {
return false;
}
return this.find(id, options) !== null;
}
// function that saves the data in the store
save(data, options) {
if (options && options.replace && options.update) {
console.error('You cannot use the replace and update options at the same time');
return null;
}
if (options?.dataset && options.dataset === '') {
options.dataset = 'all';
}
// create the dataset if it doesn't exist
if (options?.dataset && !this.datasetExists(options?.dataset)) {
this.data[options.dataset] = [];
}
const saveOptions = this.returnSaveOptions(options);
return this.transform(data, saveOptions);
}
// delete one or multiple models from the data store by id or by key/value pair
delete(key, value, options) {
if (options?.dataset && options.dataset === '') {
options.dataset = 'all';
}
if (!this.datasetExists(options?.dataset)) {
return;
}
if (Array.isArray(key)) {
key.forEach((item) => {
this.deleteSingleItem(item, value, options);
});
}
else if (typeof key === 'number') {
this.deleteSingleItem(key, value, options);
}
else {
this.deleteSingleItem(key, value, options);
}
}
update(id, data, options) {
if (options?.dataset && options.dataset === '') {
options.dataset = 'all';
}
if (!this.datasetExists(options?.dataset)) {
return null;
}
const item = this.find(id, options);
if (item === null) {
return null;
}
item.beforeUpdate();
this.deepUpdate(item, data, options);
item.afterUpdate();
return this.find(id, options);
}
// deleteSingleItem by id or by key/value pair
deleteSingleItem(key, value, options) {
const dataKey = options?.dataset ?? 'all';
const filterNeedle = typeof key === 'number' ? 'id' : key;
const filterValue = typeof key === 'number' ? key : value;
// grab all items that match the id
const itemsToDelete = this.data[dataKey].filter((item) => item[filterNeedle] === filterValue);
// call beforeDelete on each item
itemsToDelete.forEach((item) => item.beforeDelete());
// filter out the items that do not match the id
this.data[dataKey] = this.data[dataKey].filter((item) => item[filterNeedle] !== filterValue);
// call afterDelete on each item
itemsToDelete.forEach((item) => item.afterDelete());
}
// delete all data from the store (optionally by key)
clear(options) {
if (options?.dataset && options.dataset === '') {
options.dataset = 'all';
}
if (!this.datasetExists(options?.dataset)) {
return;
}
this.data[options?.dataset ?? 'all'] = [];
}
// completely reset the store
reset() {
this.data = {
all: [],
};
}
// transforms json data into an instance of the model
transform(data, options) {
const instanceData = [];
const isArray = Array.isArray(data);
const dataToTransform = isArray ? data : [data];
// loop over data
dataToTransform.forEach((item) => {
let shouldInstantiate = false;
// see if the item already is a model
if (!(item instanceof this.model)) {
// if not, check if the item has an id
const id = item.id;
// don't save if we don't have an id
if (typeof id === 'undefined') {
return;
}
// it's not a model, but it has an id, so we should instantiate it
shouldInstantiate = true;
// check if the item already exists in the store
if (id !== null) {
const match = this.find(id, {
dataset: options.dataset ?? 'all',
});
if (match !== null) {
if (options.update) {
// update the existing model
shouldInstantiate = false;
this.update(id, item, {
dataset: options.dataset ?? 'all',
});
}
else if (options.replace) {
// fire the beforeUpdate event
match.beforeUpdate(data);
}
else {
// a match exists but we don't want to update or replace it, so we don't need to do anything
return;
}
}
}
}
else {
// if it is an existing model, fire the beforeUpdate event
item.beforeUpdate(data);
}
// instantiate the model if needed
const instance = shouldInstantiate ? new this.model(item) : item;
// add the instance to the array
instanceData.push(instance);
});
// don't save if we don't have any data
if (instanceData.length === 0) {
return [];
}
// persist the data if needed
if (options.save) {
this.persist(instanceData, options);
}
// return the data
return isArray ? instanceData : instanceData[0];
}
// add the data to the store
persist(data, options) {
if (Array.isArray(data)) {
const persistedData = [];
data.forEach((item) => {
const persistedItem = this.persistSingleItem(item, options);
if (persistedItem !== null) {
persistedData.push(persistedItem);
}
});
return persistedData;
}
else {
return this.persistSingleItem(data, options);
}
}
// add the data to the store if it doesn't already exist or replace the existing data with the new data
persistSingleItem(data, options) {
const key = options.dataset ?? 'all';
const id = data.id;
// if we don't already have the data in the store, add it
if (!this.find(id, {
dataset: options.dataset ?? 'all',
})) {
if (!this.datasetExists(key)) {
this.data[key] = [];
}
if (typeof options.prepend !== 'undefined') {
options.prepend ? this.data[key].unshift(data) : this.data[key].push(data);
}
else {
this.data[key].push(data);
}
return data;
}
else if (options.replace) {
let match = null;
// replace the existing data with the new data
this.data[key] = this.data[key].map(function (item) {
if (item.id === id) {
match = item;
}
return item.id === id ? data : item;
});
match.afterUpdate(data);
return this.find(id, {
dataset: options.dataset ?? 'all',
});
}
return null;
}
// combine the default save options with the options passed in
returnSaveOptions(options) {
const defaultEndpointOptions = {
replace: true,
update: false,
save: true,
dataset: 'all',
prepend: false,
};
return options ? { ...defaultEndpointOptions, ...options } : defaultEndpointOptions;
}
deepUpdate(item, newData, options) {
// merge the data
const mergedData = {
...item,
...newData,
};
// save the updated one
this.save(mergedData, {
replace: true,
dataset: options?.dataset ?? 'all',
});
// delete the existing record if the id has changed
if (typeof newData.id !== 'undefined' && newData.id !== item.id) {
this.delete(item.id, null, options);
}
}
datasetExists(key) {
return typeof this.data[key ?? 'all'] !== 'undefined';
}
query(options) {
return new Query(this, options?.dataset ?? 'all');
}
}