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", "name": "@ztimson/js-utilities",
"version": "0.3.8", "version": "0.4.0",
"description": "JavaScript Utility library", "description": "JavaScript Utility library",
"author": "Zak Timson", "author": "Zak Timson",
"license": "MIT", "license": "MIT",

View File

@ -42,23 +42,23 @@ export const CliBackground = {
} }
export enum LOG_LEVEL { export enum LOG_LEVEL {
VERBOSE, ERROR = 0,
DEBUG = 0, WARN = 1,
LOG = 1,
INFO = 2, INFO = 2,
WARN = 3, LOG = 3,
ERROR = 4, DEBUG = 4,
} }
export type LoggerEvents = TypedEvents & { export type LoggerEvents = TypedEvents & {
'VERBOSE': (...args: any[]) => any;
'INFO': (...args: any[]) => any;
'WARN': (...args: any[]) => any;
'ERROR': (...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> { 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) { constructor(public readonly namespace?: string) {
super(); super();
@ -79,35 +79,35 @@ export class Logger extends TypedEmitter<LoggerEvents> {
} }
debug(...args: string[]) { debug(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.VERBOSE) return; if(Logger.LOG_LEVEL < LOG_LEVEL.DEBUG) return;
const str = this.format(...args); 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); console.debug(CliForeground.LIGHT_GREY + str + CliEffects.CLEAR);
} }
log(...args: string[]) { log(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.INFO) return; if(Logger.LOG_LEVEL < LOG_LEVEL.LOG) return;
const str = this.format(...args); const str = this.format(...args);
Logger.emit(LOG_LEVEL.INFO, str); Logger.emit(LOG_LEVEL.LOG, str);
console.log(CliEffects.CLEAR + str); console.log(CliEffects.CLEAR + str);
} }
info(...args: string[]) { info(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.INFO) return; if(Logger.LOG_LEVEL < LOG_LEVEL.INFO) return;
const str = this.format(...args); const str = this.format(...args);
Logger.emit(LOG_LEVEL.INFO, str); Logger.emit(LOG_LEVEL.INFO, str);
console.info(CliForeground.BLUE + str + CliEffects.CLEAR); console.info(CliForeground.BLUE + str + CliEffects.CLEAR);
} }
warn(...args: string[]) { warn(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.WARN) return; if(Logger.LOG_LEVEL < LOG_LEVEL.WARN) return;
const str = this.format(...args); const str = this.format(...args);
Logger.emit(LOG_LEVEL.WARN, str); Logger.emit(LOG_LEVEL.WARN, str);
console.warn(CliForeground.YELLOW + str + CliEffects.CLEAR); console.warn(CliForeground.YELLOW + str + CliEffects.CLEAR);
} }
error(...args: string[]) { error(...args: string[]) {
if(Logger.LOG_LEVEL > LOG_LEVEL.ERROR) return; if(Logger.LOG_LEVEL < LOG_LEVEL.ERROR) return;
const str = this.format(...args); const str = this.format(...args);
Logger.emit(LOG_LEVEL.ERROR, str); Logger.emit(LOG_LEVEL.ERROR, str);
console.error(CliForeground.RED + str + CliEffects.CLEAR); 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> { export type Interceptor = (request: Response, next: () => void) => void;
private static interceptors: {[key: number]: FetchInterceptor} = {};
static headers: Record<string, string | null> = {};
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, export type XhrEvents = TypedEvents & {
public readonly headers: Record<string, string | null> = {} '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): () => {} { export type XhrOptions = {
const func: any = fn ? fn : keyOrFn; interceptors?: Interceptor[];
const key: string = typeof keyOrFn == 'string' ? keyOrFn : url?: string;
`_${Object.keys(XHR.interceptors).length.toString()}`; }
XHR.interceptors[<any>key] = func;
return () => delete XHR.interceptors[<any>key]; 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): () => {}; static addInterceptor(fn: Interceptor): () => void {
addInterceptor(key: string, fn: FetchInterceptor): () => {}; const key = Object.keys(XHR.interceptors).length.toString();
addInterceptor(keyOrFn: string | FetchInterceptor, fn?: FetchInterceptor): () => {} { XHR.interceptors[key] = fn;
const func: any = fn ? fn : keyOrFn; return () => { XHR.interceptors[key] = <any>null; }
const key: string = typeof keyOrFn == 'string' ? keyOrFn :
`_${Object.keys(this.interceptors).length.toString()}`;
this.interceptors[<any>key] = func;
return () => delete this.interceptors[<any>key];
} }
getInterceptors() { addInterceptor(fn: Interceptor): () => void {
return [...Object.values(XHR.interceptors), ...Object.values(this.interceptors)]; 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> { async request<T>(opts: RequestOptions = {}): Promise<T> {
const headers = { if(!this.opts.url && !opts.url) throw new Error('Momentum server URL needs to be set');
'Content-Type': (body && !(body instanceof FormData)) ? 'application/json' : undefined, 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, ...XHR.headers,
...this.headers, ...this.headers,
...opts.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, headers,
method: opts.method || (body ? 'POST' : 'GET'), method: opts.method || (opts.body ? 'POST' : 'GET'),
body: (headers['Content-Type']?.startsWith('application/json') && body) ? JSON.stringify(body) : body body: (headers['Content-Type']?.startsWith('application/json') && opts.body) ? JSON.stringify(opts.body) : opts.body
}).then(async resp => { }).then(async resp => {
for(let fn of this.getInterceptors()) { for(let fn of [...Object.values(XHR.interceptors), ...Object.values(this.interceptors)]) {
const wait = new Promise(res => const wait = new Promise(res => fn(resp, () => res(null)));
fn(resp, () => res(null)));
await wait; await wait;
} }
if(resp.headers.has('Content-Type')) {
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('application/json')) return await resp.json();
if(resp.headers.get('Content-Type')?.startsWith('text/plain')) return await resp.text(); if(resp.headers.get('Content-Type')?.startsWith('text/plain')) return await <any>resp.text();
}
return resp; return resp;
}).catch((err: Error) => {
this.emit('REJECTED', err, opts);
throw err;
}); });
} this.emit('REQUEST', req, opts)
return req;
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;
} }
} }