Updated logger & emitter
Some checks failed
Build / Build NPM Project (push) Failing after 0s
Build / Tag Version (push) Has been skipped
Build / Publish (push) Has been skipped

This commit is contained in:
Zakary Timson 2024-04-11 20:22:44 -04:00
parent 77e6a40261
commit c8ccc19996
3 changed files with 84 additions and 88 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@ztimson/js-utilities",
"version": "0.3.8",
"version": "0.4.0",
"description": "JavaScript Utility library",
"author": "Zak Timson",
"license": "MIT",

View File

@ -42,23 +42,23 @@ export const CliBackground = {
}
export enum LOG_LEVEL {
VERBOSE,
DEBUG = 0,
LOG = 1,
ERROR = 0,
WARN = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
LOG = 3,
DEBUG = 4,
}
export type LoggerEvents = TypedEvents & {
'VERBOSE': (...args: any[]) => any;
'INFO': (...args: any[]) => any;
'WARN': (...args: any[]) => any;
'ERROR': (...args: any[]) => any;
'WARN': (...args: any[]) => any;
'INFO': (...args: any[]) => any;
'LOG': (...args: any[]) => any;
'DEBUG': (...args: any[]) => any;
};
export class Logger extends TypedEmitter<LoggerEvents> {
static LOG_LEVEL: LOG_LEVEL = LOG_LEVEL.VERBOSE;
static LOG_LEVEL: LOG_LEVEL = LOG_LEVEL.DEBUG;
constructor(public readonly namespace?: string) {
super();
@ -79,35 +79,35 @@ export class Logger extends TypedEmitter<LoggerEvents> {
}
debug(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.VERBOSE) return;
if(Logger.LOG_LEVEL < LOG_LEVEL.DEBUG) return;
const str = this.format(...args);
Logger.emit(LOG_LEVEL.VERBOSE, str);
Logger.emit(LOG_LEVEL.DEBUG, str);
console.debug(CliForeground.LIGHT_GREY + str + CliEffects.CLEAR);
}
log(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.INFO) return;
if(Logger.LOG_LEVEL < LOG_LEVEL.LOG) return;
const str = this.format(...args);
Logger.emit(LOG_LEVEL.INFO, str);
Logger.emit(LOG_LEVEL.LOG, str);
console.log(CliEffects.CLEAR + str);
}
info(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.INFO) return;
if(Logger.LOG_LEVEL < LOG_LEVEL.INFO) return;
const str = this.format(...args);
Logger.emit(LOG_LEVEL.INFO, str);
console.info(CliForeground.BLUE + str + CliEffects.CLEAR);
}
warn(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.WARN) return;
if(Logger.LOG_LEVEL < LOG_LEVEL.WARN) return;
const str = this.format(...args);
Logger.emit(LOG_LEVEL.WARN, str);
console.warn(CliForeground.YELLOW + str + CliEffects.CLEAR);
}
error(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.ERROR) return;
if(Logger.LOG_LEVEL < LOG_LEVEL.ERROR) return;
const str = this.format(...args);
Logger.emit(LOG_LEVEL.ERROR, str);
console.error(CliForeground.RED + str + CliEffects.CLEAR);

View File

@ -1,92 +1,88 @@
export type FetchInterceptor = (resp: Response, next: () => any) => any;
import {TypedEmitter, type TypedEvents} from './emitter';
import {clean} from './objects';
export class XHR<T> {
private static interceptors: {[key: number]: FetchInterceptor} = {};
static headers: Record<string, string | null> = {};
export type Interceptor = (request: Response, next: () => void) => void;
private interceptors: {[key: string]: FetchInterceptor} = {};
export type RequestOptions = {
url?: string;
method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
body?: any;
headers?: {[key: string | symbol]: string | null | undefined};
[key: string]: any;
}
constructor(public readonly baseUrl: string,
public readonly headers: Record<string, string | null> = {}
) { }
export type XhrEvents = TypedEvents & {
'REQUEST': (request: Promise<any>, options: RequestOptions) => any;
'RESPONSE': (response: Response, options: RequestOptions) => any;
'REJECTED': (response: Error, options: RequestOptions) => any;
static addInterceptor(fn: FetchInterceptor): () => {};
static addInterceptor(key: string, fn: FetchInterceptor): () => {};
static addInterceptor(keyOrFn: string | FetchInterceptor, fn?: FetchInterceptor): () => {} {
const func: any = fn ? fn : keyOrFn;
const key: string = typeof keyOrFn == 'string' ? keyOrFn :
`_${Object.keys(XHR.interceptors).length.toString()}`;
XHR.interceptors[<any>key] = func;
return () => delete XHR.interceptors[<any>key];
};
export type XhrOptions = {
interceptors?: Interceptor[];
url?: string;
}
export class XHR extends TypedEmitter<XhrEvents> {
private static headers: {[key: string]: string} = {};
private static interceptors: {[key: string]: Interceptor} = {};
private headers: {[key: string]: string} = {}
private interceptors: {[key: string]: Interceptor} = {}
constructor(public readonly opts: XhrOptions = {}) {
super();
if(opts.interceptors) {
opts.interceptors.forEach(i => XHR.addInterceptor(i));
}
}
addInterceptor(fn: FetchInterceptor): () => {};
addInterceptor(key: string, fn: FetchInterceptor): () => {};
addInterceptor(keyOrFn: string | FetchInterceptor, fn?: FetchInterceptor): () => {} {
const func: any = fn ? fn : keyOrFn;
const key: string = typeof keyOrFn == 'string' ? keyOrFn :
`_${Object.keys(this.interceptors).length.toString()}`;
this.interceptors[<any>key] = func;
return () => delete this.interceptors[<any>key];
static addInterceptor(fn: Interceptor): () => void {
const key = Object.keys(XHR.interceptors).length.toString();
XHR.interceptors[key] = fn;
return () => { XHR.interceptors[key] = <any>null; }
}
getInterceptors() {
return [...Object.values(XHR.interceptors), ...Object.values(this.interceptors)];
addInterceptor(fn: Interceptor): () => void {
const key = Object.keys(this.interceptors).length.toString();
this.interceptors[key] = fn;
return () => { this.interceptors[key] = <any>null; }
}
fetch<T2 = T>(href?: string, body?: any, opts: any = {}): Promise<T2> {
const headers = {
'Content-Type': (body && !(body instanceof FormData)) ? 'application/json' : undefined,
async request<T>(opts: RequestOptions = {}): Promise<T> {
if(!this.opts.url && !opts.url) throw new Error('Momentum server URL needs to be set');
const url = (opts.url?.startsWith('http') ? opts.url : (this.opts.url || '') + opts.url).replace(/([^:]\/)\/+/g, '$1');
// Prep headers
const headers = <any>clean({
'Content-Type': (opts.body && !(opts.body instanceof FormData)) ? 'application/json' : undefined,
...XHR.headers,
...this.headers,
...opts.headers
};
Object.keys(headers).forEach(h => { if(!headers[h]) delete headers[h]; });
return fetch(`${this.baseUrl}${href || ''}`.replace(/([^:]\/)\/+/g, '$1'), {
});
// Send request
const req = fetch(url, {
headers,
method: opts.method || (body ? 'POST' : 'GET'),
body: (headers['Content-Type']?.startsWith('application/json') && body) ? JSON.stringify(body) : body
method: opts.method || (opts.body ? 'POST' : 'GET'),
body: (headers['Content-Type']?.startsWith('application/json') && opts.body) ? JSON.stringify(opts.body) : opts.body
}).then(async resp => {
for(let fn of this.getInterceptors()) {
const wait = new Promise(res =>
fn(resp, () => res(null)));
for(let fn of [...Object.values(XHR.interceptors), ...Object.values(this.interceptors)]) {
const wait = new Promise(res => fn(resp, () => res(null)));
await wait;
}
if(resp.headers.has('Content-Type')) {
if(resp.headers.get('Content-Type')?.startsWith('application/json')) return await resp.json();
if(resp.headers.get('Content-Type')?.startsWith('text/plain')) return await resp.text();
}
this.emit(`${resp.status}`, resp, opts);
if(!resp.ok) throw Error(resp.statusText);
this.emit('RESPONSE', resp, opts);
if(resp.headers.get('Content-Type')?.startsWith('application/json')) return await resp.json();
if(resp.headers.get('Content-Type')?.startsWith('text/plain')) return await <any>resp.text();
return resp;
}).catch((err: Error) => {
this.emit('REJECTED', err, opts);
throw err;
});
}
delete<T2 = void>(url?: string, opts?: any): Promise<T2> {
return this.fetch(url, null, {method: 'delete', ...opts});
}
get<T2 = T>(url?: string, opts?: any): Promise<T2> {
return this.fetch(url, null, {method: 'get', ...opts});
}
patch<T2 = T>(data: T2, url?: string, opts?: any): Promise<T2> {
return this.fetch(url, data, {method: 'patch', ...opts});
}
post<T2 = T>(data: T2, url?: string, opts?: any): Promise<T2> {
return this.fetch(url, data, {method: 'post', ...opts});
}
put<T2 = T>(data: Partial<T2>, url?: string, opts?: any): Promise<T2> {
return this.fetch(url, data, {method: 'put', ...opts});
}
new<T2 = T>(href: string, headers: Record<string, string | null>): XHR<T2> {
const fetch = new XHR<T2>(`${this.baseUrl}${href}`, {
...this.headers,
...headers,
});
Object.entries(this.interceptors).map(([key, value]) =>
fetch.addInterceptor(key, value));
return fetch;
this.emit('REQUEST', req, opts)
return req;
}
}