Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 50 logger #57

Merged
merged 4 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import Model from './lib/Model';
import Metronom from './lib/Metronom';
import ModelInstance from './lib/ModelInstance';
import * as Constants from './lib/Constants';
import * as Interfaces from './lib/Interfaces';
import * as Enums from './lib/Enums';

export = {
Model,
ModelInstance,
Metronom,
...Constants,
...Interfaces,
...Enums,
};
1 change: 1 addition & 0 deletions lib/Constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable object-shorthand */

/**
* Metronom default data types.
* @module Types
Expand Down
10 changes: 10 additions & 0 deletions lib/Enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Metronom's log levels
* @enum
*/
export enum LogLevels {
Error,
Information,
All,
None,
}
5 changes: 5 additions & 0 deletions lib/IRedisAdaptor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/* eslint-disable no-unused-vars */

/**
* Redis Interface
* @internal
*/
interface IRedisAdaptor{
redisClient: any;
hGetAll(redisKey: string): Promise<object>;
Expand Down
72 changes: 72 additions & 0 deletions lib/Interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { RedisClientOptions } from 'redis';
import Model from './Model';
import ModelInstance from './ModelInstance';
import { LogLevels } from './Enums';

/**
* Schema of Metronom model
* @interface
* @memberof Model
* @example
* ```
* import { Types } from 'metronom';
* const schema = {
* isAdmin: {
* type: Types.Boolean,
* default: false,
* }
* };
* ```
*/
export interface Schema {
[index: string]: {
type: any,
default?: unknown,
}
}

/**
* Metronom Model settings
* @interface
*/
export interface ModelOptions {
keyUnique?: string;
redisClientOptions?: RedisClientOptions | any;
flexSchema?: boolean;
log?: boolean | LogLevels;
}

/**
* Metronom `Model.filter` function's argument type
* @interface
*/
export interface FilterFunction {
/* eslint-disable-next-line */
(value: ModelInstance, index: number, array: ModelInstance[]): boolean
}

/**
* Metronom `Model.filter` function's search options
* @interface
*/
export interface FilterOptions {
limit?: number,
}

/**
* ModelInstance info
* @interface
*/
export interface DataInfo {
redisKey: String,
}

/**
* ModelInstance's data fileds
* @interface
*/
export interface ModelFields {
_model: Model;
_previousDataValues: any | Object;
_dataInfo: DataInfo;
}
46 changes: 46 additions & 0 deletions lib/Logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { LogLevels } from './Enums';

/**
* Metronom's internal logger
* @internal
*/
class Logger {
static level: LogLevels | null = null;

constructor(level?: boolean | LogLevels) {
if (Logger.level !== null) {
return; // All models must has same level
}

if (typeof level === 'boolean' && level === true) {
Logger.level = LogLevels.All;
return;
}
if (typeof level === 'number') {
Logger.level = level;
return;
}

Logger.level = LogLevels.None;
}

static #log(message: string) {
console.log(message);
}

static log(message: string) {
if (!(Logger.level === LogLevels.Information || Logger.level === LogLevels.All)) {
return;
}
this.#log(`[ METRONOM ] / ${new Date().toISOString()}: ${message}`);
}

static error(message: string) {
if (!(Logger.level === LogLevels.Error || Logger.level === LogLevels.All)) {
return;
}
this.#log(`\x1b[31m[ METRONOM ERROR ] / ${new Date().toISOString()}: ${message}\x1b[0m`);
}
}

export default Logger;
6 changes: 5 additions & 1 deletion lib/Metronom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { RedisClientOptions } from 'redis';
import Model, { ModelOptions, Schema } from './Model';
import Model from './Model';
import {
ModelOptions,
Schema,
} from './Interfaces';

/**
* Metronom model creator
Expand Down
70 changes: 24 additions & 46 deletions lib/Model.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,22 @@
import { RedisClientOptions } from 'redis';
import IRedisAdaptor from './IRedisAdaptor';
import ModelInstance, { DataInfo } from './ModelInstance';
import NodeRedisAdaptor from './NodeRedisAdaptor';
import ModelInstance from './ModelInstance';
import NodeRedisAdaptor from './adaptors/NodeRedisAdaptor';
import Logger from './Logger';
import {
isObject, getKeyValue, safeWrite, safeRead,
isObject,
getKeyValue,
safeWrite,
safeRead,
throwError,
} from './Utilities';

/**
* Schema of Metronom model
* @example
* ```
* import { Types } from 'metronom';
* const schema = {
* isAdmin: {
* type: Types.Boolean,
* default: false,
* }
* };
* ```
*/
export interface Schema {
[index: string]: {
type: any,
default?: unknown,
}
}

export interface ModelOptions {
keyUnique?: string,
redisClientOptions?: RedisClientOptions | any,
flexSchema?: boolean,
}

export interface FilterFunction {
/* eslint-disable-next-line */
(value: ModelInstance, index: number, array: ModelInstance[]): boolean
}

export interface FilterOptions {
limit?: number,
}
import {
Schema,
ModelOptions,
FilterOptions,
FilterFunction,
DataInfo,
} from './Interfaces';

/**
* Model Class
Expand Down Expand Up @@ -84,18 +61,19 @@ class Model {
this.schema = schema;
this.keyPrefix = keyPrefix;
this.flexSchema = modelOption?.flexSchema ? modelOption?.flexSchema : false;
new Logger(modelOption?.log);

if (!this.flexSchema) {
if (Object.keys(schema).length === 0) {
throw new Error('Only flex schema can be empty! Set the "modelOption.flexSchema" to "true"');
throwError('Only flex schema can be empty! Set the "modelOption.flexSchema" to "true"');
}

Object.entries(schema).forEach(([key, value]) => {
if (!value.type) {
throw new Error(`"${key}" key must have to "type" property in the schema!`);
throwError(`"${key}" key must have to "type" property in the schema!`);
}
if (typeof value.type !== 'function') {
throw new Error(`"schema.${key}.type" must be constructor! You send "${value.type}"`);
throwError(`"schema.${key}.type" must be constructor! You send "${value.type}"`);
}
});
}
Expand All @@ -104,7 +82,7 @@ class Model {
this.keyUnique = undefined;
} else {
if (!Object.keys(schema).includes(modelOption?.keyUnique)) {
throw new Error(`${modelOption?.keyUnique} keyUnique must be in to schema!`);
throwError(`${modelOption?.keyUnique} keyUnique must be in to schema!`);
}
this.keyUnique = modelOption?.keyUnique;
}
Expand All @@ -113,7 +91,7 @@ class Model {
try {
this.redisClient.connect();
} catch (error: any) {
throw new Error(`Metronom: redis connecting error: ${error.message}`);
throwError(`Metronom: redis connecting error: ${error.message}`);
}
}

Expand All @@ -126,14 +104,14 @@ class Model {
*/
public async create(valueObject: Object): Promise<ModelInstance> {
if (!isObject(valueObject) && !Array.isArray(valueObject)) {
throw new Error(`Value must be object or array!. Your type is: ${typeof valueObject}`);
throwError(`Value must be object or array!. Your type is: ${typeof valueObject}`);
}

const redisKey = this.generateRedisKey(valueObject);
const isExist = (await this.redisClient.keys(redisKey)).length > 0;

if (isExist) {
throw new Error(`"${redisKey}" already exist!`);
throwError(`"${redisKey}" already exist!`);
}

valueObject = await this._write(redisKey, valueObject);
Expand Down Expand Up @@ -210,7 +188,7 @@ class Model {
*/
public async filter(filterFunction: FilterFunction): Promise<Array<ModelInstance> | []> {
if (typeof filterFunction !== 'function') {
throw new Error('The type of the parameter of the "filter" function must be "function"!');
throwError('The type of the parameter of the "filter" function must be "function"!');
}
const records: Array<ModelInstance> = await this.getAll();
const filtredRecords = records.filter(async (value, index, array) =>
Expand Down
14 changes: 4 additions & 10 deletions lib/ModelInstance.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import Model from './Model';
import { safeWrite } from './Utilities';

export interface DataInfo {
redisKey: String,
}

interface ModelFields {
_model: Model;
_previousDataValues: any | Object;
_dataInfo: DataInfo;
}
import {
ModelFields,
DataInfo,
} from './Interfaces';

/**
* ModelInstance Class
Expand Down
12 changes: 11 additions & 1 deletion lib/Utilities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable eqeqeq */
import { Types } from './Constants';
import { Schema } from './Model';
import { Schema } from './Interfaces';
import Logger from './Logger';
/**
* Utilities
* @category Utilities
Expand Down Expand Up @@ -99,3 +100,12 @@ export const safeWrite = async (
await redisClient.hSet(redisKey, keysAndValues);
return data;
};

/**
* Throw and log error with Metronom's internal Logger
* @param message {string}
*/
export const throwError = (message: string) => {
Logger.error(message);
throw new Error(message);
};
48 changes: 48 additions & 0 deletions lib/adaptors/NodeRedisAdaptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable class-methods-use-this */
import { createClient, RedisClientOptions, RedisClientType } from 'redis';
import IRedisAdaptor from '../IRedisAdaptor';
import Logger from '../Logger';

class NodeRedisAdaptor implements IRedisAdaptor {
redisClient: RedisClientType<any, any>;

constructor(options: RedisClientOptions) {
this.redisClient = createClient(options);
}

hGetAll(redisKey: string): Promise<object> {
Logger.log(`hGetAll ${redisKey}`);
return this.redisClient.hGetAll(redisKey);
}

hSet(redisKey: string, values: [string, any]): Promise<number> {
Logger.log(`hSet ${redisKey} ${values.join(' ')}`);
return this.redisClient.hSet(redisKey, values);
}

connect(): void {
this.redisClient.connect();
}

keys(regex: string): Promise<string[]> {
Logger.log(`keys ${regex}`);
return this.redisClient.keys(regex);
}

del(redisKey: string): Promise<number> {
Logger.log(`del ${redisKey}`);
return this.redisClient.del(redisKey);
}

sendCommand(commands: string[]): Promise<unknown> {
Logger.log(`sendCommand ${commands.join(' ')}`);
return this.redisClient.sendCommand(commands);
}

echo(message: any) {
Logger.log(`echo ${message}`);
return this.redisClient.echo(message);
}
}

export default NodeRedisAdaptor;