Skip to content

Commit

Permalink
feat(engine): add HTTP engine (#5)
Browse files Browse the repository at this point in the history
* test: remove short-circuit test from called assertion

* test: only run pre-commit checks against staged files

* feat(engine): add HTTP engine

* fix: vulnerability in fast-xml-parser

* test: add semicolon required rule to eslint
  • Loading branch information
acodeninja authored Aug 3, 2024
1 parent ed12acb commit 34f61aa
Show file tree
Hide file tree
Showing 14 changed files with 1,383 additions and 259 deletions.
1 change: 1 addition & 0 deletions .husky/post-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
git stash apply
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
git stash --keep-index --include-untracked
make commit/pre
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default [
varsIgnorePattern: '^_',
}],
'quote-props': ['error', 'as-needed'],
semi: ['error', 'always'],
'sort-imports': ['error', {}],
quotes: ['error', 'single'],
},
Expand Down
549 changes: 312 additions & 237 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 20 additions & 6 deletions src/engine/Engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default class Engine {
}

static async search(model, query) {
this._checkConfiguration?.();
const index = await this.getSearchIndexCompiled(model);

try {
Expand All @@ -61,12 +62,14 @@ export default class Engine {
}

static async find(model, parameters) {
this._checkConfiguration?.();
const response = await this.findByValue(model, parameters);

return response.map(m => model.fromData(m));
}

static async put(model) {
this._checkConfiguration?.();
const uploadedModels = [];
const indexUpdates = {};

Expand All @@ -87,15 +90,15 @@ export default class Engine {
await this.putSearchIndexRaw(model.constructor, rawSearchIndex);

const compiledIndex = lunr(function () {
this.ref('id')
this.ref('id');

for (const field of model.constructor.searchProperties()) {
this.field(field);
}

Object.values(rawSearchIndex).forEach(function (doc) {
this.add(doc);
}, this)
}, this);
});

await this.putSearchIndexCompiled(model.constructor, compiledIndex);
Expand All @@ -118,6 +121,7 @@ export default class Engine {
}

static async get(model, id) {
this._checkConfiguration?.();
const found = await this.getById(id);

try {
Expand All @@ -128,6 +132,7 @@ export default class Engine {
}

static async hydrate(model) {
this._checkConfiguration?.();
const hydratedModels = {};

const hydrateModel = async (modelToProcess) => {
Expand All @@ -142,7 +147,7 @@ export default class Engine {
}

return modelToProcess;
}
};

const hydrateSubModel = async (property, modelToProcess, name) => {
if (hydratedModels[property.id]) {
Expand All @@ -155,7 +160,7 @@ export default class Engine {
const hydratedSubModel = await hydrateModel(subModel);
hydratedModels[property.id] = hydratedSubModel;
return hydratedSubModel;
}
};

const hydrateModelList = async (property, modelToProcess, name) => {
const subModelClass = getSubModelClass(modelToProcess, name, true);
Expand All @@ -177,7 +182,7 @@ export default class Engine {
hydratedModels[hydratedSubModel.id] = hydratedSubModel;
return hydratedSubModel;
}));
}
};

function getSubModelClass(modelToProcess, name, isArray = false) {
const constructorField = modelToProcess.constructor[name];
Expand All @@ -195,7 +200,7 @@ export default class Engine {
static _configuration = configuration;
}

Object.defineProperty(ConfiguredStore, 'name', {value: `${this.toString()}`})
Object.defineProperty(ConfiguredStore, 'name', {value: `${this.toString()}`});

return ConfiguredStore;
}
Expand All @@ -213,3 +218,12 @@ export class NotFoundEngineError extends EngineError {

export class NotImplementedError extends EngineError {
}

export class MissConfiguredError extends EngineError {
configuration;

constructor(configuration) {
super('Engine is miss-configured');
this.configuration = configuration;
}
}
175 changes: 175 additions & 0 deletions src/engine/HTTPEngine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import Engine, {MissConfiguredError} from './Engine.js';

export default class HTTPEngine extends Engine {
static configure(configuration = {}) {
configuration.fetchOptions = {
...(configuration.fetchOptions ?? {}),
headers: {
...(configuration.fetchOptions?.headers ?? {}),
Accept: 'application/json',
},
};

return super.configure(configuration);
}

static _checkConfiguration() {
if (
!this._configuration?.host
) throw new MissConfiguredError(this._configuration);
}

static _getReadOptions() {
return this._configuration.fetchOptions;
}

static _getWriteOptions() {
return {
...this._getReadOptions(),
headers: {
...this._getReadOptions().headers,
'Content-Type': 'application/json',
},
method: 'PUT',
};
}

static async getById(id) {
this._checkConfiguration();
const url = new URL([
this._configuration.host,
this._configuration.prefix,
`${id}.json`,
].filter(e => !!e).join('/'));

try {
return await this._configuration.fetch(url, this._getReadOptions()).then(r => r.json());
} catch (_error) {
return undefined;
}
}

static async findByValue(model, parameters) {
const index = await this.getIndex(model.name);
return Object.values(index)
.filter((model) =>
Object.entries(parameters)
.some(([name, value]) => model[name] === value),
);
}

static async putModel(model) {
const url = new URL([
this._configuration.host,
this._configuration.prefix,
`${model.id}.json`,
].filter(e => !!e).join('/'));

try {
return await this._configuration.fetch(url, {
...this._getWriteOptions(),
body: JSON.stringify(model.toData()),
}).then(r => r.json());
} catch (_error) {
return undefined;
}
}

static async putIndex(index) {
const processIndex = async (location, models) => {
const modelIndex = Object.fromEntries(models.map(m => [m.id, m.toIndexData()]));
const url = new URL([
this._configuration.host,
this._configuration.prefix,
location,
'_index.json',
].filter(e => !!e).join('/'));

const currentIndex = await this.getIndex(location);

try {
return await this._configuration.fetch(url, {
...this._getWriteOptions(),
body: JSON.stringify({
...currentIndex,
...modelIndex,
}),
}).then(r => r.json());
} catch (_error) {
return undefined;
}
};

for (const [location, models] of Object.entries(index)) {
await processIndex(location, models);
}

await processIndex(null, Object.values(index).flat());
}

static async getIndex(location) {
const url = new URL(this._configuration.host + '/' + [this._configuration.prefix, location, '_index.json'].filter(e => !!e).join('/'));

try {
return await this._configuration.fetch(url, this._getReadOptions()).then(r => r.json());
} catch (_error) {
return {};
}
}

static async getSearchIndexCompiled(model) {
const url = new URL(this._configuration.host + '/' + [this._configuration.prefix].concat([model.name]).concat(['_search_index.json']).join('/'));

try {
return await this._configuration.fetch(url, this._getReadOptions()).then(r => r.json());
} catch (_error) {
return {};
}
}

static async getSearchIndexRaw(model) {
const url = new URL(this._configuration.host + '/' + [this._configuration.prefix].concat([model.name]).concat(['_search_index_raw.json']).join('/'));

try {
return await this._configuration.fetch(url, this._getReadOptions()).then(r => r.json());
} catch (_error) {
return {};
}
}

static async putSearchIndexCompiled(model, compiledIndex) {
const url = new URL([
this._configuration.host,
this._configuration.prefix,
model.name,
'_search_index.json',
].filter(e => !!e).join('/'));

try {
return await this._configuration.fetch(url, {
...this._getWriteOptions(),
body: JSON.stringify(compiledIndex),
}).then(r => r.json());
} catch (_error) {
return undefined;
}
}

static async putSearchIndexRaw(model, rawIndex) {
const url = new URL([
this._configuration.host,
this._configuration.prefix,
model.name,
'_search_index_raw.json',
].filter(e => !!e).join('/'));

try {
return await this._configuration.fetch(url, {
...this._getWriteOptions(),
body: JSON.stringify(rawIndex),
}).then(r => r.json());
} catch (_error) {
return undefined;
}
}
}
Loading

0 comments on commit 34f61aa

Please sign in to comment.