Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
b1005227ab | |||
3a389de72e | |||
151465aa65 | |||
b103b6f786 | |||
3b486310de | |||
8699fb49ff | |||
fdb29e7984 | |||
274c22bb83 | |||
b21f462d35 | |||
0f10aebfd2 | |||
1af23ac544 | |||
494cfaaccd | |||
23df6ad265 | |||
2fda11f3b7 |
5753
package-lock.json
generated
5753
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ztimson/utils",
|
"name": "@ztimson/utils",
|
||||||
"version": "0.19.0",
|
"version": "0.20.8",
|
||||||
"description": "Utility library",
|
"description": "Utility library",
|
||||||
"author": "Zak Timson",
|
"author": "Zak Timson",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -38,8 +38,5 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
]
|
||||||
"dependencies": {
|
|
||||||
"var-persist": "^1.0.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
39
src/cache.ts
39
src/cache.ts
@ -1,3 +1,12 @@
|
|||||||
|
export type CacheOptions = {
|
||||||
|
/** Delete keys automatically after x amount of seconds */
|
||||||
|
ttl?: number;
|
||||||
|
/** Storage to persist cache */
|
||||||
|
storage?: Storage;
|
||||||
|
/** Key cache will be stored under */
|
||||||
|
storageKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of data which tracks whether it is a complete collection & offers optional expiry of cached values
|
* Map of data which tracks whether it is a complete collection & offers optional expiry of cached values
|
||||||
*/
|
*/
|
||||||
@ -13,9 +22,18 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
* Create new cache
|
* Create new cache
|
||||||
*
|
*
|
||||||
* @param {keyof T} key Default property to use as primary key
|
* @param {keyof T} key Default property to use as primary key
|
||||||
* @param {number} ttl Default expiry in milliseconds
|
* @param options
|
||||||
*/
|
*/
|
||||||
constructor(public readonly key?: keyof T, public ttl?: number) {
|
constructor(public readonly key?: keyof T, public readonly options: CacheOptions = {}) {
|
||||||
|
if(options.storageKey && !options.storage && typeof(Storage) !== 'undefined')
|
||||||
|
options.storage = localStorage;
|
||||||
|
if(options.storageKey && options.storage) {
|
||||||
|
const stored = options.storage.getItem(options.storageKey);
|
||||||
|
if(stored) {
|
||||||
|
try { Object.assign(this.store, JSON.parse(stored)); }
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
return new Proxy(this, {
|
return new Proxy(this, {
|
||||||
get: (target: this, prop: string | symbol) => {
|
get: (target: this, prop: string | symbol) => {
|
||||||
if (prop in target) return (target as any)[prop];
|
if (prop in target) return (target as any)[prop];
|
||||||
@ -69,6 +87,13 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all keys from cache
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this.store = <Record<K, T>>{};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete an item from the cache
|
* Delete an item from the cache
|
||||||
*
|
*
|
||||||
@ -76,6 +101,8 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
*/
|
*/
|
||||||
delete(key: K) {
|
delete(key: K) {
|
||||||
delete this.store[key];
|
delete this.store[key];
|
||||||
|
if(this.options.storageKey && this.options.storage)
|
||||||
|
this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,15 +145,17 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
*
|
*
|
||||||
* @param {K} key Key item will be cached under
|
* @param {K} key Key item will be cached under
|
||||||
* @param {T} value Item to cache
|
* @param {T} value Item to cache
|
||||||
* @param {number | undefined} ttl Override default expiry
|
* @param {number | undefined} ttl Override default expiry in seconds
|
||||||
* @return {this}
|
* @return {this}
|
||||||
*/
|
*/
|
||||||
set(key: K, value: T, ttl = this.ttl): this {
|
set(key: K, value: T, ttl = this.options.ttl): this {
|
||||||
this.store[key] = value;
|
this.store[key] = value;
|
||||||
|
if(this.options.storageKey && this.options.storage)
|
||||||
|
this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store));
|
||||||
if(ttl) setTimeout(() => {
|
if(ttl) setTimeout(() => {
|
||||||
this.complete = false;
|
this.complete = false;
|
||||||
this.delete(key);
|
this.delete(key);
|
||||||
}, ttl);
|
}, ttl * 1000);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
81
src/http.ts
81
src/http.ts
@ -75,47 +75,52 @@ export class Http {
|
|||||||
|
|
||||||
// Send request
|
// Send request
|
||||||
return new PromiseProgress((res, rej, prog) => {
|
return new PromiseProgress((res, rej, prog) => {
|
||||||
fetch(url, {
|
try {
|
||||||
headers,
|
fetch(url, {
|
||||||
method: opts.method || (opts.body ? 'POST' : 'GET'),
|
headers,
|
||||||
body: opts.body
|
method: opts.method || (opts.body ? 'POST' : 'GET'),
|
||||||
}).then(async (resp: any) => {
|
body: opts.body
|
||||||
for(let fn of [...Object.values(Http.interceptors), ...Object.values(this.interceptors)]) {
|
}).then(async (resp: any) => {
|
||||||
await new Promise<void>(res => fn(resp, () => res()));
|
for(let fn of [...Object.values(Http.interceptors), ...Object.values(this.interceptors)]) {
|
||||||
}
|
await new Promise<void>(res => fn(resp, () => res()));
|
||||||
|
|
||||||
const contentLength = resp.headers.get('Content-Length');
|
|
||||||
const total = contentLength ? parseInt(contentLength, 10) : 0;
|
|
||||||
let loaded = 0;
|
|
||||||
|
|
||||||
const reader = resp.body?.getReader();
|
|
||||||
const stream = new ReadableStream({
|
|
||||||
start(controller) {
|
|
||||||
function push() {
|
|
||||||
reader?.read().then((event: any) => {
|
|
||||||
if(event.done) return controller.close();
|
|
||||||
loaded += event.value.byteLength;
|
|
||||||
prog(loaded / total);
|
|
||||||
controller.enqueue(event.value);
|
|
||||||
push();
|
|
||||||
}).catch((error: any) => controller.error(error));
|
|
||||||
}
|
|
||||||
push();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
resp.data = new Response(stream);
|
const contentLength = resp.headers.get('Content-Length');
|
||||||
if(opts.decode == null || opts.decode) {
|
const total = contentLength ? parseInt(contentLength, 10) : 0;
|
||||||
const content = resp.headers.get('Content-Type')?.toLowerCase();
|
let loaded = 0;
|
||||||
if(content?.includes('form')) resp.data = <T>await resp.data.formData();
|
|
||||||
else if(content?.includes('json')) resp.data = <T>await resp.data.json();
|
|
||||||
else if(content?.includes('text')) resp.data = <T>await resp.data.text();
|
|
||||||
else if(content?.includes('application')) resp.data = <T>await resp.data.blob();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(resp.ok) res(resp);
|
const reader = resp.body?.getReader();
|
||||||
else rej(resp);
|
const stream = new ReadableStream({
|
||||||
})
|
start(controller) {
|
||||||
|
function push() {
|
||||||
|
reader?.read().then((event: any) => {
|
||||||
|
if(event.done) return controller.close();
|
||||||
|
loaded += event.value.byteLength;
|
||||||
|
prog(loaded / total);
|
||||||
|
controller.enqueue(event.value);
|
||||||
|
push();
|
||||||
|
}).catch((error: any) => controller.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
push();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resp.data = new Response(stream);
|
||||||
|
if(opts.decode == null || opts.decode) {
|
||||||
|
const content = resp.headers.get('Content-Type')?.toLowerCase();
|
||||||
|
if(content?.includes('form')) resp.data = <T>await resp.data.formData();
|
||||||
|
else if(content?.includes('json')) resp.data = <T>await resp.data.json();
|
||||||
|
else if(content?.includes('text')) resp.data = <T>await resp.data.text();
|
||||||
|
else if(content?.includes('application')) resp.data = <T>await resp.data.blob();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(resp.ok) res(resp);
|
||||||
|
else rej(resp);
|
||||||
|
}).catch(err => rej(err));
|
||||||
|
} catch(err) {
|
||||||
|
rej(err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,3 @@ export * from './promise-progress';
|
|||||||
export * from './string';
|
export * from './string';
|
||||||
export * from './time';
|
export * from './time';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from 'var-persist';
|
|
||||||
|
@ -14,16 +14,16 @@ import {ASet} from './aset.ts';
|
|||||||
export type Method = '*' | 'n' | 'c' | 'r' | 'u' | 'd' | 'x';
|
export type Method = '*' | 'n' | 'c' | 'r' | 'u' | 'd' | 'x';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shorthand for creating PathedEvent from a string
|
* Shorthand for creating Event from a string
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* const event: PathedEvent = PE`users/system:*`;
|
* const event: Event = PE`users/system:*`;
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {TemplateStringsArray} str String that will be parsed into PathedEvent
|
* @param {TemplateStringsArray} str String that will be parsed into Event
|
||||||
* @param {string} args
|
* @param {string} args
|
||||||
* @return {PathEvent} PathedEvent object
|
* @return {PathEvent} Event object
|
||||||
*/
|
*/
|
||||||
export function PE(str: TemplateStringsArray, ...args: string[]) {
|
export function PE(str: TemplateStringsArray, ...args: string[]) {
|
||||||
const combined = [];
|
const combined = [];
|
||||||
@ -35,7 +35,7 @@ export function PE(str: TemplateStringsArray, ...args: string[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shorthand for creating PathedEvent strings, ensures paths are correct
|
* Shorthand for creating Event strings, ensures paths are correct
|
||||||
*
|
*
|
||||||
* @param {TemplateStringsArray} str
|
* @param {TemplateStringsArray} str
|
||||||
* @param {string} args
|
* @param {string} args
|
||||||
@ -52,9 +52,11 @@ export function PES(str: TemplateStringsArray, ...args: any[]) {
|
|||||||
return PathEvent.toString(paths, <any>methods?.split(''));
|
return PathEvent.toString(paths, <any>methods?.split(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PathError extends Error { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pathed event broken down into its core components for easy processing
|
* A event broken down into its core components for easy processing
|
||||||
* PathedEvent Structure: `module/path/name:property:method`
|
* Event Structure: `module/path/name:property:method`
|
||||||
* Example: `users/system:crud` or `storage/some/path/file.txt:r`
|
* Example: `users/system:crud` or `storage/some/path/file.txt:r`
|
||||||
*/
|
*/
|
||||||
export class PathEvent {
|
export class PathEvent {
|
||||||
@ -81,9 +83,9 @@ export class PathEvent {
|
|||||||
/** Delete method specified */
|
/** Delete method specified */
|
||||||
delete!: boolean;
|
delete!: boolean;
|
||||||
|
|
||||||
constructor(pathedEvent: string | PathEvent) {
|
constructor(Event: string | PathEvent) {
|
||||||
if(typeof pathedEvent == 'object') return Object.assign(this, pathedEvent);
|
if(typeof Event == 'object') return Object.assign(this, Event);
|
||||||
let [p, scope, method] = pathedEvent.split(':');
|
let [p, scope, method] = Event.split(':');
|
||||||
if(!method) method = scope || '*';
|
if(!method) method = scope || '*';
|
||||||
if(p == '*' || !p && method == '*') {
|
if(p == '*' || !p && method == '*') {
|
||||||
p = '';
|
p = '';
|
||||||
@ -104,10 +106,10 @@ export class PathEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine multiple pathed events into one parsed object. Longest path takes precedent, but all subsequent methods are
|
* Combine multiple events into one parsed object. Longest path takes precedent, but all subsequent methods are
|
||||||
* combined until a "none" is reached
|
* combined until a "none" is reached
|
||||||
*
|
*
|
||||||
* @param {string | PathEvent} paths PathedEvents as strings or pre-parsed
|
* @param {string | PathEvent} paths Events as strings or pre-parsed
|
||||||
* @return {PathEvent} Final combined permission
|
* @return {PathEvent} Final combined permission
|
||||||
*/
|
*/
|
||||||
static combine(paths: (string | PathEvent)[]): PathEvent {
|
static combine(paths: (string | PathEvent)[]): PathEvent {
|
||||||
@ -138,7 +140,7 @@ export class PathEvent {
|
|||||||
/**
|
/**
|
||||||
* Squash 2 sets of paths & return true if any overlap is found
|
* Squash 2 sets of paths & return true if any overlap is found
|
||||||
*
|
*
|
||||||
* @param {string | PathEvent | (string | PathEvent)[]} target Array of PathedEvents as strings or pre-parsed
|
* @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed
|
||||||
* @param has Target must have at least one of these path
|
* @param has Target must have at least one of these path
|
||||||
* @return {boolean} Whether there is any overlap
|
* @return {boolean} Whether there is any overlap
|
||||||
*/
|
*/
|
||||||
@ -157,7 +159,7 @@ export class PathEvent {
|
|||||||
/**
|
/**
|
||||||
* Squash 2 sets of paths & return true if the target has all paths
|
* Squash 2 sets of paths & return true if the target has all paths
|
||||||
*
|
*
|
||||||
* @param {string | PathEvent | (string | PathEvent)[]} target Array of PathedEvents as strings or pre-parsed
|
* @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed
|
||||||
* @param has Target must have all these paths
|
* @param has Target must have all these paths
|
||||||
* @return {boolean} Whether there is any overlap
|
* @return {boolean} Whether there is any overlap
|
||||||
*/
|
*/
|
||||||
@ -168,29 +170,29 @@ export class PathEvent {
|
|||||||
/**
|
/**
|
||||||
* Same as `has` but raises an error if there is no overlap
|
* Same as `has` but raises an error if there is no overlap
|
||||||
*
|
*
|
||||||
* @param {string | string[]} target Array of PathedEvents as strings or pre-parsed
|
* @param {string | string[]} target Array of Events as strings or pre-parsed
|
||||||
* @param has Target must have at least one of these path
|
* @param has Target must have at least one of these path
|
||||||
*/
|
*/
|
||||||
static hasFatal(target: string | PathEvent | (string | PathEvent)[], ...has: (string | PathEvent)[]): void {
|
static hasFatal(target: string | PathEvent | (string | PathEvent)[], ...has: (string | PathEvent)[]): void {
|
||||||
if(!PathEvent.has(target, ...has)) throw new Error(`Requires one of: ${makeArray(has).join(', ')}`);
|
if(!PathEvent.has(target, ...has)) throw new PathError(`Requires one of: ${makeArray(has).join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as `hasAll` but raises an error if the target is missing any paths
|
* Same as `hasAll` but raises an error if the target is missing any paths
|
||||||
*
|
*
|
||||||
* @param {string | string[]} target Array of PathedEvents as strings or pre-parsed
|
* @param {string | string[]} target Array of Events as strings or pre-parsed
|
||||||
* @param has Target must have all these paths
|
* @param has Target must have all these paths
|
||||||
*/
|
*/
|
||||||
static hasAllFatal(target: string | PathEvent | (string | PathEvent)[], ...has: (string | PathEvent)[]): void {
|
static hasAllFatal(target: string | PathEvent | (string | PathEvent)[], ...has: (string | PathEvent)[]): void {
|
||||||
if(!PathEvent.hasAll(target, ...has)) throw new Error(`Requires all: ${makeArray(has).join(', ')}`);
|
if(!PathEvent.hasAll(target, ...has)) throw new PathError(`Requires all: ${makeArray(has).join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create pathed event string from its components
|
* Create event string from its components
|
||||||
*
|
*
|
||||||
* @param {string | string[]} path Event path
|
* @param {string | string[]} path Event path
|
||||||
* @param {Method} methods Event method
|
* @param {Method} methods Event method
|
||||||
* @return {string} String representation of PathedEvent
|
* @return {string} String representation of Event
|
||||||
*/
|
*/
|
||||||
static toString(path: string | string[], methods: Method | Method[]): string {
|
static toString(path: string | string[], methods: Method | Method[]): string {
|
||||||
let p = makeArray(path).filter(p => p != null).join('/');
|
let p = makeArray(path).filter(p => p != null).join('/');
|
||||||
@ -199,9 +201,9 @@ export class PathEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create pathed event string from its components
|
* Create event string from its components
|
||||||
*
|
*
|
||||||
* @return {string} String representation of PathedEvent
|
* @return {string} String representation of Event
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
return PathEvent.toString(this.fullPath, this.methods);
|
return PathEvent.toString(this.fullPath, this.methods);
|
||||||
@ -211,18 +213,18 @@ export class PathEvent {
|
|||||||
export type PathListener = (event: PathEvent, ...args: any[]) => any;
|
export type PathListener = (event: PathEvent, ...args: any[]) => any;
|
||||||
export type PathUnsubscribe = () => void;
|
export type PathUnsubscribe = () => void;
|
||||||
|
|
||||||
export interface IPathedEventEmitter {
|
export interface IPathEventEmitter {
|
||||||
emit(event: string, ...args: any[]): void;
|
emit(event: string, ...args: any[]): void;
|
||||||
off(listener: PathListener): void;
|
off(listener: PathListener): void;
|
||||||
on(event: string, listener: PathListener): PathUnsubscribe;
|
on(event: string, listener: PathListener): PathUnsubscribe;
|
||||||
once(event: string, listener?: PathListener): Promise<any>;
|
once(event: string, listener?: PathListener): Promise<any>;
|
||||||
relayEvents(emitter: PathedEventEmitter): void;
|
relayEvents(emitter: PathEventEmitter): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event emitter that uses paths allowing listeners to listen to different combinations of modules, paths & methods
|
* Event emitter that uses paths allowing listeners to listen to different combinations of modules, paths & methods
|
||||||
*/
|
*/
|
||||||
export class PathedEventEmitter implements IPathedEventEmitter{
|
export class PathEventEmitter implements IPathEventEmitter{
|
||||||
private listeners: [PathEvent, PathListener][] = [];
|
private listeners: [PathEvent, PathListener][] = [];
|
||||||
|
|
||||||
emit(event: string | PathEvent, ...args: any[]) {
|
emit(event: string | PathEvent, ...args: any[]) {
|
||||||
@ -250,7 +252,7 @@ export class PathedEventEmitter implements IPathedEventEmitter{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
relayEvents(emitter: IPathedEventEmitter) {
|
relayEvents(emitter: IPathEventEmitter) {
|
||||||
emitter.on('*', (event, ...args) => this.emit(event, ...args));
|
emitter.on('*', (event, ...args) => this.emit(event, ...args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user