Skip to content

Commit

Permalink
refactor: code quality changes (#14)
Browse files Browse the repository at this point in the history
* refactor: bad usage of String#match

* refactor: default cases in switch

* refactor: empty async arrow function

* refactor: empty static method 'checkConfiguration'

* refactor: async function without any await expressions

* refactor: shorthand type coercions

* refactor: async function should have await expression

* docs: add jsdoc blocks to the transaction class

* refactor: variable name shadows variable in outer scope
  • Loading branch information
acodeninja authored Sep 24, 2024
1 parent 5f5ad93 commit c0f6db5
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 106 deletions.
2 changes: 1 addition & 1 deletion src/Persist.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class TestEngine {
}
}

test('includes Type', async t => {
test('includes Type', t => {
t.is(Persist.Type, Type);
});

Expand Down
5 changes: 2 additions & 3 deletions src/Query.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@ class Query {
execute(model, index) {
const matchIs = (query) => query?.$is !== undefined;
const matchPrimitive = (query) => ['string', 'number', 'boolean'].includes(typeof query);
const matchContains = (query) => !!query?.$contains;
const matchContains = (query) => query?.$contains !== undefined;

const matchesQuery = (subject, inputQuery = this.query) => {
if (matchPrimitive(inputQuery)) return subject === inputQuery;

if (matchIs(inputQuery))
if (subject === inputQuery.$is) return true;
if (matchIs(inputQuery) && subject === inputQuery.$is) return true;

if (matchContains(inputQuery)) {
if (subject.includes?.(inputQuery.$contains)) return true;
Expand Down
2 changes: 1 addition & 1 deletion src/SchemaCompiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ test('.compile(MainModel).validate(invalidModel) throws a ValidationError', t =>

t.is(error.message, 'Validation failed');

t.true(!!error.data.id.match(/MainModel\/[A-Z0-9]+/));
t.true(new RegExp(/MainModel\/[A-Z0-9]+/).test(error.data.id));

for (const [name, value] of Object.entries(invalidModel.toData())) {
t.deepEqual(error.data[name], value);
Expand Down
82 changes: 80 additions & 2 deletions src/Transactions.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,118 @@
/**
* Class representing a transaction-related error.
*
* @class TransactionError
* @extends Error
*/
class TransactionError extends Error {
}

/**
* Error thrown when a transaction is already committed.
*
* @class TransactionCommittedError
* @extends TransactionError
*/
export class TransactionCommittedError extends TransactionError {
/**
* Creates an instance of TransactionCommittedError.
* This error is thrown when attempting to commit an already committed transaction.
* @property {string} message - The error message.
*/
message = 'Transaction was already committed.';
}

/**
* Enables transaction support for the provided engine.
*
* This function enhances an engine class with transaction capabilities, allowing multiple
* changes to be grouped into a single transaction that can be committed or rolled back.
*
* @param {Engine.constructor} engine - The base engine class to be enhanced with transaction support.
* @returns {TransactionalEngine.constructor} TransactionalEngine - The enhanced engine class with transaction functionality.
*/
export default function enableTransactions(engine) {
/**
* A class representing an engine with transaction capabilities.
* @class TransactionalEngine
* @extends {engine}
*/
class TransactionalEngine extends engine {
}

/**
* Starts a transaction on the engine. Returns a Transaction class that can handle
* put, commit, and rollback actions for the transaction.
*
* @returns {Transaction.constructor} Transaction - A class that manages the transaction's operations.
*/
TransactionalEngine.start = () => {
/**
* A class representing an active transaction on the engine.
* Contains methods to put changes, commit the transaction, or roll back in case of failure.
*
* @class Transaction
*/
class Transaction extends TransactionalEngine {
/**
* @property {Array<Object>} transactions - An array storing all the operations within the transaction.
* @static
*/
static transactions = [];

/**
* @property {boolean} committed - Indicates if the transaction has been committed.
* @static
*/
static committed = false;

/**
* @property {boolean} failed - Indicates if the transaction has failed.
* @static
*/
static failed = false;

static async put(model) {
/**
* Adds a model to the transaction queue.
*
* @param {Object} model - The model to be added to the transaction.
* @returns {Promise<void>} A promise that resolves once the model is added.
*/
static put(model) {
this.transactions.push({
hasRun: false,
hasRolledBack: false,
model,
});

return Promise.resolve();
}

/**
* Checks if the transaction has already been committed. If true, throws a TransactionCommittedError.
*
* @throws {TransactionCommittedError} If the transaction has already been committed.
* @private
*/
static _checkCommitted() {
if (this.committed) throw new TransactionCommittedError();
}

/**
* Commits the transaction, applying all the changes to the engine.
* Rolls back if any part of the transaction fails.
*
* @returns {Promise<void>} A promise that resolves once the transaction is committed, or rejects if an error occurs.
* @throws {Error} If any operation in the transaction fails.
*/
static async commit() {
this._checkCommitted();

try {
for (const [index, {model}] of this.transactions.entries()) {
try {
this.transactions[index].original = await engine.get(model.constructor, model.id);
} catch (_) {
} catch (_error) {
this.transactions[index].original = null;
}

Expand Down
42 changes: 25 additions & 17 deletions src/engine/Engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,65 +16,71 @@ class Engine {
*
* @param {string} _id - The ID of the model to retrieve.
* @throws {NotImplementedError} Throws if the method is not implemented.
* @returns {Promise<Object>} - Returns a promise resolving to the raw data of the requested model.
* @abstract
*/
static async getById(_id) {
throw new NotImplementedError(`${this.name} must implement .getById()`);
static getById(_id) {
return Promise.reject(new NotImplementedError(`${this.name} must implement .getById()`));
}

/**
* Saves a model to the data store. This method must be implemented by subclasses.
*
* @param {Model} _data - The model data to save.
* @throws {NotImplementedError} Throws if the method is not implemented.
* @returns {Promise<void>}
* @abstract
*/
static async putModel(_data) {
throw new NotImplementedError(`${this.name} must implement .putModel()`);
static putModel(_data) {
return Promise.reject(new NotImplementedError(`${this.name} must implement .putModel()`));
}

/**
* Retrieves the index for a given model. This method must be implemented by subclasses.
*
* @param {Model.constructor} _model - The model to retrieve the index for.
* @throws {NotImplementedError} Throws if the method is not implemented.
* @returns {Promise<Object>} - Returns a promise resolving to the model index.
* @abstract
*/
static async getIndex(_model) {
throw new NotImplementedError(`${this.name} does not implement .getIndex()`);
static getIndex(_model) {
return Promise.reject(new NotImplementedError(`${this.name} does not implement .getIndex()`));
}

/**
* Saves the index for a given model. This method must be implemented by subclasses.
*
* @param {Object} _index - The index data to save.
* @throws {NotImplementedError} Throws if the method is not implemented.
* @returns {Promise<void>}
* @abstract
*/
static async putIndex(_index) {
throw new NotImplementedError(`${this.name} does not implement .putIndex()`);
static putIndex(_index) {
return Promise.reject(new NotImplementedError(`${this.name} does not implement .putIndex()`));
}

/**
* Retrieves the compiled search index for a model. This method must be implemented by subclasses.
*
* @param {Model.constructor} _model - The model to retrieve the compiled search index for.
* @throws {NotImplementedError} Throws if the method is not implemented.
* @returns {Promise<Object>} - Returns a promise resolving to the compiled search index.
* @abstract
*/
static async getSearchIndexCompiled(_model) {
throw new NotImplementedError(`${this.name} does not implement .getSearchIndexCompiled()`);
static getSearchIndexCompiled(_model) {
return Promise.reject(new NotImplementedError(`${this.name} does not implement .getSearchIndexCompiled()`));
}

/**
* Retrieves the raw search index for a model. This method must be implemented by subclasses.
*
* @param {Model.constructor} _model - The model to retrieve the raw search index for.
* @throws {NotImplementedError} Throws if the method is not implemented.
* @returns {Promise<Object>} - Returns a promise resolving to the raw search index.
* @abstract
*/
static async getSearchIndexRaw(_model) {
throw new NotImplementedError(`${this.name} does not implement .getSearchIndexRaw()`);
static getSearchIndexRaw(_model) {
return Promise.reject(new NotImplementedError(`${this.name} does not implement .getSearchIndexRaw()`));
}

/**
Expand All @@ -83,10 +89,11 @@ class Engine {
* @param {Model.constructor} _model - The model for which the compiled search index is saved.
* @param {Object} _compiledIndex - The compiled search index data.
* @throws {NotImplementedError} Throws if the method is not implemented.
* @returns {Promise<void>}
* @abstract
*/
static async putSearchIndexCompiled(_model, _compiledIndex) {
throw new NotImplementedError(`${this.name} does not implement .putSearchIndexCompiled()`);
static putSearchIndexCompiled(_model, _compiledIndex) {
return Promise.reject(new NotImplementedError(`${this.name} does not implement .putSearchIndexCompiled()`));
}

/**
Expand All @@ -95,10 +102,11 @@ class Engine {
* @param {Model.constructor} _model - The model for which the raw search index is saved.
* @param {Object} _rawIndex - The raw search index data.
* @throws {NotImplementedError} Throws if the method is not implemented.
* @returns {Promise<void>}
* @abstract
*/
static async putSearchIndexRaw(_model, _rawIndex) {
throw new NotImplementedError(`${this.name} does not implement .putSearchIndexRaw()`);
static putSearchIndexRaw(_model, _rawIndex) {
return Promise.reject(new NotImplementedError(`${this.name} does not implement .putSearchIndexRaw()`));
}

/**
Expand Down Expand Up @@ -321,7 +329,7 @@ class Engine {
* @abstract
*/
static checkConfiguration() {

// Implemented in extending Engine class
}

/**
Expand Down
16 changes: 8 additions & 8 deletions src/engine/HTTPEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class HTTPEngine extends Engine {
*
* @throws {HTTPRequestFailedError} Thrown if the fetch request fails.
*/
static async _processFetch(url, options, defaultValue = undefined) {
static _processFetch(url, options, defaultValue = undefined) {
return this.configuration.fetch(url, options)
.then(response => {
if (!response.ok) {
Expand Down Expand Up @@ -133,7 +133,7 @@ class HTTPEngine extends Engine {
this.configuration.host,
this.configuration.prefix,
`${id}.json`,
].filter(e => !!e).join('/'));
].filter(e => Boolean(e)).join('/'));

return await this._processFetch(url, this._getReadOptions());
}
Expand All @@ -146,14 +146,14 @@ class HTTPEngine extends Engine {
*
* @throws {HTTPRequestFailedError} Thrown if the PUT request fails.
*/
static async putModel(model) {
static putModel(model) {
const url = new URL([
this.configuration.host,
this.configuration.prefix,
`${model.id}.json`,
].filter(e => !!e).join('/'));
].filter(e => Boolean(e)).join('/'));

return await this._processFetch(url, {
return this._processFetch(url, {
...this._getWriteOptions(),
body: JSON.stringify(model.toData()),
});
Expand All @@ -175,7 +175,7 @@ class HTTPEngine extends Engine {
this.configuration.prefix,
location,
'_index.json',
].filter(e => !!e).join('/'));
].filter(e => Boolean(e)).join('/'));

return await this._processFetch(url, {
...this._getWriteOptions(),
Expand Down Expand Up @@ -259,7 +259,7 @@ class HTTPEngine extends Engine {
this.configuration.prefix,
model.toString(),
'_search_index.json',
].filter(e => !!e).join('/'));
].filter(e => Boolean(e)).join('/'));

return this._processFetch(url, {
...this._getWriteOptions(),
Expand All @@ -282,7 +282,7 @@ class HTTPEngine extends Engine {
this.configuration.prefix,
model.toString(),
'_search_index_raw.json',
].filter(e => !!e).join('/'));
].filter(e => Boolean(e)).join('/'));

return await this._processFetch(url, {
...this._getWriteOptions(),
Expand Down
2 changes: 1 addition & 1 deletion src/engine/S3Engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class S3Engine extends Engine {
}));

return JSON.parse(await data.Body.transformToString());
} catch (_) {
} catch (_error) {
return {};
}
}
Expand Down
Loading

0 comments on commit c0f6db5

Please sign in to comment.