Setup eslint and enforce rules
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
2
src/util/cache/Cache.ts
vendored
2
src/util/cache/Cache.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import {Awaitable} from "../support/types"
|
||||
import {Awaitable} from '../support/types'
|
||||
|
||||
/**
|
||||
* Abstract interface class for a cached object.
|
||||
|
||||
20
src/util/cache/InMemCache.ts
vendored
20
src/util/cache/InMemCache.ts
vendored
@@ -20,22 +20,28 @@ export class InMemCache extends Cache {
|
||||
*/
|
||||
protected items: Collection<InMemCacheItem> = new Collection<InMemCacheItem>()
|
||||
|
||||
public async fetch(key: string) {
|
||||
public async fetch(key: string): Promise<string | undefined> {
|
||||
const item = this.items.firstWhere('key', '=', key)
|
||||
if ( item ) return item.item
|
||||
if ( item ) {
|
||||
return item.item
|
||||
}
|
||||
}
|
||||
|
||||
public async put(key: string, item: string) {
|
||||
public async put(key: string, item: string): Promise<void> {
|
||||
const existing = this.items.firstWhere('key', '=', key)
|
||||
if ( existing ) existing.item = item
|
||||
else this.items.push({ key, item })
|
||||
if ( existing ) {
|
||||
existing.item = item
|
||||
} else {
|
||||
this.items.push({ key,
|
||||
item })
|
||||
}
|
||||
}
|
||||
|
||||
public async has(key: string) {
|
||||
public async has(key: string): Promise<boolean> {
|
||||
return this.items.where('key', '=', key).length > 0
|
||||
}
|
||||
|
||||
public async drop(key: string) {
|
||||
public async drop(key: string): Promise<void> {
|
||||
this.items = this.items.whereNot('key', '=', key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Iterable } from './Iterable'
|
||||
import { collect } from './Collection'
|
||||
import {collect, Collection} from './Collection'
|
||||
|
||||
/**
|
||||
* A basic Iterable implementation that uses an array as a backend.
|
||||
@@ -15,19 +15,19 @@ export class ArrayIterable<T> extends Iterable<T> {
|
||||
super()
|
||||
}
|
||||
|
||||
async at(i: number) {
|
||||
async at(i: number): Promise<T | undefined> {
|
||||
return this.items[i]
|
||||
}
|
||||
|
||||
async range(start: number, end: number) {
|
||||
async range(start: number, end: number): Promise<Collection<T>> {
|
||||
return collect(this.items.slice(start, end + 1))
|
||||
}
|
||||
|
||||
async count() {
|
||||
async count(): Promise<number> {
|
||||
return this.items.length
|
||||
}
|
||||
|
||||
clone() {
|
||||
clone(): ArrayIterable<T> {
|
||||
return new ArrayIterable([...this.items])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
Collection, CollectionIndex,
|
||||
CollectionItem, ComparisonFunction,
|
||||
DeterminesEquality, KeyFunction,
|
||||
KeyOperator, KeyReducerFunction, MaybeCollectionIndex, MaybeCollectionItem
|
||||
KeyOperator, KeyReducerFunction, MaybeCollectionIndex, MaybeCollectionItem,
|
||||
} from './Collection'
|
||||
import {Iterable, StopIteration} from './Iterable'
|
||||
import {applyWhere, WhereOperator} from './where'
|
||||
@@ -20,53 +20,56 @@ export class AsyncCollection<T> {
|
||||
* Iterable of items to base this collction on.
|
||||
* @type Iterable
|
||||
*/
|
||||
private _items: Iterable<T>,
|
||||
private storedItems: Iterable<T>,
|
||||
|
||||
/**
|
||||
* Size to use when chunking results for memory-optimization.
|
||||
* @type number
|
||||
*/
|
||||
private _chunk_size: number = 1000, // TODO fix this. It's just for testing
|
||||
private iteratorChunkSize: number = 1000, // TODO fix this. It's just for testing
|
||||
) {}
|
||||
|
||||
private async _chunk(callback: (items: Collection<T>) => any): Promise<void> {
|
||||
await this._items.chunk(this._chunk_size, async items => {
|
||||
private async inChunks(callback: (items: Collection<T>) => any): Promise<void> {
|
||||
await this.storedItems.chunk(this.iteratorChunkSize, async items => {
|
||||
await callback(items)
|
||||
})
|
||||
await this._items.reset()
|
||||
await this.storedItems.reset()
|
||||
}
|
||||
|
||||
private async _chunk_all<T2>(key: KeyOperator<T, T2>, callback: (items: Collection<T2>) => any): Promise<void> {
|
||||
await this._items.chunk(this._chunk_size, async items => {
|
||||
private async inChunksAll<T2>(key: KeyOperator<T, T2>, callback: (items: Collection<T2>) => any): Promise<void> {
|
||||
await this.storedItems.chunk(this.iteratorChunkSize, async items => {
|
||||
await callback(items.pluck(key))
|
||||
})
|
||||
await this._items.reset()
|
||||
await this.storedItems.reset()
|
||||
}
|
||||
|
||||
private async _chunk_all_numbers<T2>(key: KeyOperator<T, T2>, callback: (items: number[]) => any): Promise<void> {
|
||||
await this._items.chunk(this._chunk_size, async items => {
|
||||
await callback(items.pluck(key).map(x => Number(x)).all())
|
||||
private async inChunksAllNumbers<T2>(key: KeyOperator<T, T2>, callback: (items: number[]) => any): Promise<void> {
|
||||
await this.storedItems.chunk(this.iteratorChunkSize, async items => {
|
||||
await callback(items.pluck(key).map(x => Number(x))
|
||||
.all())
|
||||
})
|
||||
await this._items.reset()
|
||||
await this.storedItems.reset()
|
||||
}
|
||||
|
||||
private async _chunk_all_associate<T2>(key: KeyOperator<T, T2>, callback: (items: AssociatedCollectionItem<T2, T>[]) => any): Promise<void> {
|
||||
await this._items.chunk(this._chunk_size, async items => {
|
||||
const assoc_items: AssociatedCollectionItem<T2, T>[] = []
|
||||
private async inChunksAllAssociated<T2>(key: KeyOperator<T, T2>, callback: (items: AssociatedCollectionItem<T2, T>[]) => any): Promise<void> {
|
||||
await this.storedItems.chunk(this.iteratorChunkSize, async items => {
|
||||
const assocItems: AssociatedCollectionItem<T2, T>[] = []
|
||||
if ( typeof key === 'function' ) {
|
||||
items.map((item, index) => {
|
||||
const key_item = key(item, index)
|
||||
assoc_items.push({ key: key_item, item })
|
||||
const keyItem = key(item, index)
|
||||
assocItems.push({ key: keyItem,
|
||||
item })
|
||||
})
|
||||
} else {
|
||||
items.map((item, index) => {
|
||||
assoc_items.push({key: (<any>item)[key], item})
|
||||
items.map(item => {
|
||||
assocItems.push({key: (<any>item)[key],
|
||||
item})
|
||||
})
|
||||
}
|
||||
|
||||
await callback(assoc_items)
|
||||
await callback(assocItems)
|
||||
})
|
||||
await this._items.reset()
|
||||
await this.storedItems.reset()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +77,7 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<array>
|
||||
*/
|
||||
async all(): Promise<CollectionItem<T>[]> {
|
||||
return (await this._items.all()).toArray()
|
||||
return (await this.storedItems.all()).toArray()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +85,7 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async collect(): Promise<Collection<T>> {
|
||||
return this._items.all()
|
||||
return this.storedItems.all()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,20 +94,23 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<number>
|
||||
*/
|
||||
async average<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||
let running_total = 0
|
||||
let running_items = 0
|
||||
let runningTotal = 0
|
||||
let runningItems = 0
|
||||
|
||||
const chunk_helper = (items: number[]) => {
|
||||
running_items += items.length
|
||||
running_total += items.reduce((prev, curr) => prev + curr)
|
||||
const chunkHelper = (items: number[]) => {
|
||||
runningItems += items.length
|
||||
runningTotal += items.reduce((prev, curr) => prev + curr)
|
||||
}
|
||||
|
||||
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
|
||||
else await this._chunk((items) => {
|
||||
chunk_helper(items.map(x => Number(x)).all())
|
||||
})
|
||||
if ( key ) {
|
||||
await this.inChunksAllNumbers(key, chunkHelper)
|
||||
} else {
|
||||
await this.inChunks((items) => {
|
||||
chunkHelper(items.map(x => Number(x)).all())
|
||||
})
|
||||
}
|
||||
|
||||
return running_total / running_items
|
||||
return runningTotal / runningItems
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,19 +121,25 @@ export class AsyncCollection<T> {
|
||||
async median<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||
let items: number[] = []
|
||||
|
||||
const chunk_helper = (next_items: number[]) => {
|
||||
items = items.concat(next_items)
|
||||
const chunkHelper = (nextItems: number[]) => {
|
||||
items = items.concat(nextItems)
|
||||
}
|
||||
|
||||
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
|
||||
else await this._chunk(items => {
|
||||
chunk_helper(items.map(x => Number(x)).all())
|
||||
})
|
||||
if ( key ) {
|
||||
await this.inChunksAllNumbers(key, chunkHelper)
|
||||
} else {
|
||||
await this.inChunks(chunkItems => {
|
||||
chunkHelper(chunkItems.map(x => Number(x)).all())
|
||||
})
|
||||
}
|
||||
|
||||
items = items.sort((a, b) => a - b)
|
||||
const middle = Math.floor((items.length - 1) / 2)
|
||||
if ( items.length % 2 ) return items[middle]
|
||||
else return (items[middle] + items[middle + 1]) / 2
|
||||
if ( items.length % 2 ) {
|
||||
return items[middle]
|
||||
} else {
|
||||
return (items[middle] + items[middle + 1]) / 2
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,19 +148,25 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<number>
|
||||
*/
|
||||
async mode<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||
let counts: any = {}
|
||||
const counts: any = {}
|
||||
|
||||
const chunk_helper = (items: number[]) => {
|
||||
const chunkHelper = (items: number[]) => {
|
||||
for ( const item of items ) {
|
||||
if ( !counts[item] ) counts[item] = 1
|
||||
else counts[item] += 1
|
||||
if ( !counts[item] ) {
|
||||
counts[item] = 1
|
||||
} else {
|
||||
counts[item] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
|
||||
else await this._chunk(items => {
|
||||
chunk_helper(items.map(x => Number(x)).all())
|
||||
})
|
||||
if ( key ) {
|
||||
await this.inChunksAllNumbers(key, chunkHelper)
|
||||
} else {
|
||||
await this.inChunks(items => {
|
||||
chunkHelper(items.map(x => Number(x)).all())
|
||||
})
|
||||
}
|
||||
|
||||
return Number(Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b)[0])
|
||||
}
|
||||
@@ -171,10 +189,10 @@ export class AsyncCollection<T> {
|
||||
* @param [operand]
|
||||
* @return Promise<boolean>
|
||||
*/
|
||||
async contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
|
||||
async contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<boolean> {
|
||||
let contains = false
|
||||
|
||||
await this._chunk_all_associate(key, (items: AssociatedCollectionItem<T2, T>[]) => {
|
||||
await this.inChunksAllAssociated(key, (items: AssociatedCollectionItem<T2, T>[]) => {
|
||||
const matches = applyWhere(items, operator, operand)
|
||||
if ( matches.length > 0 ) {
|
||||
contains = true
|
||||
@@ -190,7 +208,7 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<AsyncCollection>
|
||||
*/
|
||||
async clone(): Promise<AsyncCollection<T>> {
|
||||
return new AsyncCollection<T>(await this._items.clone())
|
||||
return new AsyncCollection<T>(await this.storedItems.clone())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +219,7 @@ export class AsyncCollection<T> {
|
||||
async diff(items: AsyncCollectionComparable<T>): Promise<Collection<T>> {
|
||||
const matches: T[] = []
|
||||
|
||||
await this._chunk(async chunk => {
|
||||
await this.inChunks(async chunk => {
|
||||
for ( const item of chunk.all() ) {
|
||||
if ( !(await items.includes(item)) ) {
|
||||
matches.push(item)
|
||||
@@ -222,10 +240,11 @@ export class AsyncCollection<T> {
|
||||
async diffUsing(items: AsyncCollectionComparable<T>, compare: DeterminesEquality<T>): Promise<Collection<T>> {
|
||||
const matches: T[] = []
|
||||
|
||||
await this._chunk(async chunk => {
|
||||
await this.inChunks(async chunk => {
|
||||
for ( const item of chunk.all() ) {
|
||||
if ( !(await items.some(exc => compare(item, exc))) )
|
||||
if ( !(await items.some(exc => compare(item, exc))) ) {
|
||||
matches.push(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -240,7 +259,7 @@ export class AsyncCollection<T> {
|
||||
async includes(item: CollectionItem<T>): Promise<boolean> {
|
||||
let contains = false
|
||||
|
||||
await this._chunk(items => {
|
||||
await this.inChunks(items => {
|
||||
if ( items.includes(item) ) {
|
||||
contains = true
|
||||
throw new StopIteration()
|
||||
@@ -258,7 +277,7 @@ export class AsyncCollection<T> {
|
||||
async some(operator: (item: T) => boolean): Promise<boolean> {
|
||||
let contains = false
|
||||
|
||||
await this._chunk(items => {
|
||||
await this.inChunks(items => {
|
||||
for ( const item of items.all() ) {
|
||||
if ( operator(item) ) {
|
||||
contains = true
|
||||
@@ -278,7 +297,7 @@ export class AsyncCollection<T> {
|
||||
async each<T2>(func: AsyncKeyFunction<T, T2>): Promise<void> {
|
||||
let index = 0
|
||||
|
||||
await this._chunk(async items => {
|
||||
await this.inChunks(async items => {
|
||||
for ( const item of items.all() ) {
|
||||
await func(item, index)
|
||||
index += 1
|
||||
@@ -292,11 +311,11 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async map<T2>(func: AsyncKeyFunction<T, T2>): Promise<Collection<T2>> {
|
||||
const new_items: CollectionItem<T2>[] = []
|
||||
const newItems: CollectionItem<T2>[] = []
|
||||
await this.each(async (item, index) => {
|
||||
new_items.push(await func(item, index))
|
||||
newItems.push(await func(item, index))
|
||||
})
|
||||
return new Collection<T2>(new_items)
|
||||
return new Collection<T2>(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,7 +327,7 @@ export class AsyncCollection<T> {
|
||||
let pass = true
|
||||
let index = 0
|
||||
|
||||
await this._chunk(async items => {
|
||||
await this.inChunks(async items => {
|
||||
for ( const item of items.all() ) {
|
||||
if ( !(await func(item, index)) ) {
|
||||
pass = false
|
||||
@@ -328,10 +347,10 @@ export class AsyncCollection<T> {
|
||||
* @param {WhereOperator} operator
|
||||
* @param [operand]
|
||||
*/
|
||||
async everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
|
||||
async everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<boolean> {
|
||||
let pass = true
|
||||
|
||||
await this._chunk(async items => {
|
||||
await this.inChunks(async items => {
|
||||
pass = pass && items.everyWhere(key, operator, operand)
|
||||
if ( !pass ) {
|
||||
throw new StopIteration()
|
||||
@@ -347,13 +366,13 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async filter<T2>(func: KeyFunction<T, T2>): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
let newItems: CollectionItem<T>[] = []
|
||||
|
||||
await this._chunk(async items => {
|
||||
new_items = new_items.concat(items.filter(func).all())
|
||||
await this.inChunks(async items => {
|
||||
newItems = newItems.concat(items.filter(func).all())
|
||||
})
|
||||
|
||||
return new Collection<T>(new_items)
|
||||
return new Collection<T>(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,7 +382,9 @@ export class AsyncCollection<T> {
|
||||
* @return AsyncCollection
|
||||
*/
|
||||
when<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
|
||||
if ( bool ) then(this)
|
||||
if ( bool ) {
|
||||
then(this)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -374,7 +395,9 @@ export class AsyncCollection<T> {
|
||||
* @return AsyncCollection
|
||||
*/
|
||||
unless<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
|
||||
if ( !bool ) then(this)
|
||||
if ( !bool ) {
|
||||
then(this)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -385,12 +408,12 @@ export class AsyncCollection<T> {
|
||||
* @param [operand]
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
await this._chunk(async items => {
|
||||
new_items = new_items.concat(items.where(key, operator, operand).all())
|
||||
async where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<Collection<T>> {
|
||||
let newItems: CollectionItem<T>[] = []
|
||||
await this.inChunks(async items => {
|
||||
newItems = newItems.concat(items.where(key, operator, operand).all())
|
||||
})
|
||||
return new Collection<T>(new_items)
|
||||
return new Collection<T>(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,12 +424,12 @@ export class AsyncCollection<T> {
|
||||
* @param [operand]
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
await this._chunk(async items => {
|
||||
new_items = new_items.concat(items.whereNot(key, operator, operand).all())
|
||||
async whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<Collection<T>> {
|
||||
let newItems: CollectionItem<T>[] = []
|
||||
await this.inChunks(async items => {
|
||||
newItems = newItems.concat(items.whereNot(key, operator, operand).all())
|
||||
})
|
||||
return new Collection<T>(new_items)
|
||||
return new Collection<T>(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,15 +439,15 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async whereIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
await this._chunk_all_associate(key,async chunk => {
|
||||
const newItems: CollectionItem<T>[] = []
|
||||
await this.inChunksAllAssociated(key, async chunk => {
|
||||
for ( const item of chunk ) {
|
||||
if ( await items.includes(item.key) ) {
|
||||
new_items.push(item.item)
|
||||
newItems.push(item.item)
|
||||
}
|
||||
}
|
||||
})
|
||||
return new Collection<T>(new_items)
|
||||
return new Collection<T>(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,15 +458,15 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async whereNotIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
await this._chunk_all_associate(key,async chunk => {
|
||||
const newItems: CollectionItem<T>[] = []
|
||||
await this.inChunksAllAssociated(key, async chunk => {
|
||||
for ( const item of chunk ) {
|
||||
if ( !(await items.includes(item.key)) ) {
|
||||
new_items.push(item.item)
|
||||
newItems.push(item.item)
|
||||
}
|
||||
}
|
||||
})
|
||||
return new Collection<T>(new_items)
|
||||
return new Collection<T>(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,7 +474,7 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<MaybeCollectionItem>
|
||||
*/
|
||||
async first(): Promise<MaybeCollectionItem<T>> {
|
||||
return this._items.at(0)
|
||||
return this.storedItems.at(0)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,7 +486,7 @@ export class AsyncCollection<T> {
|
||||
*/
|
||||
async firstWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
|
||||
let item = undefined
|
||||
await this._chunk_all_associate(key, async items => {
|
||||
await this.inChunksAllAssociated(key, async items => {
|
||||
const matches = applyWhere(items, operator, operand)
|
||||
if ( matches.length > 0 ) {
|
||||
item = matches[0]
|
||||
@@ -481,7 +504,7 @@ export class AsyncCollection<T> {
|
||||
*/
|
||||
async firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
|
||||
let item: MaybeCollectionItem<T> = undefined
|
||||
await this._chunk(async items => {
|
||||
await this.inChunks(async items => {
|
||||
const matches = items.whereNot(key, operator, operand)
|
||||
if ( matches.length > 0 ) {
|
||||
item = matches.first()
|
||||
@@ -495,16 +518,16 @@ export class AsyncCollection<T> {
|
||||
* Returns the number of elements in this collection.
|
||||
* @return Promise<number>
|
||||
*/
|
||||
async count() {
|
||||
return this._items.count()
|
||||
async count(): Promise<number> {
|
||||
return this.storedItems.count()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of elements in this collection.
|
||||
* @return Promise<number>
|
||||
*/
|
||||
async length() {
|
||||
return this._items.count()
|
||||
async length(): Promise<number> {
|
||||
return this.storedItems.count()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,9 +537,12 @@ export class AsyncCollection<T> {
|
||||
* @param [fallback]
|
||||
* @return Promise<any>
|
||||
*/
|
||||
async get(index: number, fallback?: any) {
|
||||
if ( (await this.count()) > index ) return this._items.at(index)
|
||||
else return fallback
|
||||
async get(index: number, fallback?: T): Promise<T | undefined> {
|
||||
if ( (await this.count()) > index ) {
|
||||
return this.storedItems.at(index)
|
||||
} else {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -553,13 +579,13 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<string>
|
||||
*/
|
||||
async join(delimiter: string): Promise<string> {
|
||||
let running_strings: string[] = []
|
||||
const runningStrings: string[] = []
|
||||
|
||||
await this._chunk(async items => {
|
||||
running_strings.push(items.join(delimiter))
|
||||
await this.inChunks(async items => {
|
||||
runningStrings.push(items.join(delimiter))
|
||||
})
|
||||
|
||||
return running_strings.join(delimiter)
|
||||
return runningStrings.join(delimiter)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -580,7 +606,7 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<boolean>
|
||||
*/
|
||||
async isEmpty(): Promise<boolean> {
|
||||
return (await this._items.count()) < 1
|
||||
return (await this.storedItems.count()) < 1
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -588,7 +614,7 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<boolean>
|
||||
*/
|
||||
async isNotEmpty(): Promise<boolean> {
|
||||
return (await this._items.count()) > 0
|
||||
return (await this.storedItems.count()) > 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,8 +622,10 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<MaybeCollectionItem>
|
||||
*/
|
||||
async last(): Promise<MaybeCollectionItem<T>> {
|
||||
const length = await this._items.count()
|
||||
if ( length > 0 ) return this._items.at(length - 1)
|
||||
const length = await this.storedItems.count()
|
||||
if ( length > 0 ) {
|
||||
return this.storedItems.at(length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -607,7 +635,7 @@ export class AsyncCollection<T> {
|
||||
* @param [operand]
|
||||
* @return Promise<MaybeCollectionItem>
|
||||
*/
|
||||
async lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
|
||||
async lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<MaybeCollectionItem<T>> {
|
||||
return (await this.where(key, operator, operand)).last()
|
||||
}
|
||||
|
||||
@@ -618,7 +646,7 @@ export class AsyncCollection<T> {
|
||||
* @param [operand]
|
||||
* @return Promise<MaybeCollectionItem>
|
||||
*/
|
||||
async lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
|
||||
async lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<MaybeCollectionItem<T>> {
|
||||
return (await this.whereNot(key, operator, operand)).last()
|
||||
}
|
||||
|
||||
@@ -631,13 +659,13 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async pluck<T2>(key: KeyOperator<T, T2>): Promise<Collection<T2>> {
|
||||
let new_items: CollectionItem<T2>[] = []
|
||||
let newItems: CollectionItem<T2>[] = []
|
||||
|
||||
await this._chunk_all(key, async items => {
|
||||
new_items = new_items.concat(items.all())
|
||||
await this.inChunksAll(key, async items => {
|
||||
newItems = newItems.concat(items.all())
|
||||
})
|
||||
|
||||
return new Collection<T2>(new_items)
|
||||
return new Collection<T2>(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -645,17 +673,19 @@ export class AsyncCollection<T> {
|
||||
* @param {KeyOperator} key
|
||||
* @return Promise<number>
|
||||
*/
|
||||
async max<T2>(key: KeyOperator<T, T2>): Promise<number> {
|
||||
let running_max: number
|
||||
async max<T2>(key: KeyOperator<T, T2>): Promise<number | undefined> {
|
||||
let runningMax: number | undefined = undefined
|
||||
|
||||
await this._chunk_all_numbers(key, async items => {
|
||||
const local_max = Math.max(...items)
|
||||
if ( typeof running_max === 'undefined' ) running_max = local_max
|
||||
else running_max = Math.max(running_max, local_max)
|
||||
await this.inChunksAllNumbers(key, async items => {
|
||||
const localMax = Math.max(...items)
|
||||
if ( typeof runningMax === 'undefined' ) {
|
||||
runningMax = localMax
|
||||
} else {
|
||||
runningMax = Math.max(runningMax, localMax)
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
return running_max
|
||||
return runningMax
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -672,17 +702,19 @@ export class AsyncCollection<T> {
|
||||
* @param {KeyOperator} key
|
||||
* @return Promise<number>
|
||||
*/
|
||||
async min<T2>(key: KeyOperator<T, T2>): Promise<number> {
|
||||
let running_min: number
|
||||
async min<T2>(key: KeyOperator<T, T2>): Promise<number | undefined> {
|
||||
let runningMin: number | undefined = undefined
|
||||
|
||||
await this._chunk_all_numbers(key, async items => {
|
||||
const local_min = Math.min(...items)
|
||||
if ( typeof running_min === 'undefined' ) running_min = local_min
|
||||
else running_min = Math.min(running_min, local_min)
|
||||
await this.inChunksAllNumbers(key, async items => {
|
||||
const localMin = Math.min(...items)
|
||||
if ( typeof runningMin === 'undefined' ) {
|
||||
runningMin = localMin
|
||||
} else {
|
||||
runningMin = Math.min(runningMin, localMin)
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
return running_min
|
||||
return runningMin
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -696,16 +728,19 @@ export class AsyncCollection<T> {
|
||||
|
||||
/**
|
||||
* Merge the two collections.
|
||||
* @param {AsyncCollectionComparable} merge_with
|
||||
* @param {AsyncCollectionComparable} mergeWith
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async merge<T2>(merge_with: AsyncCollectionComparable<T2>): Promise<Collection<T|T2>> {
|
||||
let items: T2[]
|
||||
if ( merge_with instanceof Collection ) items = await merge_with.all()
|
||||
else if ( merge_with instanceof AsyncCollection ) items = await merge_with.all()
|
||||
else if ( Array.isArray(merge_with) ) items = merge_with
|
||||
async merge<T2>(mergeWith: AsyncCollectionComparable<T2>): Promise<Collection<T|T2>> {
|
||||
let items: T2[] = []
|
||||
if ( mergeWith instanceof Collection ) {
|
||||
items = await mergeWith.all()
|
||||
} else if ( mergeWith instanceof AsyncCollection ) {
|
||||
items = await mergeWith.all()
|
||||
} else if ( Array.isArray(mergeWith) ) {
|
||||
items = mergeWith
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return new Collection<T|T2>([...items, ...await this.all()])
|
||||
}
|
||||
|
||||
@@ -718,11 +753,15 @@ export class AsyncCollection<T> {
|
||||
const matches: CollectionItem<T>[] = []
|
||||
let current = 1
|
||||
|
||||
await this._chunk(async chunk => {
|
||||
await this.inChunks(async chunk => {
|
||||
for ( const item of chunk.all() ) {
|
||||
if ( current === 1 ) matches.push(item)
|
||||
if ( current === 1 ) {
|
||||
matches.push(item)
|
||||
}
|
||||
current += 1
|
||||
if ( current > n ) current = 1
|
||||
if ( current > n ) {
|
||||
current = 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -732,12 +771,12 @@ export class AsyncCollection<T> {
|
||||
/**
|
||||
* Return a collection containing the items that would be on the given page, with the given number of items per page.
|
||||
* @param {number} page
|
||||
* @param {number} per_page
|
||||
* @param {number} perPage
|
||||
*/
|
||||
async forPage(page: number, per_page: number): Promise<Collection<T>> {
|
||||
const start = page * per_page - per_page
|
||||
const end = page * per_page - 1
|
||||
return this._items.range(start, end)
|
||||
async forPage(page: number, perPage: number): Promise<Collection<T>> {
|
||||
const start = page * perPage - perPage
|
||||
const end = page * perPage - 1
|
||||
return this.storedItems.range(start, end)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,10 +787,10 @@ export class AsyncCollection<T> {
|
||||
return func(this)
|
||||
}
|
||||
|
||||
/*async pop(): Promise<MaybeCollectionItem<T>> {
|
||||
const next_item = await this._items.next()
|
||||
if ( !next_item.done ) {
|
||||
return next_item.value
|
||||
/* async pop(): Promise<MaybeCollectionItem<T>> {
|
||||
const nextItem = await this.storedItems.next()
|
||||
if ( !nextItem.done ) {
|
||||
return nextItem.value
|
||||
}
|
||||
}*/ // TODO Fix this
|
||||
|
||||
@@ -762,57 +801,60 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async random(n: number): Promise<Collection<T>> {
|
||||
const random_items: CollectionItem<T>[] = []
|
||||
const fetched_indices: number[] = []
|
||||
const randomItems: CollectionItem<T>[] = []
|
||||
const fetchedIndices: number[] = []
|
||||
|
||||
const max_n = await this._items.count()
|
||||
if ( n > max_n ) n = max_n
|
||||
const maxN = await this.storedItems.count()
|
||||
if ( n > maxN ) {
|
||||
n = maxN
|
||||
}
|
||||
|
||||
while ( random_items.length < n ) {
|
||||
const index = Math.floor(Math.random() * max_n)
|
||||
while ( randomItems.length < n ) {
|
||||
const index = Math.floor(Math.random() * maxN)
|
||||
|
||||
if ( !fetched_indices.includes(index) ) {
|
||||
fetched_indices.push(index)
|
||||
const item = await this._items.at(index)
|
||||
if ( typeof item !== 'undefined' )
|
||||
random_items.push(item)
|
||||
if ( !fetchedIndices.includes(index) ) {
|
||||
fetchedIndices.push(index)
|
||||
const item = await this.storedItems.at(index)
|
||||
if ( typeof item !== 'undefined' ) {
|
||||
randomItems.push(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Collection<T>(random_items)
|
||||
return new Collection<T>(randomItems)
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the collection into a single value using a reducer function.
|
||||
* @param {KeyReducerFunction} reducer
|
||||
* @param [initial_value]
|
||||
* @param [initialValue]
|
||||
* @return Promise<any>
|
||||
*/
|
||||
async reduce<T2>(reducer: KeyReducerFunction<T, T2>, initial_value?: T2): Promise<T2 | undefined> {
|
||||
let current_value = initial_value
|
||||
async reduce<T2>(reducer: KeyReducerFunction<T, T2>, initialValue?: T2): Promise<T2 | undefined> {
|
||||
let currentValue = initialValue
|
||||
let index = 0
|
||||
|
||||
await this._chunk(async items => {
|
||||
await this.inChunks(async items => {
|
||||
for ( const item of items.all() ) {
|
||||
current_value = reducer(current_value, item, index)
|
||||
currentValue = reducer(currentValue, item, index)
|
||||
index += 1
|
||||
}
|
||||
})
|
||||
|
||||
return current_value
|
||||
return currentValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of items that fail the truth test.
|
||||
* @param {AsyncKeyFunction} truth_test
|
||||
* @param {AsyncKeyFunction} truthTestFunction
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async reject<T2>(truth_test: AsyncKeyFunction<T, T2>): Promise<Collection<T>> {
|
||||
async reject<T2>(truthTestFunction: AsyncKeyFunction<T, T2>): Promise<Collection<T>> {
|
||||
let rejected: CollectionItem<T>[] = []
|
||||
|
||||
await this._chunk(async items => {
|
||||
await this.inChunks(async items => {
|
||||
rejected = rejected.concat(items.all().filter((item, index) => {
|
||||
return !truth_test(item, index)
|
||||
return !truthTestFunction(item, index)
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -833,13 +875,13 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<MaybeCollectionIndex>
|
||||
*/
|
||||
async search(item: CollectionItem<T>): Promise<MaybeCollectionIndex> {
|
||||
let found_index
|
||||
let foundIndex
|
||||
let index = 0
|
||||
|
||||
await this._chunk(async items => {
|
||||
items.some(possible_item => {
|
||||
if ( possible_item === item ) {
|
||||
found_index = index
|
||||
await this.inChunks(async items => {
|
||||
items.some(possibleItem => {
|
||||
if ( possibleItem === item ) {
|
||||
foundIndex = index
|
||||
throw new StopIteration()
|
||||
}
|
||||
|
||||
@@ -848,7 +890,7 @@ export class AsyncCollection<T> {
|
||||
})
|
||||
})
|
||||
|
||||
return found_index
|
||||
return foundIndex
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -856,9 +898,9 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<MaybeCollectionItem>
|
||||
*/
|
||||
async shift(): Promise<MaybeCollectionItem<T>> {
|
||||
const next_item = await this._items.next()
|
||||
if ( !next_item.done ) {
|
||||
return next_item.value
|
||||
const nextItem = await this.storedItems.next()
|
||||
if ( !nextItem.done ) {
|
||||
return nextItem.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,16 +919,16 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async slice(start: number, end: number): Promise<Collection<T>> {
|
||||
return this._items.range(start, end - 1)
|
||||
return this.storedItems.range(start, end - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the collection, optionally with the given comparison function.
|
||||
* @param {ComparisonFunction} compare_func
|
||||
* @param {ComparisonFunction} comparisonFunction
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async sort(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
|
||||
return (await this.collect()).sort(compare_func)
|
||||
async sort(comparisonFunction?: ComparisonFunction<T>): Promise<Collection<T>> {
|
||||
return (await this.collect()).sort(comparisonFunction)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -900,11 +942,11 @@ export class AsyncCollection<T> {
|
||||
|
||||
/**
|
||||
* Reverse sort the collection, optionally with the given comparison function.
|
||||
* @param {ComparisonFunction} compare_func
|
||||
* @param {ComparisonFunction} comparisonFunction
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async sortDesc(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
|
||||
return (await this.collect()).sortDesc(compare_func)
|
||||
async sortDesc(comparisonFunction?: ComparisonFunction<T>): Promise<Collection<T>> {
|
||||
return (await this.collect()).sortDesc(comparisonFunction)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -932,20 +974,23 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<number>
|
||||
*/
|
||||
async sum<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||
let running_sum: number = 0
|
||||
let runningSum = 0
|
||||
|
||||
const chunk_handler = (items: number[]) => {
|
||||
const chunkHandler = (items: number[]) => {
|
||||
for ( const item of items ) {
|
||||
running_sum += item
|
||||
runningSum += item
|
||||
}
|
||||
}
|
||||
|
||||
if ( key ) await this._chunk_all_numbers(key, chunk_handler)
|
||||
else await this._chunk(async chunk => {
|
||||
chunk_handler(chunk.map(x => Number(x)).all())
|
||||
})
|
||||
if ( key ) {
|
||||
await this.inChunksAllNumbers(key, chunkHandler)
|
||||
} else {
|
||||
await this.inChunks(async chunk => {
|
||||
chunkHandler(chunk.map(x => Number(x)).all())
|
||||
})
|
||||
}
|
||||
|
||||
return running_sum
|
||||
return runningSum
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -954,12 +999,13 @@ export class AsyncCollection<T> {
|
||||
* @return Promise<Collection>
|
||||
*/
|
||||
async take(limit: number): Promise<Collection<T>> {
|
||||
if ( limit === 0 ) return new Collection<T>()
|
||||
else if ( limit > 0 ) {
|
||||
if ( limit === 0 ) {
|
||||
return new Collection<T>()
|
||||
} else if ( limit > 0 ) {
|
||||
return this.slice(0, limit)
|
||||
} else {
|
||||
const cnt = await this._items.count()
|
||||
return this._items.range(cnt - (-1 * limit), cnt - 1)
|
||||
const cnt = await this.storedItems.count()
|
||||
return this.storedItems.range(cnt - (-1 * limit), cnt - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,15 +1028,19 @@ export class AsyncCollection<T> {
|
||||
const has: CollectionItem<T|T2>[] = []
|
||||
|
||||
if ( !key ) {
|
||||
await this._chunk(async items => {
|
||||
await this.inChunks(async items => {
|
||||
for ( const item of items.all() ) {
|
||||
if ( !has.includes(item) ) has.push(item)
|
||||
if ( !has.includes(item) ) {
|
||||
has.push(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await this._chunk_all(key, async items => {
|
||||
await this.inChunksAll(key, async items => {
|
||||
for ( const item of items.all() ) {
|
||||
if ( !has.includes(item) ) has.push(item)
|
||||
if ( !has.includes(item) ) {
|
||||
has.push(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1005,9 +1055,13 @@ export class AsyncCollection<T> {
|
||||
async toArray(): Promise<any[]> {
|
||||
const returns: any = []
|
||||
for ( const item of (await this.all()) ) {
|
||||
if ( item instanceof Collection ) returns.push(item.toArray())
|
||||
else if ( item instanceof AsyncCollection ) returns.push(await item.toArray())
|
||||
else returns.push(item)
|
||||
if ( item instanceof Collection ) {
|
||||
returns.push(item.toArray())
|
||||
} else if ( item instanceof AsyncCollection ) {
|
||||
returns.push(await item.toArray())
|
||||
} else {
|
||||
returns.push(item)
|
||||
}
|
||||
}
|
||||
return returns
|
||||
}
|
||||
@@ -1025,7 +1079,7 @@ export class AsyncCollection<T> {
|
||||
* Get a clone of the underlying iterator of this collection.
|
||||
* @return Iterable
|
||||
*/
|
||||
iterator() {
|
||||
return this._items.clone()
|
||||
iterator(): Iterable<T> {
|
||||
return this.storedItems.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Pipe} from "../support/Pipe";
|
||||
import {Pipe} from '../support/Pipe'
|
||||
|
||||
type CollectionItem<T> = T
|
||||
type MaybeCollectionItem<T> = CollectionItem<T> | undefined
|
||||
@@ -11,9 +11,9 @@ type CollectionComparable<T> = CollectionItem<T>[] | Collection<T>
|
||||
type DeterminesEquality<T> = (item: CollectionItem<T>, other: any) => boolean
|
||||
type CollectionIndex = number
|
||||
type MaybeCollectionIndex = CollectionIndex | undefined
|
||||
type ComparisonFunction<T> = (item: CollectionItem<T>, other_item: CollectionItem<T>) => number
|
||||
type ComparisonFunction<T> = (item: CollectionItem<T>, otherItem: CollectionItem<T>) => number
|
||||
|
||||
import { WhereOperator, applyWhere, whereMatch } from "./where";
|
||||
import { WhereOperator, applyWhere, whereMatch } from './where'
|
||||
|
||||
const collect = <T>(items: CollectionItem<T>[]): Collection<T> => Collection.collect(items)
|
||||
export {
|
||||
@@ -40,13 +40,13 @@ export {
|
||||
* Provides helpers for accessing sub-keys, filtering, piping, and aggregate functions.
|
||||
*/
|
||||
class Collection<T> {
|
||||
private _items: CollectionItem<T>[] = []
|
||||
private storedItems: CollectionItem<T>[] = []
|
||||
|
||||
/**
|
||||
* Create a new collection from an array of items.
|
||||
* @param items
|
||||
*/
|
||||
public static collect<T>(items: CollectionItem<T>[]): Collection<T> {
|
||||
public static collect<T2>(items: CollectionItem<T2>[]): Collection<T2> {
|
||||
return new Collection(items)
|
||||
}
|
||||
|
||||
@@ -64,56 +64,63 @@ class Collection<T> {
|
||||
* @param size
|
||||
* @param item
|
||||
*/
|
||||
public static fill<T>(size: number, item: T): Collection<T> {
|
||||
public static fill<T2>(size: number, item: T2): Collection<T2> {
|
||||
const arr = Array(size).fill(item)
|
||||
return new Collection<T>(arr)
|
||||
return new Collection<T2>(arr)
|
||||
}
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The items to base the collection on.
|
||||
*/
|
||||
items?: CollectionItem<T>[]
|
||||
items?: CollectionItem<T>[],
|
||||
) {
|
||||
if ( items )
|
||||
this._items = items
|
||||
if ( items ) {
|
||||
this.storedItems = items
|
||||
}
|
||||
}
|
||||
|
||||
private _all<T2>(key: KeyOperator<T, T2>): CollectionItem<T2>[] {
|
||||
private allOperator<T2>(key: KeyOperator<T, T2>): CollectionItem<T2>[] {
|
||||
let items: CollectionItem<T2>[] = []
|
||||
if ( typeof key === 'function' ) {
|
||||
items = this._items.map(key)
|
||||
items = this.storedItems.map(key)
|
||||
} else {
|
||||
items = this._items.map((item: CollectionItem<T>) => (<any>item)[key])
|
||||
items = this.storedItems.map((item: CollectionItem<T>) => (<any>item)[key])
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
private _all_numbers<T2>(key: KeyOperator<T, T2>): number[] {
|
||||
return this._all(key).map(value => Number(value))
|
||||
private allAsNumbers<T2>(key: KeyOperator<T, T2>): number[] {
|
||||
return this.allOperator(key).map(value => Number(value))
|
||||
}
|
||||
|
||||
private _all_associate<T2>(key: KeyOperator<T, T2>): AssociatedCollectionItem<T2, T>[] {
|
||||
const assoc_items: AssociatedCollectionItem<T2, T>[] = []
|
||||
let items = [...this._items]
|
||||
private allAssociated<T2>(key: KeyOperator<T, T2>): AssociatedCollectionItem<T2, T>[] {
|
||||
const associatedItems: AssociatedCollectionItem<T2, T>[] = []
|
||||
const items = [...this.storedItems]
|
||||
if ( typeof key === 'function' ) {
|
||||
items.map((item, index) => {
|
||||
const key_item = key(item, index)
|
||||
assoc_items.push({ key: key_item, item })
|
||||
const keyItem = key(item, index)
|
||||
associatedItems.push({
|
||||
key: keyItem,
|
||||
item,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
items.map((item, index) => {
|
||||
assoc_items.push({key: (<any>item)[key], item})
|
||||
items.map(item => {
|
||||
associatedItems.push({
|
||||
key: (<any>item)[key],
|
||||
item,
|
||||
})
|
||||
})
|
||||
}
|
||||
return assoc_items
|
||||
return associatedItems
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the collection to an array.
|
||||
*/
|
||||
all(): CollectionItem<T>[] {
|
||||
return [...this._items]
|
||||
return [...this.storedItems]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,11 +129,16 @@ class Collection<T> {
|
||||
*/
|
||||
average<T2>(key?: KeyOperator<T, T2>): number {
|
||||
let items
|
||||
if ( key ) items = this._all_numbers(key)
|
||||
else items = this._items.map(x => Number(x))
|
||||
if ( items.length === 0 ) return 0
|
||||
if ( key ) {
|
||||
items = this.allAsNumbers(key)
|
||||
} else {
|
||||
items = this.storedItems.map(x => Number(x))
|
||||
}
|
||||
if ( items.length === 0 ) {
|
||||
return 0
|
||||
}
|
||||
|
||||
let sum = items.reduce((prev, curr) => prev + curr)
|
||||
const sum = items.reduce((prev, curr) => prev + curr)
|
||||
return sum / items.length
|
||||
}
|
||||
|
||||
@@ -136,12 +148,18 @@ class Collection<T> {
|
||||
*/
|
||||
median<T2>(key?: KeyOperator<T, T2>): number {
|
||||
let items
|
||||
if ( key ) items = this._all_numbers(key).sort((a, b) => a - b)
|
||||
else items = this._items.map(x => Number(x)).sort((a, b) => a - b)
|
||||
if ( key ) {
|
||||
items = this.allAsNumbers(key).sort((a, b) => a - b)
|
||||
} else {
|
||||
items = this.storedItems.map(x => Number(x)).sort((a, b) => a - b)
|
||||
}
|
||||
|
||||
const middle = Math.floor((items.length - 1) / 2)
|
||||
if ( items.length % 2 ) return items[middle]
|
||||
else return (items[middle] + items[middle + 1]) / 2
|
||||
if ( items.length % 2 ) {
|
||||
return items[middle]
|
||||
} else {
|
||||
return (items[middle] + items[middle + 1]) / 2
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,10 +168,13 @@ class Collection<T> {
|
||||
*/
|
||||
mode<T2>(key?: KeyOperator<T, T2>): number {
|
||||
let items
|
||||
if ( key ) items = this._all_numbers(key).sort((a, b) => a - b)
|
||||
else items = this._items.map(x => Number(x)).sort((a, b) => a - b)
|
||||
if ( key ) {
|
||||
items = this.allAsNumbers(key).sort((a, b) => a - b)
|
||||
} else {
|
||||
items = this.storedItems.map(x => Number(x)).sort((a, b) => a - b)
|
||||
}
|
||||
|
||||
let counts: any = {}
|
||||
const counts: any = {}
|
||||
for ( const item of items ) {
|
||||
counts[item] = (counts[item] ?? -1) + 1
|
||||
}
|
||||
@@ -165,24 +186,22 @@ class Collection<T> {
|
||||
* Collapse a (potentially nested) collection of items down to a single dimension.
|
||||
*/
|
||||
collapse(): Collection<any> {
|
||||
const new_items: CollectionItem<T>[] = []
|
||||
const items = [...this._items]
|
||||
const get_layer = (current: CollectionItem<T>|CollectionItem<T>[]) => {
|
||||
const newItems: CollectionItem<T>[] = []
|
||||
const items = [...this.storedItems]
|
||||
const getLayer = (current: CollectionItem<T>|CollectionItem<T>[]) => {
|
||||
if ( typeof (<any>current)[Symbol.iterator] === 'function' ) {
|
||||
// @ts-ignore
|
||||
// TODO fix this
|
||||
for (const item of current) {
|
||||
for (const item of (current as any)) {
|
||||
if (Array.isArray(item)) {
|
||||
get_layer(item)
|
||||
getLayer(item)
|
||||
} else {
|
||||
new_items.push(item)
|
||||
newItems.push(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get_layer(items)
|
||||
return new Collection(new_items)
|
||||
getLayer(items)
|
||||
return new Collection(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,8 +216,8 @@ class Collection<T> {
|
||||
* @param operator
|
||||
* @param operand
|
||||
*/
|
||||
contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): boolean {
|
||||
const associate = this._all_associate(key)
|
||||
contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): boolean {
|
||||
const associate = this.allAssociated(key)
|
||||
const matches = applyWhere(associate, operator, operand)
|
||||
return matches.length > 0
|
||||
}
|
||||
@@ -210,7 +229,7 @@ class Collection<T> {
|
||||
* Does NOT deep copy the underlying items.
|
||||
*/
|
||||
clone(): Collection<T> {
|
||||
return new Collection<T>(this._items)
|
||||
return new Collection<T>(this.storedItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,8 +239,10 @@ class Collection<T> {
|
||||
diff<T2>(items: CollectionComparable<T|T2>): Collection<T> {
|
||||
const exclude = items instanceof Collection ? items.all() : items
|
||||
const matches = []
|
||||
for ( const item of [...this._items] ) {
|
||||
if ( !exclude.includes(item) ) matches.push(item)
|
||||
for ( const item of [...this.storedItems] ) {
|
||||
if ( !exclude.includes(item) ) {
|
||||
matches.push(item)
|
||||
}
|
||||
}
|
||||
return new Collection(matches)
|
||||
}
|
||||
@@ -241,9 +262,10 @@ class Collection<T> {
|
||||
diffUsing<T2>(items: CollectionComparable<T|T2>, compare: DeterminesEquality<T>): Collection<T> {
|
||||
const exclude = items instanceof Collection ? items.all() : items
|
||||
const matches = []
|
||||
for ( const item of [...this._items] ) {
|
||||
if ( !exclude.some(exc => compare(item, exc)) )
|
||||
for ( const item of [...this.storedItems] ) {
|
||||
if ( !exclude.some(exc => compare(item, exc)) ) {
|
||||
matches.push(item)
|
||||
}
|
||||
}
|
||||
return new Collection(matches)
|
||||
}
|
||||
@@ -254,7 +276,7 @@ class Collection<T> {
|
||||
* @param func
|
||||
*/
|
||||
some(func: (item: T) => boolean): boolean {
|
||||
return this._items.some(func)
|
||||
return this.storedItems.some(func)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,7 +284,7 @@ class Collection<T> {
|
||||
* @param func
|
||||
*/
|
||||
each<T2>(func: KeyFunction<T, T2>): void {
|
||||
this._items.map(func)
|
||||
this.storedItems.map(func)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,11 +292,11 @@ class Collection<T> {
|
||||
* @param func
|
||||
*/
|
||||
map<T2>(func: KeyFunction<T, T2>): Collection<T2> {
|
||||
const new_items: CollectionItem<T2>[] = []
|
||||
const newItems: CollectionItem<T2>[] = []
|
||||
this.each(((item, index) => {
|
||||
new_items.push(func(item, index))
|
||||
newItems.push(func(item, index))
|
||||
}))
|
||||
return new Collection<T2>(new_items)
|
||||
return new Collection<T2>(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +313,7 @@ class Collection<T> {
|
||||
keyBy(key: KeyOperator<T, string>): {[key: string]: T} {
|
||||
const obj: {[key: string]: T} = {}
|
||||
|
||||
this._all_associate(key).forEach(assoc => {
|
||||
this.allAssociated(key).forEach(assoc => {
|
||||
obj[assoc.key] = assoc.item
|
||||
})
|
||||
|
||||
@@ -314,13 +336,12 @@ class Collection<T> {
|
||||
keyMap<T2>(key: KeyOperator<T, string>, value: KeyOperator<T, T2>): {[key: string]: T2} {
|
||||
const obj: {[key: string]: T2} = {}
|
||||
|
||||
let i = -1;
|
||||
this._all_associate(key).forEach(assoc => {
|
||||
i += 1;
|
||||
let i = -1
|
||||
this.allAssociated(key).forEach(assoc => {
|
||||
i += 1
|
||||
|
||||
if ( typeof value === 'string' ) {
|
||||
// @ts-ignore
|
||||
obj[assoc.key] = assoc.item[value]
|
||||
obj[assoc.key] = (assoc.item as any)[value]
|
||||
} else {
|
||||
obj[assoc.key] = value(assoc.item, i)
|
||||
}
|
||||
@@ -334,11 +355,11 @@ class Collection<T> {
|
||||
* @param func
|
||||
*/
|
||||
every<T2>(func: KeyFunction<T, T2>): boolean {
|
||||
return this._items.every(func)
|
||||
return this.storedItems.every(func)
|
||||
}
|
||||
|
||||
everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): boolean {
|
||||
const items = this._all_associate(key)
|
||||
everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): boolean {
|
||||
const items = this.allAssociated(key)
|
||||
return items.every(item => whereMatch(item, operator, operand))
|
||||
}
|
||||
|
||||
@@ -347,7 +368,7 @@ class Collection<T> {
|
||||
* @param func
|
||||
*/
|
||||
filter<T2>(func: KeyFunction<T, T2>): Collection<T> {
|
||||
return new Collection(this._items.filter(func))
|
||||
return new Collection(this.storedItems.filter(func))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,14 +376,14 @@ class Collection<T> {
|
||||
* @param func
|
||||
*/
|
||||
find<T2>(func: KeyFunction<T, T2>): number | undefined {
|
||||
let found_index: number | undefined = undefined
|
||||
this._items.some((item, index) => {
|
||||
let foundIndex: number | undefined = undefined
|
||||
this.storedItems.some((item, index) => {
|
||||
if ( func(item, index) ) {
|
||||
found_index = index
|
||||
foundIndex = index
|
||||
return true
|
||||
}
|
||||
})
|
||||
return found_index
|
||||
return foundIndex
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,7 +393,9 @@ class Collection<T> {
|
||||
* @param then
|
||||
*/
|
||||
when<T2>(bool: boolean, then: CollectionFunction<T, T2>): Collection<T> {
|
||||
if ( bool ) then(this)
|
||||
if ( bool ) {
|
||||
then(this)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -383,7 +406,9 @@ class Collection<T> {
|
||||
* @param then
|
||||
*/
|
||||
unless<T2>(bool: boolean, then: CollectionFunction<T, T2>): Collection<T> {
|
||||
if ( !bool ) then(this)
|
||||
if ( !bool ) {
|
||||
then(this)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -405,8 +430,8 @@ class Collection<T> {
|
||||
* @param operator
|
||||
* @param operand
|
||||
*/
|
||||
where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Collection<T> {
|
||||
const items = this._all_associate(key)
|
||||
where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Collection<T> {
|
||||
const items = this.allAssociated(key)
|
||||
return new Collection(applyWhere(items, operator, operand))
|
||||
}
|
||||
|
||||
@@ -416,7 +441,7 @@ class Collection<T> {
|
||||
* @param operator
|
||||
* @param operand
|
||||
*/
|
||||
whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Collection<T> {
|
||||
whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Collection<T> {
|
||||
return this.diff(this.where(key, operator, operand))
|
||||
}
|
||||
|
||||
@@ -428,8 +453,10 @@ class Collection<T> {
|
||||
whereIn<T2>(key: KeyOperator<T, T2>, items: CollectionComparable<T2>): Collection<T> {
|
||||
const allowed = items instanceof Collection ? items.all() : items
|
||||
const matches = []
|
||||
for ( const { key: search, item } of this._all_associate(key) ) {
|
||||
if ( allowed.includes(search) ) matches.push(item)
|
||||
for ( const { key: search, item } of this.allAssociated(key) ) {
|
||||
if ( allowed.includes(search) ) {
|
||||
matches.push(item)
|
||||
}
|
||||
}
|
||||
return new Collection(matches)
|
||||
}
|
||||
@@ -447,7 +474,9 @@ class Collection<T> {
|
||||
* Return the first item in the collection, if it exists.
|
||||
*/
|
||||
first(): MaybeCollectionItem<T> {
|
||||
if ( this.length > 0 ) return this._items[0]
|
||||
if ( this.length > 0 ) {
|
||||
return this.storedItems[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -458,7 +487,9 @@ class Collection<T> {
|
||||
*/
|
||||
firstWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): MaybeCollectionItem<T> {
|
||||
const items = this.where(key, operator, operand).all()
|
||||
if ( items.length > 0 ) return items[0]
|
||||
if ( items.length > 0 ) {
|
||||
return items[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -467,23 +498,25 @@ class Collection<T> {
|
||||
* @param operator
|
||||
* @param operand
|
||||
*/
|
||||
firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
|
||||
firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): MaybeCollectionItem<T> {
|
||||
const items = this.whereNot(key, operator, operand).all()
|
||||
if ( items.length > 0 ) return items[0]
|
||||
if ( items.length > 0 ) {
|
||||
return items[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items in the collection.
|
||||
*/
|
||||
get length() {
|
||||
return this._items.length
|
||||
get length(): number {
|
||||
return this.storedItems.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items in the collection.
|
||||
*/
|
||||
count() {
|
||||
return this._items.length
|
||||
count(): number {
|
||||
return this.storedItems.length
|
||||
}
|
||||
|
||||
// TODO flatten - depth
|
||||
@@ -494,9 +527,12 @@ class Collection<T> {
|
||||
* @param index
|
||||
* @param fallback
|
||||
*/
|
||||
get(index: number, fallback?: any) {
|
||||
if ( this.length > index ) return this._items[index]
|
||||
else return fallback
|
||||
get(index: number, fallback?: T): MaybeCollectionItem<T> {
|
||||
if ( this.length > index ) {
|
||||
return this.storedItems[index]
|
||||
} else {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,13 +560,17 @@ class Collection<T> {
|
||||
* @param key
|
||||
*/
|
||||
groupBy<T2>(key: KeyOperator<T, T2>): any {
|
||||
const items = this._all_associate(key)
|
||||
const items = this.allAssociated(key)
|
||||
const groups: any = {}
|
||||
for ( const item of items ) {
|
||||
const key = String(item.key)
|
||||
if ( !groups[key] ) groups[key] = []
|
||||
groups[key].push(item.item)
|
||||
const itemKey = String(item.key)
|
||||
if ( !groups[itemKey] ) {
|
||||
groups[itemKey] = []
|
||||
}
|
||||
|
||||
groups[itemKey].push(item.item)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
@@ -540,7 +580,7 @@ class Collection<T> {
|
||||
* @param key
|
||||
*/
|
||||
associate<T2>(key: KeyOperator<T, T2>): any {
|
||||
const items = this._all_associate(key)
|
||||
const items = this.allAssociated(key)
|
||||
const values: any = {}
|
||||
for ( const item of items ) {
|
||||
values[String(item.key)] = item.item
|
||||
@@ -553,7 +593,7 @@ class Collection<T> {
|
||||
* @param delimiter
|
||||
*/
|
||||
join(delimiter: string): string {
|
||||
return this._items.join(delimiter)
|
||||
return this.storedItems.join(delimiter)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,15 +613,22 @@ class Collection<T> {
|
||||
intersect(items: CollectionComparable<T>, key?: KeyOperator<T, T>): Collection<T> {
|
||||
const compare = items instanceof Collection ? items.all() : items
|
||||
const intersect = []
|
||||
let all_items
|
||||
if ( key ) all_items = this._all_associate(key)
|
||||
else all_items = this._items.map(item => {
|
||||
return { key: item, item }
|
||||
})
|
||||
let allItems
|
||||
if ( key ) {
|
||||
allItems = this.allAssociated(key)
|
||||
} else {
|
||||
allItems = this.storedItems.map(item => {
|
||||
return {
|
||||
key: item,
|
||||
item,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for ( const item of all_items ) {
|
||||
if ( compare.includes(item.key) )
|
||||
for ( const item of allItems ) {
|
||||
if ( compare.includes(item.key) ) {
|
||||
intersect.push(item.item)
|
||||
}
|
||||
}
|
||||
return new Collection(intersect)
|
||||
}
|
||||
@@ -604,7 +651,9 @@ class Collection<T> {
|
||||
* Return the last item in the collection.
|
||||
*/
|
||||
last(): MaybeCollectionItem<T> {
|
||||
if ( this.length > 0 ) return this._items.reverse()[0]
|
||||
if ( this.length > 0 ) {
|
||||
return this.storedItems.reverse()[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,9 +662,11 @@ class Collection<T> {
|
||||
* @param operator
|
||||
* @param operand
|
||||
*/
|
||||
lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
|
||||
lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): MaybeCollectionItem<T> {
|
||||
const items = this.where(key, operator, operand).all()
|
||||
if ( items.length > 0 ) return items.reverse()[0]
|
||||
if ( items.length > 0 ) {
|
||||
return items.reverse()[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,9 +675,11 @@ class Collection<T> {
|
||||
* @param operator
|
||||
* @param operand
|
||||
*/
|
||||
lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
|
||||
lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): MaybeCollectionItem<T> {
|
||||
const items = this.whereNot(key, operator, operand).all()
|
||||
if ( items.length > 0 ) return items.reverse()[0]
|
||||
if ( items.length > 0 ) {
|
||||
return items.reverse()[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -646,7 +699,7 @@ class Collection<T> {
|
||||
* @param key
|
||||
*/
|
||||
pluck<T2>(key: KeyOperator<T, T2>): Collection<T2> {
|
||||
return new Collection<T2>(this._all(key))
|
||||
return new Collection<T2>(this.allOperator(key))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -654,7 +707,7 @@ class Collection<T> {
|
||||
* @param key
|
||||
*/
|
||||
max<T2>(key: KeyOperator<T, T2>): number {
|
||||
const values = this._all_numbers(key)
|
||||
const values = this.allAsNumbers(key)
|
||||
return Math.max(...values)
|
||||
}
|
||||
|
||||
@@ -671,7 +724,7 @@ class Collection<T> {
|
||||
* @param key
|
||||
*/
|
||||
min<T2>(key: KeyOperator<T, T2>): number {
|
||||
const values = this._all_numbers(key)
|
||||
const values = this.allAsNumbers(key)
|
||||
return Math.min(...values)
|
||||
}
|
||||
|
||||
@@ -689,7 +742,7 @@ class Collection<T> {
|
||||
*/
|
||||
merge<T2>(items: CollectionComparable<T2>): Collection<T|T2> {
|
||||
const merge = items instanceof Collection ? items.all() : items
|
||||
return new Collection([...this._items, ...merge])
|
||||
return new Collection([...this.storedItems, ...merge])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -707,10 +760,15 @@ class Collection<T> {
|
||||
nth(n: number): Collection<T> {
|
||||
const matches: CollectionItem<T>[] = []
|
||||
let current = 1
|
||||
this._items.forEach((item, index) => {
|
||||
if ( current === 1 ) matches.push(item)
|
||||
this.storedItems.forEach(item => {
|
||||
if ( current === 1 ) {
|
||||
matches.push(item)
|
||||
}
|
||||
|
||||
current += 1
|
||||
if ( current > n ) current = 1
|
||||
if ( current > n ) {
|
||||
current = 1
|
||||
}
|
||||
})
|
||||
return new Collection(matches)
|
||||
}
|
||||
@@ -720,10 +778,10 @@ class Collection<T> {
|
||||
* @param page
|
||||
* @param perPage
|
||||
*/
|
||||
forPage(page: number, perPage: number) {
|
||||
forPage(page: number, perPage: number): Collection<T> {
|
||||
const start = page * perPage - perPage
|
||||
const end = page * perPage
|
||||
return new Collection(this._items.slice(start, end))
|
||||
return new Collection(this.storedItems.slice(start, end))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -738,7 +796,7 @@ class Collection<T> {
|
||||
*/
|
||||
pop(): MaybeCollectionItem<T> {
|
||||
if ( this.length > 0 ) {
|
||||
return this._items.pop()
|
||||
return this.storedItems.pop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,7 +805,7 @@ class Collection<T> {
|
||||
* @param item
|
||||
*/
|
||||
prepend(item: CollectionItem<T>): Collection<T> {
|
||||
this._items = [item, ...this._items]
|
||||
this.storedItems = [item, ...this.storedItems]
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -756,7 +814,7 @@ class Collection<T> {
|
||||
* @param item
|
||||
*/
|
||||
push(item: CollectionItem<T>): Collection<T> {
|
||||
this._items.push(item)
|
||||
this.storedItems.push(item)
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -768,7 +826,7 @@ class Collection<T> {
|
||||
concat(items: CollectionComparable<T>): Collection<T> {
|
||||
const concats = items instanceof Collection ? items.all() : items
|
||||
for ( const item of concats ) {
|
||||
this._items.push(item)
|
||||
this.storedItems.push(item)
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -779,19 +837,21 @@ class Collection<T> {
|
||||
* @param item
|
||||
*/
|
||||
put(index: number, item: CollectionItem<T>): Collection<T> {
|
||||
const new_items = []
|
||||
const newItems = []
|
||||
let inserted = false
|
||||
this._items.forEach((existing, existing_index) => {
|
||||
if ( existing_index === index ) {
|
||||
new_items.push(item)
|
||||
this.storedItems.forEach((existing, existingIndex) => {
|
||||
if ( existingIndex === index ) {
|
||||
newItems.push(item)
|
||||
inserted = true
|
||||
}
|
||||
|
||||
new_items.push(existing)
|
||||
newItems.push(existing)
|
||||
})
|
||||
|
||||
if ( !inserted ) new_items.push(item)
|
||||
return new Collection(new_items)
|
||||
if ( !inserted ) {
|
||||
newItems.push(item)
|
||||
}
|
||||
return new Collection(newItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -799,14 +859,18 @@ class Collection<T> {
|
||||
* @param n
|
||||
*/
|
||||
random(n: number): Collection<T> {
|
||||
const random_items: CollectionItem<T>[] = []
|
||||
const all = this._items
|
||||
if ( n > this.length ) n = this.length
|
||||
while ( random_items.length < n ) {
|
||||
const item = all[Math.floor(Math.random() * all.length)]
|
||||
if ( !random_items.includes(item) ) random_items.push(item)
|
||||
const randomItems: CollectionItem<T>[] = []
|
||||
const all = this.storedItems
|
||||
if ( n > this.length ) {
|
||||
n = this.length
|
||||
}
|
||||
return new Collection(random_items)
|
||||
while ( randomItems.length < n ) {
|
||||
const item = all[Math.floor(Math.random() * all.length)]
|
||||
if ( !randomItems.includes(item) ) {
|
||||
randomItems.push(item)
|
||||
}
|
||||
}
|
||||
return new Collection(randomItems)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -820,23 +884,24 @@ class Collection<T> {
|
||||
* ```
|
||||
*
|
||||
* @param reducer
|
||||
* @param initial_value
|
||||
* @param initialValue
|
||||
*/
|
||||
reduce<T2>(reducer: KeyReducerFunction<T, T2>, initial_value?: T2): T2 | undefined {
|
||||
let current_value = initial_value
|
||||
this._items.forEach((item, index) => {
|
||||
current_value = reducer(current_value, item, index)
|
||||
reduce<T2>(reducer: KeyReducerFunction<T, T2>, initialValue?: T2): T2 | undefined {
|
||||
let currentValue = initialValue
|
||||
this.storedItems.forEach((item, index) => {
|
||||
currentValue = reducer(currentValue, item, index)
|
||||
})
|
||||
return current_value
|
||||
|
||||
return currentValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new collection of items that fail the given truth-test function.
|
||||
* @param truth_test
|
||||
* @param truthTestFunction
|
||||
*/
|
||||
reject<T2>(truth_test: KeyFunction<T, T2>): Collection<T> {
|
||||
const rejected = this._items.filter((item, index) => {
|
||||
return !truth_test(item, index)
|
||||
reject<T2>(truthTestFunction: KeyFunction<T, T2>): Collection<T> {
|
||||
const rejected = this.storedItems.filter((item, index) => {
|
||||
return !truthTestFunction(item, index)
|
||||
})
|
||||
return new Collection(rejected)
|
||||
}
|
||||
@@ -845,7 +910,7 @@ class Collection<T> {
|
||||
* Return a new collection whose items are in the reverse order of the current one.
|
||||
*/
|
||||
reverse(): Collection<T> {
|
||||
return new Collection([...this._items.reverse()])
|
||||
return new Collection([...this.storedItems.reverse()])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -853,14 +918,14 @@ class Collection<T> {
|
||||
* @param item
|
||||
*/
|
||||
search(item: CollectionItem<T>): MaybeCollectionIndex {
|
||||
let found_index
|
||||
this._items.some((possible_item, index) => {
|
||||
if ( possible_item === item ) {
|
||||
found_index = index
|
||||
let foundIndex
|
||||
this.storedItems.some((possibleItem, index) => {
|
||||
if ( possibleItem === item ) {
|
||||
foundIndex = index
|
||||
return true
|
||||
}
|
||||
})
|
||||
return found_index
|
||||
return foundIndex
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -868,7 +933,7 @@ class Collection<T> {
|
||||
*/
|
||||
shift(): MaybeCollectionItem<T> {
|
||||
if ( this.length > 0 ) {
|
||||
return this._items.shift()
|
||||
return this.storedItems.shift()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,7 +941,7 @@ class Collection<T> {
|
||||
* Shuffle the items in this collection to a random order.
|
||||
*/
|
||||
shuffle(): Collection<T> {
|
||||
const items = [...this._items]
|
||||
const items = [...this.storedItems]
|
||||
for ( let i = items.length - 1; i > 0; i-- ) {
|
||||
const j = Math.floor(Math.random() * (i + 1))
|
||||
;[items[i], items[j]] = [items[j], items[i]]
|
||||
@@ -889,8 +954,8 @@ class Collection<T> {
|
||||
* @param start
|
||||
* @param end
|
||||
*/
|
||||
slice(start: number, end: number) {
|
||||
return new Collection(this._items.slice(start, end))
|
||||
slice(start: number, end: number): Collection<T> {
|
||||
return new Collection(this.storedItems.slice(start, end))
|
||||
}
|
||||
|
||||
// TODO split
|
||||
@@ -898,12 +963,16 @@ class Collection<T> {
|
||||
|
||||
/**
|
||||
* Sort the collection (optionally) using the given comparison function.
|
||||
* @param compare_func
|
||||
* @param comparisonFunction
|
||||
*/
|
||||
sort(compare_func?: ComparisonFunction<T>): Collection<T> {
|
||||
const items = this._items
|
||||
if ( compare_func ) items.sort(compare_func)
|
||||
else items.sort()
|
||||
sort(comparisonFunction?: ComparisonFunction<T>): Collection<T> {
|
||||
const items = this.storedItems
|
||||
if ( comparisonFunction ) {
|
||||
items.sort(comparisonFunction)
|
||||
} else {
|
||||
items.sort()
|
||||
}
|
||||
|
||||
return new Collection(items)
|
||||
}
|
||||
|
||||
@@ -913,25 +982,33 @@ class Collection<T> {
|
||||
*/
|
||||
sortBy<T2>(key?: KeyOperator<T, T2>): Collection<T> {
|
||||
let items: any[]
|
||||
if ( key ) items = this._all_associate(key)
|
||||
else items = this._items.map(item => {
|
||||
return { key: item, item }
|
||||
})
|
||||
if ( key ) {
|
||||
items = this.allAssociated(key)
|
||||
} else {
|
||||
items = this.storedItems.map(item => {
|
||||
return { key: item,
|
||||
item }
|
||||
})
|
||||
}
|
||||
|
||||
items.sort((a: any, b: any) => {
|
||||
if ( a.key > b.key ) return 1
|
||||
else if ( a.key < b.key ) return -1
|
||||
else return 0
|
||||
if ( a.key > b.key ) {
|
||||
return 1
|
||||
} else if ( a.key < b.key ) {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
return new Collection(items.map((item: AssociatedCollectionItem<T2, T>) => item.item))
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to `sort()`, but in reverse order.
|
||||
* @param compare_func
|
||||
* @param comparisonFunction
|
||||
*/
|
||||
sortDesc(compare_func?: ComparisonFunction<T>): Collection<T> {
|
||||
return this.sort(compare_func).reverse()
|
||||
sortDesc(comparisonFunction?: ComparisonFunction<T>): Collection<T> {
|
||||
return this.sort(comparisonFunction).reverse()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -948,7 +1025,7 @@ class Collection<T> {
|
||||
* @param deleteCount
|
||||
*/
|
||||
splice(start: CollectionIndex, deleteCount?: number): Collection<T> {
|
||||
return new Collection([...this._items].splice(start, deleteCount))
|
||||
return new Collection([...this.storedItems].splice(start, deleteCount))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -965,8 +1042,11 @@ class Collection<T> {
|
||||
*/
|
||||
sum<T2>(key?: KeyOperator<T, T2>): number {
|
||||
let items
|
||||
if ( key ) items = this._all_numbers(key)
|
||||
else items = this._items.map(x => Number(x))
|
||||
if ( key ) {
|
||||
items = this.allAsNumbers(key)
|
||||
} else {
|
||||
items = this.storedItems.map(x => Number(x))
|
||||
}
|
||||
return items.reduce((prev, curr) => prev + curr)
|
||||
}
|
||||
|
||||
@@ -976,11 +1056,13 @@ class Collection<T> {
|
||||
* @param limit
|
||||
*/
|
||||
take(limit: number): Collection<T> {
|
||||
if ( limit === 0 ) return new Collection()
|
||||
else if ( limit > 0 ) {
|
||||
return new Collection(this._items.slice(0, limit))
|
||||
if ( limit === 0 ) {
|
||||
return new Collection()
|
||||
} else if ( limit > 0 ) {
|
||||
return new Collection(this.storedItems.slice(0, limit))
|
||||
} else {
|
||||
return new Collection(this._items.reverse().slice(0, -1 * limit).reverse())
|
||||
return new Collection(this.storedItems.reverse().slice(0, -1 * limit)
|
||||
.reverse())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,10 +1106,15 @@ class Collection<T> {
|
||||
unique<T2>(key?: KeyOperator<T, T2>): Collection<T|T2> {
|
||||
const has: CollectionItem<T|T2>[] = []
|
||||
let items
|
||||
if ( key ) items = this._all<T2>(key)
|
||||
else items = [...this._items]
|
||||
if ( key ) {
|
||||
items = this.allOperator<T2>(key)
|
||||
} else {
|
||||
items = [...this.storedItems]
|
||||
}
|
||||
for ( const item of items ) {
|
||||
if ( !has.includes(item) ) has.push(item)
|
||||
if ( !has.includes(item) ) {
|
||||
has.push(item)
|
||||
}
|
||||
}
|
||||
return new Collection(has)
|
||||
}
|
||||
@@ -1037,7 +1124,7 @@ class Collection<T> {
|
||||
* @param item
|
||||
*/
|
||||
includes(item: CollectionItem<T>): boolean {
|
||||
return this._items.includes(item)
|
||||
return this.storedItems.includes(item)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1046,8 +1133,10 @@ class Collection<T> {
|
||||
* @param value
|
||||
*/
|
||||
pad(length: number, value: CollectionItem<T>): Collection<T> {
|
||||
const items = [...this._items]
|
||||
while ( items.length < length ) items.push(value)
|
||||
const items = [...this.storedItems]
|
||||
while ( items.length < length ) {
|
||||
items.push(value)
|
||||
}
|
||||
return new Collection(items)
|
||||
}
|
||||
|
||||
@@ -1056,9 +1145,12 @@ class Collection<T> {
|
||||
*/
|
||||
toArray(): any[] {
|
||||
const returns: any = []
|
||||
for ( const item of this._items ) {
|
||||
if ( item instanceof Collection ) returns.push(item.toArray())
|
||||
else returns.push(item)
|
||||
for ( const item of this.storedItems ) {
|
||||
if ( item instanceof Collection ) {
|
||||
returns.push(item.toArray())
|
||||
} else {
|
||||
returns.push(item)
|
||||
}
|
||||
}
|
||||
return returns
|
||||
}
|
||||
@@ -1075,18 +1167,18 @@ class Collection<T> {
|
||||
// TODO getIterator
|
||||
// TODO getCachingIterator
|
||||
|
||||
[Symbol.iterator]() {
|
||||
const items = this._items
|
||||
[Symbol.iterator](): Iterator<T> {
|
||||
const items = this.storedItems
|
||||
let currentIndex = 0
|
||||
return {
|
||||
current_index: 0,
|
||||
next() {
|
||||
if ( items.length < 1 || this.current_index + 1 > items.length ) {
|
||||
return { done: true }
|
||||
}
|
||||
const item = items[currentIndex]
|
||||
currentIndex += 1
|
||||
|
||||
const item = items[this.current_index]
|
||||
this.current_index += 1
|
||||
return { done: false, value: item }
|
||||
return {
|
||||
done: currentIndex > items.length,
|
||||
value: item,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1109,7 +1201,7 @@ class Collection<T> {
|
||||
*/
|
||||
async promiseMap<T2>(func: KeyFunction<T, T2 | Promise<T2>>): Promise<Collection<T2>> {
|
||||
return new Collection<T2>(await Promise.all(
|
||||
this.map(func).toArray()
|
||||
this.map(func).toArray(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ export abstract class Iterable<T> {
|
||||
}
|
||||
|
||||
this.index = i + 1
|
||||
return { done: false, value: await this.at(i) }
|
||||
return { done: false,
|
||||
value: await this.at(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +73,7 @@ export abstract class Iterable<T> {
|
||||
* @param {ChunkCallback} callback
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public async chunk(size: number, callback: ChunkCallback<T>) {
|
||||
public async chunk(size: number, callback: ChunkCallback<T>): Promise<void> {
|
||||
const total = await this.count()
|
||||
|
||||
while ( this.index < total ) {
|
||||
@@ -81,8 +82,11 @@ export abstract class Iterable<T> {
|
||||
try {
|
||||
await callback(items)
|
||||
} catch ( error ) {
|
||||
if ( error instanceof StopIteration ) break
|
||||
else throw error
|
||||
if ( error instanceof StopIteration ) {
|
||||
break
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
this.index += size
|
||||
@@ -94,9 +98,12 @@ export abstract class Iterable<T> {
|
||||
* @param {number} index
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public async seek(index: number) {
|
||||
if ( index < 0 ) throw new TypeError('Cannot seek to negative index.')
|
||||
else if ( index >= await this.count() ) throw new TypeError('Cannot seek past last item.')
|
||||
public async seek(index: number): Promise<void> {
|
||||
if ( index < 0 ) {
|
||||
throw new TypeError('Cannot seek to negative index.')
|
||||
} else if ( index >= await this.count() ) {
|
||||
throw new TypeError('Cannot seek past last item.')
|
||||
}
|
||||
this.index = index
|
||||
}
|
||||
|
||||
@@ -105,15 +112,18 @@ export abstract class Iterable<T> {
|
||||
* @return Promise<any|undefined>
|
||||
*/
|
||||
public async peek(): Promise<T | undefined> {
|
||||
if ( this.index + 1 >= await this.count() ) return undefined
|
||||
else return this.at(this.index + 1)
|
||||
if ( this.index + 1 >= await this.count() ) {
|
||||
return undefined
|
||||
} else {
|
||||
return this.at(this.index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the iterable to the first index.
|
||||
* @return Promise<any>
|
||||
*/
|
||||
public async reset() {
|
||||
public async reset(): Promise<void> {
|
||||
this.index = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,48 +20,73 @@ type WhereResult = any[]
|
||||
* @param [operand]
|
||||
* @return boolean
|
||||
*/
|
||||
const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand?: any): boolean => {
|
||||
const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand?: unknown): boolean => {
|
||||
switch ( operator ) {
|
||||
case '&':
|
||||
if ( item.key & operand ) return true
|
||||
break
|
||||
case '>':
|
||||
if ( item.key > operand ) return true
|
||||
break
|
||||
case '>=':
|
||||
if ( item.key >= operand ) return true
|
||||
break
|
||||
case '<':
|
||||
if ( item.key < operand ) return true
|
||||
break
|
||||
case '<=':
|
||||
if ( item.key <= operand ) return true
|
||||
break
|
||||
case '!=':
|
||||
if ( item.key !== operand ) return true
|
||||
break
|
||||
case '<=>':
|
||||
if ( item.key === operand && typeof item.key !== 'undefined' && item.key !== null )
|
||||
return true
|
||||
break
|
||||
case '%':
|
||||
if ( item.key % operand ) return true
|
||||
break
|
||||
case '|':
|
||||
if ( item.key | operand ) return true
|
||||
break
|
||||
case '!':
|
||||
if ( !item.key ) return true
|
||||
break
|
||||
case '~':
|
||||
if ( ~item.key ) return true
|
||||
break
|
||||
case '=':
|
||||
if ( item.key === operand ) return true
|
||||
break
|
||||
case '^':
|
||||
if ( item.key ^ operand ) return true
|
||||
break
|
||||
case '&':
|
||||
if ( item.key & Number(operand) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '>':
|
||||
if ( item.key > (operand as any) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '>=':
|
||||
if ( item.key >= (operand as any) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '<':
|
||||
if ( item.key < (operand as any) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '<=':
|
||||
if ( item.key <= (operand as any) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '!=':
|
||||
if ( item.key !== (operand as any) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '<=>':
|
||||
if ( item.key === operand && typeof item.key !== 'undefined' && item.key !== null ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '%':
|
||||
if ( item.key % Number(operand) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '|':
|
||||
if ( item.key | Number(operand) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '!':
|
||||
if ( !item.key ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '~':
|
||||
if ( ~item.key ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '=':
|
||||
if ( item.key === operand ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case '^':
|
||||
if ( item.key ^ Number(operand) ) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -73,11 +98,12 @@ const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand
|
||||
* @param {WhereOperator} operator
|
||||
* @param [operand]
|
||||
*/
|
||||
const applyWhere = (items: AssociatedSearchItem[], operator: WhereOperator, operand?: any): WhereResult => {
|
||||
const applyWhere = (items: AssociatedSearchItem[], operator: WhereOperator, operand?: unknown): WhereResult => {
|
||||
const matches: WhereResult = []
|
||||
for ( const item of items ) {
|
||||
if ( whereMatch(item, operator, operand) )
|
||||
if ( whereMatch(item, operator, operand) ) {
|
||||
matches.push(item.item)
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
*/
|
||||
export class ErrorWithContext extends Error {
|
||||
public context: {[key: string]: any} = {}
|
||||
|
||||
public originalError?: Error
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
context?: {[key: string]: any}
|
||||
context?: {[key: string]: any},
|
||||
) {
|
||||
super(message)
|
||||
if ( context ) this.context = context
|
||||
if ( context ) {
|
||||
this.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,21 +30,21 @@ export abstract class Logger {
|
||||
* @return string
|
||||
*/
|
||||
protected levelDisplay(level: LoggingLevel): string {
|
||||
switch(level) {
|
||||
case LoggingLevel.Success:
|
||||
return color.green('success')
|
||||
case LoggingLevel.Error:
|
||||
return color.red(' error')
|
||||
case LoggingLevel.Warning:
|
||||
return color.yellow('warning')
|
||||
case LoggingLevel.Info:
|
||||
return color.blue(' info')
|
||||
case LoggingLevel.Debug:
|
||||
return color.cyan(' debug')
|
||||
case LoggingLevel.Verbose:
|
||||
return color.gray('verbose')
|
||||
case LoggingLevel.Silent:
|
||||
return color.gray(' silent')
|
||||
switch (level) {
|
||||
case LoggingLevel.Success:
|
||||
return color.green('success')
|
||||
case LoggingLevel.Error:
|
||||
return color.red(' error')
|
||||
case LoggingLevel.Warning:
|
||||
return color.yellow('warning')
|
||||
case LoggingLevel.Info:
|
||||
return color.blue(' info')
|
||||
case LoggingLevel.Debug:
|
||||
return color.cyan(' debug')
|
||||
case LoggingLevel.Verbose:
|
||||
return color.gray('verbose')
|
||||
case LoggingLevel.Silent:
|
||||
return color.gray(' silent')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {Logger} from "./Logger";
|
||||
import {LogMessage} from "./types";
|
||||
import {Logger} from './Logger'
|
||||
import {LogMessage} from './types'
|
||||
import * as color from 'colors/safe'
|
||||
|
||||
/**
|
||||
* A Logger implementation that writes to the console.
|
||||
*/
|
||||
export class StandardLogger extends Logger {
|
||||
public write(message: LogMessage) {
|
||||
public write(message: LogMessage): void {
|
||||
const prefix = this.levelDisplay(message.level)
|
||||
const text = `${prefix} ${color.gray(this.formatDate(message.date))} (${color.cyan(message.callerName || 'Unknown')})`
|
||||
console.log(text, message.output)
|
||||
console.log(text, message.output) // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ enum LoggingLevel {
|
||||
* @param something
|
||||
* @return boolean
|
||||
*/
|
||||
const isLoggingLevel = (something: any): something is LoggingLevel => {
|
||||
const isLoggingLevel = (something: unknown): something is LoggingLevel => {
|
||||
return [
|
||||
LoggingLevel.Silent,
|
||||
LoggingLevel.Success,
|
||||
@@ -24,8 +24,8 @@ const isLoggingLevel = (something: any): something is LoggingLevel => {
|
||||
LoggingLevel.Warning,
|
||||
LoggingLevel.Info,
|
||||
LoggingLevel.Debug,
|
||||
LoggingLevel.Verbose
|
||||
].includes(something)
|
||||
LoggingLevel.Verbose,
|
||||
].includes(something as any)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,8 +43,8 @@ interface LogMessage {
|
||||
* @param something
|
||||
* @return boolean
|
||||
*/
|
||||
const isLogMessage = (something: any): something is LogMessage => {
|
||||
return isLoggingLevel(something?.level) && something?.date instanceof Date;
|
||||
const isLogMessage = (something: unknown): something is LogMessage => {
|
||||
return isLoggingLevel((something as any)?.level) && (something as any)?.date instanceof Date
|
||||
}
|
||||
|
||||
export { LoggingLevel, LogMessage, isLoggingLevel, isLogMessage }
|
||||
|
||||
@@ -62,18 +62,18 @@ export class BehaviorSubject<T> {
|
||||
* True if this subject has been marked complete.
|
||||
* @type boolean
|
||||
*/
|
||||
protected _isComplete: boolean = false
|
||||
protected subjectIsComplete = false
|
||||
|
||||
/**
|
||||
* The current value of this subject.
|
||||
*/
|
||||
protected _value?: T
|
||||
protected currentValue?: T
|
||||
|
||||
/**
|
||||
* True if any value has been pushed to this subject.
|
||||
* @type boolean
|
||||
*/
|
||||
protected _hasPush: boolean = false
|
||||
protected hasPush = false
|
||||
|
||||
/**
|
||||
* Register a new subscription to this subject.
|
||||
@@ -90,7 +90,7 @@ export class BehaviorSubject<T> {
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
this.subscribers = this.subscribers.filter(x => x !== subscriber)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +110,11 @@ export class BehaviorSubject<T> {
|
||||
unsubscribe()
|
||||
},
|
||||
complete: (val?: T) => {
|
||||
if ( typeof val !== 'undefined' ) resolve(val)
|
||||
if ( typeof val !== 'undefined' ) {
|
||||
resolve(val)
|
||||
}
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -123,9 +125,11 @@ export class BehaviorSubject<T> {
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public async next(val: T): Promise<void> {
|
||||
if ( this._isComplete ) throw new CompletedObservableError()
|
||||
this._value = val
|
||||
this._hasPush = true
|
||||
if ( this.subjectIsComplete ) {
|
||||
throw new CompletedObservableError()
|
||||
}
|
||||
this.currentValue = val
|
||||
this.hasPush = true
|
||||
for ( const subscriber of this.subscribers ) {
|
||||
if ( subscriber.next ) {
|
||||
try {
|
||||
@@ -150,25 +154,32 @@ export class BehaviorSubject<T> {
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public async push(vals: T[]): Promise<void> {
|
||||
if ( this._isComplete ) throw new CompletedObservableError()
|
||||
if ( this.subjectIsComplete ) {
|
||||
throw new CompletedObservableError()
|
||||
}
|
||||
await Promise.all(vals.map(val => this.next(val)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this subject as complete.
|
||||
* The promise resolves when all subscribers have been pushed to.
|
||||
* @param [final_val] - optionally, a final value to set
|
||||
* @param [finalValue] - optionally, a final value to set
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public async complete(final_val?: T): Promise<void> {
|
||||
if ( this._isComplete ) throw new CompletedObservableError()
|
||||
if ( typeof final_val === 'undefined' ) final_val = this.value()
|
||||
else this._value = final_val
|
||||
public async complete(finalValue?: T): Promise<void> {
|
||||
if ( this.subjectIsComplete ) {
|
||||
throw new CompletedObservableError()
|
||||
}
|
||||
if ( typeof finalValue === 'undefined' ) {
|
||||
finalValue = this.value()
|
||||
} else {
|
||||
this.currentValue = finalValue
|
||||
}
|
||||
|
||||
for ( const subscriber of this.subscribers ) {
|
||||
if ( subscriber.complete ) {
|
||||
try {
|
||||
await subscriber.complete(final_val)
|
||||
await subscriber.complete(finalValue)
|
||||
} catch (e) {
|
||||
if ( subscriber.error ) {
|
||||
await subscriber.error(e)
|
||||
@@ -179,14 +190,14 @@ export class BehaviorSubject<T> {
|
||||
}
|
||||
}
|
||||
|
||||
this._isComplete = true
|
||||
this.subjectIsComplete = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current value of this subject.
|
||||
*/
|
||||
public value(): T | undefined {
|
||||
return this._value
|
||||
return this.currentValue
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,6 +205,6 @@ export class BehaviorSubject<T> {
|
||||
* @return boolean
|
||||
*/
|
||||
public isComplete(): boolean {
|
||||
return this._isComplete
|
||||
return this.subjectIsComplete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {collect} from "../collection/Collection";
|
||||
import {InvalidJSONStateError, JSONState, Rehydratable} from "./Rehydratable";
|
||||
import {Pipe} from "./Pipe";
|
||||
import {collect} from '../collection/Collection'
|
||||
import {InvalidJSONStateError, JSONState, Rehydratable} from './Rehydratable'
|
||||
import {Pipe} from './Pipe'
|
||||
|
||||
/**
|
||||
* A class for building and working with messages grouped by keys.
|
||||
@@ -34,14 +34,14 @@ export class Messages implements Rehydratable {
|
||||
|
||||
/** Returns true if the given group has the message. */
|
||||
public has(key: string, message: string): boolean {
|
||||
return !!this.messages[key]?.includes(message)
|
||||
return Boolean(this.messages[key]?.includes(message))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any messages are found.
|
||||
* @param [forKeys] if provided, only search the given keys
|
||||
*/
|
||||
public any(forKeys?: string[]) {
|
||||
public any(forKeys?: string[]): boolean {
|
||||
if ( forKeys ) {
|
||||
return forKeys.map(key => this.messages[key])
|
||||
.filter(Boolean)
|
||||
@@ -55,8 +55,11 @@ export class Messages implements Rehydratable {
|
||||
* Returns the first message.
|
||||
* @param [forKey] if provided, only search the given key
|
||||
*/
|
||||
public first(forKey?: string) {
|
||||
if ( !forKey ) forKey = Object.keys(this.messages)[0]
|
||||
public first(forKey?: string): string | undefined {
|
||||
if ( !forKey ) {
|
||||
forKey = Object.keys(this.messages)[0]
|
||||
}
|
||||
|
||||
if ( forKey && this.messages[forKey].length ) {
|
||||
return this.messages[forKey][0]
|
||||
}
|
||||
@@ -66,20 +69,22 @@ export class Messages implements Rehydratable {
|
||||
* Return all messages in a flat array.
|
||||
* @param [forKey] if provided, only search the given key
|
||||
*/
|
||||
public all(forKey?: string) {
|
||||
public all(forKey?: string): string[] {
|
||||
if ( forKey ) {
|
||||
return this.messages[forKey] || []
|
||||
}
|
||||
|
||||
return collect<string[]>(Object.values(this.messages)).collapse().all() as string[]
|
||||
return collect<string[]>(Object.values(this.messages)).collapse()
|
||||
.all() as string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Flat array of only distinct messages.
|
||||
* @param [forKey] if provided, only search the given key
|
||||
*/
|
||||
public unique(forKey?: string) {
|
||||
return collect<string>(this.all(forKey)).unique<string>().all()
|
||||
public unique(forKey?: string): string[] {
|
||||
return collect<string>(this.all(forKey)).unique<string>()
|
||||
.all()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,29 +103,31 @@ export class Messages implements Rehydratable {
|
||||
if ( typeof state === 'object' && !Array.isArray(state) ) {
|
||||
let all = true
|
||||
for ( const key in state ) {
|
||||
if ( !state.hasOwnProperty(key) ) continue;
|
||||
if ( !Object.prototype.hasOwnProperty.call(state, key) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
const set = state[key]
|
||||
if ( !(Array.isArray(set) && set.every(x => typeof x === 'string')) ) {
|
||||
all = false;
|
||||
break;
|
||||
all = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ( all ) {
|
||||
// @ts-ignore
|
||||
this.messages = state;
|
||||
this.messages = state as any
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidJSONStateError('Invalid message state object.', { state });
|
||||
throw new InvalidJSONStateError('Invalid message state object.', { state })
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
toJSON(): {[key: string]: string[]} {
|
||||
return this.messages
|
||||
}
|
||||
|
||||
toString() {
|
||||
return JSON.stringify(this)
|
||||
toString(): string {
|
||||
return JSON.stringify(this.toJSON())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,15 +45,15 @@ export class Pipe<T> {
|
||||
* Return a new Pipe containing the given subject.
|
||||
* @param subject
|
||||
*/
|
||||
static wrap<subject_t>(subject: subject_t) {
|
||||
return new Pipe<subject_t>(subject)
|
||||
static wrap<subjectType>(subject: subjectType): Pipe<subjectType> {
|
||||
return new Pipe<subjectType>(subject)
|
||||
}
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The item being operated on.
|
||||
*/
|
||||
private subject: T
|
||||
private subject: T,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -68,7 +68,7 @@ export class Pipe<T> {
|
||||
*
|
||||
* @param op
|
||||
*/
|
||||
tap<T2>(op: PipeOperator<T, T2>) {
|
||||
tap<T2>(op: PipeOperator<T, T2>): Pipe<T2> {
|
||||
return new Pipe(op(this.subject))
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export class Pipe<T> {
|
||||
* @param check
|
||||
* @param op
|
||||
*/
|
||||
when(check: boolean, op: ReflexivePipeOperator<T>) {
|
||||
when(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
if ( check ) {
|
||||
return Pipe.wrap(op(this.subject))
|
||||
}
|
||||
@@ -94,7 +94,7 @@ export class Pipe<T> {
|
||||
* @param check
|
||||
* @param op
|
||||
*/
|
||||
unless(check: boolean, op: ReflexivePipeOperator<T>) {
|
||||
unless(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
return this.when(!check, op)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class Pipe<T> {
|
||||
* @param check
|
||||
* @param op
|
||||
*/
|
||||
whenNot(check: boolean, op: ReflexivePipeOperator<T>) {
|
||||
whenNot(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||
return this.unless(check, op)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Type representing a JSON serializable object.
|
||||
*/
|
||||
import {ErrorWithContext} from "../error/ErrorWithContext";
|
||||
import {ErrorWithContext} from '../error/ErrorWithContext'
|
||||
|
||||
export type JSONState = { [key: string]: string | boolean | number | undefined | JSONState | Array<string | boolean | number | undefined | JSONState> }
|
||||
|
||||
@@ -15,7 +15,7 @@ export class InvalidJSONStateError extends ErrorWithContext {}
|
||||
* @param what
|
||||
* @return boolean
|
||||
*/
|
||||
export function isJSONState(what: any): what is JSONState {
|
||||
export function isJSONState(what: unknown): what is JSONState {
|
||||
try {
|
||||
JSON.stringify(what)
|
||||
return true
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
import * as node_uuid from 'uuid'
|
||||
import {ErrorWithContext} from "../error/ErrorWithContext";
|
||||
import * as nodeUUID from 'uuid'
|
||||
import {ErrorWithContext} from '../error/ErrorWithContext'
|
||||
import {JSONState} from './Rehydratable'
|
||||
|
||||
/**
|
||||
* Make a deep copy of an object.
|
||||
* @param target
|
||||
*/
|
||||
export function deepCopy<T>(target: T): T {
|
||||
if ( target === null )
|
||||
if ( target === null ) {
|
||||
return target
|
||||
}
|
||||
|
||||
if ( target instanceof Date )
|
||||
if ( target instanceof Date ) {
|
||||
return new Date(target.getTime()) as any
|
||||
}
|
||||
|
||||
if ( target instanceof Array ) {
|
||||
const copy = [] as any[]
|
||||
(target as any[]).forEach(item => { copy.push(item) })
|
||||
(target as any[]).forEach(item => {
|
||||
copy.push(item)
|
||||
})
|
||||
return copy.map((item: any) => deepCopy<any>(item)) as any
|
||||
}
|
||||
|
||||
@@ -32,28 +37,44 @@ export function deepCopy<T>(target: T): T {
|
||||
* Given a string of a value, try to infer the JavaScript type.
|
||||
* @param {string} val
|
||||
*/
|
||||
export function infer(val: string) {
|
||||
if ( !val ) return undefined
|
||||
else if ( val.toLowerCase() === 'true' ) return true
|
||||
else if ( val.toLowerCase() === 'false' ) return false
|
||||
else if ( !isNaN(Number(val)) ) return Number(val)
|
||||
else if ( isJSON(val) ) return JSON.parse(val)
|
||||
else if ( val.toLowerCase() === 'null' ) return null
|
||||
else if ( val.toLowerCase() === 'undefined' ) return undefined
|
||||
else return val
|
||||
export function infer(val: string): undefined | boolean | number | JSONState | string | null {
|
||||
if ( !val ) {
|
||||
return undefined
|
||||
} else if ( val.toLowerCase() === 'true' ) {
|
||||
return true
|
||||
} else if ( val.toLowerCase() === 'false' ) {
|
||||
return false
|
||||
} else if ( !isNaN(Number(val)) ) {
|
||||
return Number(val)
|
||||
} else if ( isJSON(val) ) {
|
||||
return JSON.parse(val)
|
||||
} else if ( val.toLowerCase() === 'null' ) {
|
||||
return null
|
||||
} else if ( val.toLowerCase() === 'undefined' ) {
|
||||
return undefined
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an inferred value, try to convert back to the string form.
|
||||
* @param val
|
||||
*/
|
||||
export function uninfer(val: any) {
|
||||
if ( typeof val === 'undefined' ) return 'undefined'
|
||||
else if ( val === true ) return 'true'
|
||||
else if ( val === false ) return 'false'
|
||||
else if ( !isNaN(Number(val)) ) return `${Number(val)}`
|
||||
else if ( typeof val === 'string' ) return val
|
||||
else return JSON.stringify(val)
|
||||
export function uninfer(val: unknown): string {
|
||||
if ( typeof val === 'undefined' ) {
|
||||
return 'undefined'
|
||||
} else if ( val === true ) {
|
||||
return 'true'
|
||||
} else if ( val === false ) {
|
||||
return 'false'
|
||||
} else if ( !isNaN(Number(val)) ) {
|
||||
return `${Number(val)}`
|
||||
} else if ( typeof val === 'string' ) {
|
||||
return val
|
||||
} else {
|
||||
return JSON.stringify(val)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +94,8 @@ export function isJSON(val: string): boolean {
|
||||
* Get a universally-unique ID string.
|
||||
* @return string
|
||||
*/
|
||||
export function uuid_v4() {
|
||||
return node_uuid.v4()
|
||||
export function uuid4(): string {
|
||||
return nodeUUID.v4()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +108,7 @@ export function uuid_v4() {
|
||||
*/
|
||||
export function* dataWalkUnsafe<T>(
|
||||
data: {[key: string]: any} | any[] | undefined,
|
||||
path: string, defaultValue?: T, currentPath: string = ''
|
||||
path: string, defaultValue?: T, currentPath = '',
|
||||
): IterableIterator<[T | T[] | undefined, string]> {
|
||||
if ( !data ) {
|
||||
yield [defaultValue, currentPath]
|
||||
@@ -102,28 +123,35 @@ export function* dataWalkUnsafe<T>(
|
||||
if ( Array.isArray(data) ) {
|
||||
if ( part === '*' ) {
|
||||
if ( subpath.length ) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
for ( const val of dataWalkUnsafe<T>(data, subpath.join('.'), defaultValue, currentPath) ) {
|
||||
yield val
|
||||
}
|
||||
} else {
|
||||
for ( const key in data ) {
|
||||
if ( !Object.prototype.hasOwnProperty.call(data, key) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
yield [data[key], `${currentPath ? currentPath + '.' : ''}${key}`]
|
||||
}
|
||||
}
|
||||
} else if ( !isNaN(parseInt(part)) ) {
|
||||
const subdata = data[parseInt(part)]
|
||||
} else if ( !isNaN(parseInt(part, 10)) ) {
|
||||
const subdata = data[parseInt(part, 10)]
|
||||
if ( subpath.length ) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
for ( const val of dataWalkUnsafe<T>(subdata, subpath, defaultValue, `${currentPath ? currentPath + '.' : ''}${parseInt(part)}`) ) {
|
||||
for ( const val of dataWalkUnsafe<T>(subdata, subpath, defaultValue, `${currentPath ? currentPath + '.' : ''}${parseInt(part, 10)}`) ) {
|
||||
yield val
|
||||
}
|
||||
} else {
|
||||
yield [subdata, `${currentPath ? currentPath + '.' : ''}${parseInt(part)}`]
|
||||
yield [subdata, `${currentPath ? currentPath + '.' : ''}${parseInt(part, 10)}`]
|
||||
}
|
||||
} else {
|
||||
const subdata = data.map(x => x[part])
|
||||
if ( subpath.length ) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
for ( const val of dataWalkUnsafe<T>(subdata, subpath, defaultValue, `${currentPath ? currentPath + '.' : ''}${part}`) ) {
|
||||
yield val
|
||||
@@ -136,6 +164,7 @@ export function* dataWalkUnsafe<T>(
|
||||
if ( part === '*' ) {
|
||||
const subdata = Object.values(data)
|
||||
if ( subpath.length ) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
for ( const val of dataWalkUnsafe<T>(subdata, subpath, defaultValue, `${currentPath ? currentPath + '.' : ''}*`) ) {
|
||||
yield val
|
||||
@@ -146,6 +175,7 @@ export function* dataWalkUnsafe<T>(
|
||||
} else {
|
||||
const subdata = data[part]
|
||||
if ( subpath.length ) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
for ( const val of dataWalkUnsafe<T>(subdata, subpath, defaultValue, `${currentPath ? currentPath + '.' : ''}${part}`) ) {
|
||||
yield val
|
||||
@@ -164,28 +194,29 @@ export function* dataWalkUnsafe<T>(
|
||||
* @param value
|
||||
* @param data
|
||||
*/
|
||||
export function dataSetUnsafe(path: string, value: any, data: {[key: string]: any} | any[] | undefined) {
|
||||
data = data || ((!isNaN(parseInt(path.split('.')[0]))) ? [] : {})
|
||||
export function dataSetUnsafe(path: string, value: unknown, data: {[key: string]: any} | any[] | undefined): any {
|
||||
data = data || ((!isNaN(parseInt(path.split('.')[0], 10))) ? [] : {})
|
||||
let current = data
|
||||
const parts = path.split('.')
|
||||
|
||||
parts.forEach((part, idx) => {
|
||||
if ( idx === path.split('.').length - 1 ) {
|
||||
current[Array.isArray(current) ? parseInt(part) : part] = value
|
||||
current[Array.isArray(current) ? parseInt(part, 10) : part] = value
|
||||
return
|
||||
}
|
||||
|
||||
let next = Array.isArray(current) ? parseInt(parts[idx + 1]) : current?.[parts[idx + 1]]
|
||||
let next = Array.isArray(current) ? parseInt(parts[idx + 1], 10) : current?.[parts[idx + 1]]
|
||||
if ( !next ) {
|
||||
next = (!isNaN(parseInt(parts[idx + 1])) ? [] : {})
|
||||
next = (!isNaN(parseInt(parts[idx + 1], 10)) ? [] : {})
|
||||
}
|
||||
|
||||
if ( Array.isArray(current) ) {
|
||||
if ( isNaN(parseInt(part)) ) {
|
||||
throw new ErrorWithContext(`Invalid property name "${part}" of array-type.`, {part, path})
|
||||
if ( isNaN(parseInt(part, 10)) ) {
|
||||
throw new ErrorWithContext(`Invalid property name "${part}" of array-type.`, {part,
|
||||
path})
|
||||
}
|
||||
|
||||
current[parseInt(part)] = next
|
||||
current[parseInt(part, 10)] = next
|
||||
current = next
|
||||
} else {
|
||||
current[part] = next
|
||||
@@ -204,69 +235,47 @@ export function dataSetUnsafe(path: string, value: any, data: {[key: string]: an
|
||||
* @param defaultValue
|
||||
*/
|
||||
export function dataGetUnsafe<T>(data: {[key: string]: any} | any[] | undefined, path: string, defaultValue?: T): T | T[] | undefined {
|
||||
if ( !data ) return
|
||||
if ( !path ) return data as T
|
||||
if ( !data ) {
|
||||
return
|
||||
}
|
||||
if ( !path ) {
|
||||
return data as T
|
||||
}
|
||||
const [part, ...subpath] = path.split('.')
|
||||
|
||||
if ( Array.isArray(data) ) {
|
||||
if ( part === '*' ) {
|
||||
return dataGetUnsafe<T>(data, subpath.join('.'), defaultValue)
|
||||
} else if ( !isNaN(parseInt(part)) ) {
|
||||
const subdata = data[parseInt(part)]
|
||||
if ( subpath.length ) return dataGetUnsafe<T>(subdata, subpath.join('.'), defaultValue)
|
||||
else return subdata
|
||||
} else if ( !isNaN(parseInt(part, 10)) ) {
|
||||
const subdata = data[parseInt(part, 10)]
|
||||
if ( subpath.length ) {
|
||||
return dataGetUnsafe<T>(subdata, subpath.join('.'), defaultValue)
|
||||
} else {
|
||||
return subdata
|
||||
}
|
||||
} else {
|
||||
const subdata = data.map(x => x[part])
|
||||
if ( subpath.length ) return dataGetUnsafe<T>(subdata, subpath.join('.'), defaultValue)
|
||||
else return subdata
|
||||
if ( subpath.length ) {
|
||||
return dataGetUnsafe<T>(subdata, subpath.join('.'), defaultValue)
|
||||
} else {
|
||||
return subdata
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( part === '*' ) {
|
||||
const subdata = Object.values(data)
|
||||
if ( subpath.length ) return dataGetUnsafe<T>(subdata, subpath.join('.'), defaultValue)
|
||||
else return subdata
|
||||
if ( subpath.length ) {
|
||||
return dataGetUnsafe<T>(subdata, subpath.join('.'), defaultValue)
|
||||
} else {
|
||||
return subdata
|
||||
}
|
||||
} else {
|
||||
const subdata = data[part]
|
||||
if ( subpath.length ) return dataGetUnsafe<T>(subdata, subpath.join('.'), defaultValue)
|
||||
else return subdata
|
||||
if ( subpath.length ) {
|
||||
return dataGetUnsafe<T>(subdata, subpath.join('.'), defaultValue)
|
||||
} else {
|
||||
return subdata
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dataGet<
|
||||
T,
|
||||
P1 extends keyof NonNullable<T>
|
||||
>(obj: T, prop1: P1): NonNullable<T>[P1] | undefined;
|
||||
|
||||
function dataGet<
|
||||
T,
|
||||
P1 extends keyof NonNullable<T>,
|
||||
P2 extends keyof NonNullable<NonNullable<T>[P1]>
|
||||
>(obj: T, prop1: P1, prop2: P2): NonNullable<NonNullable<T>[P1]>[P2] | undefined;
|
||||
|
||||
function dataGet<
|
||||
T,
|
||||
P1 extends keyof NonNullable<T>,
|
||||
P2 extends keyof NonNullable<NonNullable<T>[P1]>,
|
||||
P3 extends keyof NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>
|
||||
>(obj: T, prop1: P1, prop2: P2, prop3: P3): NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3] | undefined;
|
||||
|
||||
function dataGet<
|
||||
T,
|
||||
P1 extends keyof NonNullable<T>,
|
||||
P2 extends keyof NonNullable<NonNullable<T>[P1]>,
|
||||
P3 extends keyof NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>,
|
||||
P4 extends keyof NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>
|
||||
>(obj: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4): NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>[P4] | undefined;
|
||||
|
||||
/**
|
||||
* A type-safe way to get the value of a key nested up to 4 levels deep in an object.
|
||||
* @param obj
|
||||
* @param props
|
||||
*/
|
||||
function dataGet(obj: any, ...props: string[]): any {
|
||||
return obj && props.reduce(
|
||||
(result, prop) => result == null ? undefined : result[prop],
|
||||
obj
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
|
||||
export function isDebugging(key: string): boolean {
|
||||
const env = 'EXTOLLO_DEBUG_' + key.split(/(?:\s|\.)+/).join('_').toUpperCase()
|
||||
const env = 'EXTOLLO_DEBUG_' + key.split(/(?:\s|\.)+/).join('_')
|
||||
.toUpperCase()
|
||||
return process.env[env] === 'yes'
|
||||
}
|
||||
|
||||
export function ifDebugging(key: string, run: () => any) {
|
||||
if ( isDebugging(key) ) run()
|
||||
export function ifDebugging(key: string, run: () => any): void {
|
||||
if ( isDebugging(key) ) {
|
||||
run()
|
||||
}
|
||||
}
|
||||
|
||||
export function logIfDebugging(key: string, ...output: any[]) {
|
||||
ifDebugging(key, () => console.log(`[debug: ${key}]`, ...output))
|
||||
export function logIfDebugging(key: string, ...output: any[]): void {
|
||||
ifDebugging(key, () => console.log(`[debug: ${key}]`, ...output)) // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Collection} from "../collection/Collection";
|
||||
import {uuid_v4} from "./data";
|
||||
import {Collection} from '../collection/Collection'
|
||||
import {uuid4} from './data'
|
||||
|
||||
/**
|
||||
* Type structure for a single item in the global registry.
|
||||
@@ -12,7 +12,7 @@ export type GlobalRegistrant = { key: string | symbol, value: any }
|
||||
export class GlobalRegistry extends Collection<GlobalRegistrant> {
|
||||
constructor() {
|
||||
super()
|
||||
this.setGlobal('registry_uuid', uuid_v4())
|
||||
this.setGlobal('registry_uuid', uuid4())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,12 +20,15 @@ export class GlobalRegistry extends Collection<GlobalRegistrant> {
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public setGlobal(key: string | symbol, value: any): this {
|
||||
public setGlobal(key: string | symbol, value: unknown): this {
|
||||
const existing = this.firstWhere('key', '=', key)
|
||||
if ( existing ) {
|
||||
existing.value = value
|
||||
} else {
|
||||
this.push({ key, value })
|
||||
this.push({
|
||||
key,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
return this
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
* @param derivedCtor
|
||||
* @param {array} baseCtors
|
||||
*/
|
||||
export function applyMixins(derivedCtor: any, baseCtors: any[]) {
|
||||
export function applyMixins(derivedCtor: FunctionConstructor, baseCtors: any[]): void {
|
||||
baseCtors.forEach(baseCtor => {
|
||||
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
|
||||
const desc = Object.getOwnPropertyDescriptor(baseCtor.prototype, name)
|
||||
if ( typeof desc !== 'undefined' )
|
||||
if ( typeof desc !== 'undefined' ) {
|
||||
Object.defineProperty(derivedCtor.prototype, name, desc)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -16,4 +17,4 @@ export function applyMixins(derivedCtor: any, baseCtors: any[]) {
|
||||
/**
|
||||
* Base type for a constructor function.
|
||||
*/
|
||||
export type Constructor<T = {}> = new (...args: any[]) => T
|
||||
export type Constructor<T> = new (...args: any[]) => T
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import * as nodePath from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as mkdirp from 'mkdirp'
|
||||
import { Filesystem } from "./path/Filesystem"
|
||||
import { Filesystem } from './path/Filesystem'
|
||||
import ReadableStream = NodeJS.ReadableStream;
|
||||
import WritableStream = NodeJS.WritableStream;
|
||||
|
||||
/**
|
||||
* Possible prefixes for files referenced by a UniversalPath.
|
||||
*/
|
||||
export enum UniversalPathPrefix {
|
||||
HTTP = 'http://',
|
||||
HTTPS = 'https://',
|
||||
Local = 'file://',
|
||||
}
|
||||
|
||||
/**
|
||||
* An item that could represent a path.
|
||||
*/
|
||||
@@ -24,8 +15,10 @@ export type PathLike = string | UniversalPath
|
||||
* @param parts
|
||||
*/
|
||||
export function universalPath(...parts: PathLike[]): UniversalPath {
|
||||
let [main, ...concats] = parts
|
||||
if ( !(main instanceof UniversalPath) ) main = new UniversalPath(main)
|
||||
let [main, ...concats] = parts // eslint-disable-line prefer-const
|
||||
if ( !(main instanceof UniversalPath) ) {
|
||||
main = new UniversalPath(main)
|
||||
}
|
||||
return main.concat(...concats)
|
||||
}
|
||||
|
||||
@@ -42,8 +35,11 @@ export function universalPath(...parts: PathLike[]): UniversalPath {
|
||||
export async function* walk(dir: string): any {
|
||||
for await (const sub of await fs.promises.opendir(dir) ) {
|
||||
const entry = nodePath.join(dir, sub.name)
|
||||
if ( sub.isDirectory() ) yield* walk(entry)
|
||||
else if ( sub.isFile() ) yield entry
|
||||
if ( sub.isDirectory() ) {
|
||||
yield* walk(entry)
|
||||
} else if ( sub.isFile() ) {
|
||||
yield entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +47,9 @@ export async function* walk(dir: string): any {
|
||||
* Class representing some kind of filesystem resource.
|
||||
*/
|
||||
export class UniversalPath {
|
||||
protected _prefix!: string
|
||||
protected _local!: string
|
||||
protected resourcePrefix!: string
|
||||
|
||||
protected resourceLocalPath!: string
|
||||
|
||||
constructor(
|
||||
/**
|
||||
@@ -72,15 +69,15 @@ export class UniversalPath {
|
||||
* Determine the correct prefix for this path.
|
||||
* @protected
|
||||
*/
|
||||
protected setPrefix() {
|
||||
protected setPrefix(): void {
|
||||
if ( this.initial.toLowerCase().startsWith('http://') ) {
|
||||
this._prefix = UniversalPathPrefix.HTTP
|
||||
this.resourcePrefix = 'http://'
|
||||
} else if ( this.initial.toLowerCase().startsWith('https://') ) {
|
||||
this._prefix = UniversalPathPrefix.HTTPS
|
||||
this.resourcePrefix = 'https://'
|
||||
} else if ( this.filesystem ) {
|
||||
this._prefix = this.filesystem.getPrefix()
|
||||
this.resourcePrefix = this.filesystem.getPrefix()
|
||||
} else {
|
||||
this._prefix = UniversalPathPrefix.Local
|
||||
this.resourcePrefix = 'file://'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,68 +91,68 @@ export class UniversalPath {
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
protected setLocal() {
|
||||
this._local = this.initial
|
||||
if ( this.initial.toLowerCase().startsWith(this._prefix) ) {
|
||||
this._local = this._local.slice(this._prefix.length)
|
||||
protected setLocal(): void {
|
||||
this.resourceLocalPath = this.initial
|
||||
if ( this.initial.toLowerCase().startsWith(this.resourcePrefix) ) {
|
||||
this.resourceLocalPath = this.resourceLocalPath.slice(this.resourcePrefix.length)
|
||||
}
|
||||
|
||||
if ( this._prefix === UniversalPathPrefix.Local && !this._local.startsWith('/') && !this.filesystem ) {
|
||||
this._local = nodePath.resolve(this._local)
|
||||
if ( this.resourcePrefix === 'file://' && !this.resourceLocalPath.startsWith('/') && !this.filesystem ) {
|
||||
this.resourceLocalPath = nodePath.resolve(this.resourceLocalPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new copy of this UniversalPath instance.
|
||||
*/
|
||||
clone() {
|
||||
clone(): UniversalPath {
|
||||
return new UniversalPath(this.initial)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UniversalPathPrefix of this resource.
|
||||
* Get the string of this resource.
|
||||
*/
|
||||
get prefix() {
|
||||
return this._prefix
|
||||
get prefix(): string {
|
||||
return this.resourcePrefix
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this resource refers to a file on the local filesystem.
|
||||
*/
|
||||
get isLocal() {
|
||||
return this._prefix === UniversalPathPrefix.Local && !this.filesystem
|
||||
get isLocal(): boolean {
|
||||
return this.resourcePrefix === 'file://' && !this.filesystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this resource refers to a file on a remote filesystem.
|
||||
*/
|
||||
get isRemote() {
|
||||
return this._prefix !== UniversalPathPrefix.Local || this.filesystem
|
||||
get isRemote(): boolean {
|
||||
return Boolean(this.resourcePrefix !== 'file://' || this.filesystem)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the non-prefixed path to this resource.
|
||||
*/
|
||||
get unqualified() {
|
||||
return this._local
|
||||
get unqualified(): string {
|
||||
return this.resourceLocalPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to this resource as it would be accessed from the current filesystem.
|
||||
*/
|
||||
get toLocal() {
|
||||
get toLocal(): string {
|
||||
if ( this.isLocal ) {
|
||||
return this._local
|
||||
return this.resourceLocalPath
|
||||
} else {
|
||||
return `${this.prefix}${this._local}`
|
||||
return `${this.prefix}${this.resourceLocalPath}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully-prefixed path to this resource.
|
||||
*/
|
||||
get toRemote() {
|
||||
return `${this.prefix}${this._local}`
|
||||
get toRemote(): string {
|
||||
return `${this.prefix}${this.resourceLocalPath}`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,15 +180,15 @@ export class UniversalPath {
|
||||
* @param path
|
||||
*/
|
||||
public append(path: PathLike): this {
|
||||
this._local += String(path)
|
||||
this.resourceLocalPath += String(path)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the path to a string (fully-prefixed).
|
||||
*/
|
||||
toString() {
|
||||
return `${this.prefix}${this._local}`
|
||||
toString(): string {
|
||||
return `${this.prefix}${this.resourceLocalPath}`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,8 +201,8 @@ export class UniversalPath {
|
||||
* myFile.ext // => 'txt'
|
||||
* ```
|
||||
*/
|
||||
get ext() {
|
||||
return nodePath.extname(this._local)
|
||||
get ext(): string {
|
||||
return nodePath.extname(this.resourceLocalPath)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,26 +220,26 @@ export class UniversalPath {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
walk() {
|
||||
return walk(this._local)
|
||||
walk(): any {
|
||||
return walk(this.resourceLocalPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given resource exists at the path.
|
||||
*/
|
||||
async exists() {
|
||||
async exists(): Promise<boolean> {
|
||||
if ( this.filesystem ) {
|
||||
const stat = await this.filesystem.stat({
|
||||
storePath: this._local
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
|
||||
return stat.exists
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.promises.stat(this._local)
|
||||
await fs.promises.stat(this.resourceLocalPath)
|
||||
return true
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -250,13 +247,13 @@ export class UniversalPath {
|
||||
/**
|
||||
* Recursively create this path as a directory. Equivalent to `mkdir -p` on Linux.
|
||||
*/
|
||||
async mkdir() {
|
||||
async mkdir(): Promise<void> {
|
||||
if ( this.filesystem ) {
|
||||
await this.filesystem.mkdir({
|
||||
storePath: this._local
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
} else {
|
||||
await mkdirp(this._local)
|
||||
await mkdirp(this.resourceLocalPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,22 +261,27 @@ export class UniversalPath {
|
||||
* Write the given data to this resource as a file.
|
||||
* @param data
|
||||
*/
|
||||
async write(data: string | Buffer) {
|
||||
if ( typeof data === 'string' ) data = Buffer.from(data, 'utf8')
|
||||
async write(data: string | Buffer): Promise<void> {
|
||||
if ( typeof data === 'string' ) {
|
||||
data = Buffer.from(data, 'utf8')
|
||||
}
|
||||
|
||||
if ( this.filesystem ) {
|
||||
const stream = await this.filesystem.putStoreFileAsStream({
|
||||
storePath: this._local
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
|
||||
await new Promise<void>((res, rej) => {
|
||||
stream.write(data, err => {
|
||||
if ( err ) rej(err)
|
||||
else res()
|
||||
if ( err ) {
|
||||
rej(err)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const fd = await fs.promises.open(this._local, 'w')
|
||||
const fd = await fs.promises.open(this.resourceLocalPath, 'w')
|
||||
await fd.write(data)
|
||||
await fd.close()
|
||||
}
|
||||
@@ -291,24 +293,24 @@ export class UniversalPath {
|
||||
async writeStream(): Promise<WritableStream> {
|
||||
if ( this.filesystem ) {
|
||||
return this.filesystem.putStoreFileAsStream({
|
||||
storePath: this._local
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
} else {
|
||||
return fs.createWriteStream(this._local)
|
||||
return fs.createWriteStream(this.resourceLocalPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the data from this resource's file as a string.
|
||||
*/
|
||||
async read() {
|
||||
async read(): Promise<string> {
|
||||
let stream: ReadableStream
|
||||
if ( this.filesystem ) {
|
||||
stream = await this.filesystem.getStoreFileAsStream({
|
||||
storePath: this._local
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
} else {
|
||||
stream = fs.createReadStream(this._local)
|
||||
stream = fs.createReadStream(this.resourceLocalPath)
|
||||
}
|
||||
|
||||
const chunks: any[] = []
|
||||
@@ -325,14 +327,14 @@ export class UniversalPath {
|
||||
async readStream(): Promise<ReadableStream> {
|
||||
if ( this.filesystem ) {
|
||||
return this.filesystem.getStoreFileAsStream({
|
||||
storePath: this._local
|
||||
storePath: this.resourceLocalPath,
|
||||
})
|
||||
} else {
|
||||
return fs.createReadStream(this._local)
|
||||
return fs.createReadStream(this.resourceLocalPath)
|
||||
}
|
||||
}
|
||||
|
||||
/*get mime_type() {
|
||||
/* get mime_type() {
|
||||
return Mime.lookup(this.ext)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {UniversalPath} from "../path"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
import {uuid_v4} from "../data"
|
||||
import {UniversalPath} from '../path'
|
||||
import * as path from 'path'
|
||||
import * as os from 'os'
|
||||
import {uuid4} from '../data'
|
||||
import ReadableStream = NodeJS.ReadableStream;
|
||||
import WritableStream = NodeJS.WritableStream;
|
||||
import {ErrorWithContext} from "../../error/ErrorWithContext";
|
||||
import {ErrorWithContext} from '../../error/ErrorWithContext'
|
||||
|
||||
/**
|
||||
* Error thrown when an operation is attempted on a non-existent file.
|
||||
@@ -77,12 +77,12 @@ export abstract class Filesystem {
|
||||
/**
|
||||
* Called when the Filesystem driver is initialized. Do any standup here.
|
||||
*/
|
||||
public open(): void | Promise<void> {}
|
||||
public open(): void | Promise<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
|
||||
/**
|
||||
* Called when the Filesystem driver is destroyed. Do any cleanup here.
|
||||
*/
|
||||
public close(): void | Promise<void> {}
|
||||
public close(): void | Promise<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
|
||||
/**
|
||||
* Get the URI prefix for this filesystem.
|
||||
@@ -184,9 +184,13 @@ export abstract class Filesystem {
|
||||
* @param tags
|
||||
* @protected
|
||||
*/
|
||||
protected _normalizeTags(tag?: string, tags?: string[]): string[] {
|
||||
if ( !tags ) tags = []
|
||||
if ( tag ) tags.push(tag)
|
||||
protected normalizeTags(tag?: string, tags?: string[]): string[] {
|
||||
if ( !tags ) {
|
||||
tags = []
|
||||
}
|
||||
if ( tag ) {
|
||||
tags.push(tag)
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
@@ -195,6 +199,6 @@ export abstract class Filesystem {
|
||||
* @protected
|
||||
*/
|
||||
protected tempName(): string {
|
||||
return path.resolve(os.tmpdir(), uuid_v4())
|
||||
return path.resolve(os.tmpdir(), uuid4())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {FileMetadata, FileNotFoundError, Filesystem, Stat} from "./Filesystem"
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import {UniversalPath} from "../path"
|
||||
import * as rimraf from "rimraf"
|
||||
import * as mkdirp from "mkdirp"
|
||||
import {FileMetadata, FileNotFoundError, Filesystem, Stat} from './Filesystem'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import {UniversalPath} from '../path'
|
||||
import * as rimraf from 'rimraf'
|
||||
import * as mkdirp from 'mkdirp'
|
||||
|
||||
export interface LocalFilesystemConfig {
|
||||
baseDir: string
|
||||
@@ -15,8 +15,10 @@ export interface LocalFilesystemConfig {
|
||||
*/
|
||||
export class LocalFilesystem extends Filesystem {
|
||||
constructor(
|
||||
protected readonly baseConfig: LocalFilesystemConfig
|
||||
) { super() }
|
||||
protected readonly baseConfig: LocalFilesystemConfig,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async open(): Promise<void> {
|
||||
// Make sure the base directory exists
|
||||
@@ -26,14 +28,14 @@ export class LocalFilesystem extends Filesystem {
|
||||
}
|
||||
|
||||
public getPrefix(): string {
|
||||
return 'local://'
|
||||
return 'file://'
|
||||
}
|
||||
|
||||
public async putLocalFile({localPath, storePath, ...args}: {localPath: string, storePath: string, mimeType?: string, tags?: string[], tag?: string}) {
|
||||
public async putLocalFile({localPath, storePath, ...args}: {localPath: string, storePath: string, mimeType?: string, tags?: string[], tag?: string}): Promise<void> {
|
||||
await fs.promises.copyFile(localPath, this.storePath(storePath))
|
||||
await fs.promises.writeFile(this.metadataPath(storePath), JSON.stringify({
|
||||
mimeType: args.mimeType,
|
||||
tags: this._normalizeTags(args.tag, args.tags),
|
||||
tags: this.normalizeTags(args.tag, args.tags),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -51,13 +53,13 @@ export class LocalFilesystem extends Filesystem {
|
||||
return fs.createWriteStream(this.storePath(args.storePath))
|
||||
}
|
||||
|
||||
public async getMetadata(storePath: string) {
|
||||
public async getMetadata(storePath: string): Promise<FileMetadata> {
|
||||
try {
|
||||
const json = (await fs.promises.readFile(this.metadataPath(storePath))).toString('utf-8')
|
||||
return JSON.parse(json)
|
||||
} catch (e) {
|
||||
return {
|
||||
tags: []
|
||||
tags: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +83,7 @@ export class LocalFilesystem extends Filesystem {
|
||||
exists: true,
|
||||
sizeInBytes: stat.size,
|
||||
mimeType: meta.mimeType,
|
||||
tags: meta.tags,
|
||||
tags: meta.tags ?? [],
|
||||
accessed: stat.atime,
|
||||
modified: stat.mtime,
|
||||
created: stat.ctime,
|
||||
@@ -92,7 +94,7 @@ export class LocalFilesystem extends Filesystem {
|
||||
path: new UniversalPath(args.storePath, this),
|
||||
exists: false,
|
||||
sizeInBytes: 0,
|
||||
tags: []
|
||||
tags: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +109,13 @@ export class LocalFilesystem extends Filesystem {
|
||||
fs.utimes(storePath, time, time, err => {
|
||||
if ( err ) {
|
||||
fs.open(storePath, 'w', (err2, fd) => {
|
||||
if ( err2 ) return rej(err2)
|
||||
if ( err2 ) {
|
||||
return rej(err2)
|
||||
}
|
||||
fs.close(fd, err3 => {
|
||||
if ( err3 ) return rej(err3)
|
||||
if ( err3 ) {
|
||||
return rej(err3)
|
||||
}
|
||||
res()
|
||||
})
|
||||
})
|
||||
@@ -127,9 +133,11 @@ export class LocalFilesystem extends Filesystem {
|
||||
} else {
|
||||
await new Promise<void>((res, rej) => {
|
||||
rimraf(this.storePath(args.storePath), err => {
|
||||
if ( err ) return rej(err)
|
||||
else {
|
||||
fs.promises.unlink(this.metadataPath(args.storePath)).then(() => res()).catch(rej)
|
||||
if ( err ) {
|
||||
return rej(err)
|
||||
} else {
|
||||
fs.promises.unlink(this.metadataPath(args.storePath)).then(() => res())
|
||||
.catch(rej)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import {FileMetadata, Filesystem, Stat} from "./Filesystem"
|
||||
import * as ssh2 from "ssh2"
|
||||
import * as path from "path"
|
||||
import * as fs from "fs"
|
||||
import {FileMetadata, Filesystem, Stat} from './Filesystem'
|
||||
import * as ssh2 from 'ssh2'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import ReadableStream = NodeJS.ReadableStream
|
||||
import {UniversalPath} from "../path"
|
||||
import {UniversalPath} from '../path'
|
||||
|
||||
/**
|
||||
* A Filesystem implementation that stores files on remote hosts via SFTP/SSH.
|
||||
*/
|
||||
export class SSHFilesystem extends Filesystem {
|
||||
private _ssh?: ssh2.Client
|
||||
private sshClient?: ssh2.Client
|
||||
|
||||
constructor(
|
||||
protected readonly baseConfig: { ssh: ssh2.ConnectConfig, baseDir: string },
|
||||
) { super() }
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
getPrefix(): string {
|
||||
return `sftp+${this.baseConfig.ssh.host}://`
|
||||
@@ -48,7 +50,7 @@ export class SSHFilesystem extends Filesystem {
|
||||
const sftp = await this.getSFTP()
|
||||
await sftp.writeFile(this.metadataPath(args.storePath), JSON.stringify({
|
||||
mimeType: args.mimeType,
|
||||
tags: this._normalizeTags(args.tag, args.tags)
|
||||
tags: this.normalizeTags(args.tag, args.tags),
|
||||
}))
|
||||
|
||||
// pipe the local file to the store
|
||||
@@ -69,8 +71,11 @@ export class SSHFilesystem extends Filesystem {
|
||||
const sftp = await this.getSFTP()
|
||||
await new Promise<void>((res, rej) => {
|
||||
sftp.mkdir(this.storePath(args.storePath), err => {
|
||||
if ( err ) rej(err)
|
||||
else res()
|
||||
if ( err ) {
|
||||
rej(err)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -80,11 +85,15 @@ export class SSHFilesystem extends Filesystem {
|
||||
|
||||
await new Promise<void>((res, rej) => {
|
||||
sftp.unlink(this.storePath(args.storePath), err => {
|
||||
if ( err ) return rej(err)
|
||||
else {
|
||||
if ( err ) {
|
||||
return rej(err)
|
||||
} else {
|
||||
sftp.unlink(this.metadataPath(args.storePath), err2 => {
|
||||
if ( err2 ) rej(err2)
|
||||
else res()
|
||||
if ( err2 ) {
|
||||
rej(err2)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -96,9 +105,11 @@ export class SSHFilesystem extends Filesystem {
|
||||
|
||||
try {
|
||||
const stat = await new Promise<any>((res, rej) => {
|
||||
sftp.stat(this.storePath(args.storePath), (err, stat) => {
|
||||
if ( err ) return rej(err)
|
||||
res(stat)
|
||||
sftp.stat(this.storePath(args.storePath), (err, sftpStats) => {
|
||||
if ( err ) {
|
||||
return rej(err)
|
||||
}
|
||||
res(sftpStats)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -121,7 +132,7 @@ export class SSHFilesystem extends Filesystem {
|
||||
path: new UniversalPath(args.storePath, this),
|
||||
exists: false,
|
||||
sizeInBytes: 0,
|
||||
tags: []
|
||||
tags: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,9 +146,13 @@ export class SSHFilesystem extends Filesystem {
|
||||
sftp.utimes(storePath, time, time, err => {
|
||||
if ( err ) {
|
||||
sftp.open(storePath, 'w', (err2, fd) => {
|
||||
if ( err2 ) return rej(err2)
|
||||
if ( err2 ) {
|
||||
return rej(err2)
|
||||
}
|
||||
sftp.close(fd, err3 => {
|
||||
if ( err3 ) return rej(err3)
|
||||
if ( err3 ) {
|
||||
return rej(err3)
|
||||
}
|
||||
res()
|
||||
})
|
||||
})
|
||||
@@ -153,13 +168,15 @@ export class SSHFilesystem extends Filesystem {
|
||||
const sftp = await this.getSFTP()
|
||||
return new Promise((res, rej) => {
|
||||
sftp.readFile(this.metadataPath(storePath), (err, buffer) => {
|
||||
if ( err ) rej(err)
|
||||
if ( err ) {
|
||||
rej(err)
|
||||
}
|
||||
res(JSON.parse(buffer.toString('utf-8')))
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
return {
|
||||
tags: []
|
||||
tags: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,14 +186,17 @@ export class SSHFilesystem extends Filesystem {
|
||||
const metaPath = this.metadataPath(storePath)
|
||||
await new Promise<void>((res, rej) => {
|
||||
sftp.writeFile(metaPath, JSON.stringify(meta), err => {
|
||||
if ( err ) rej(err)
|
||||
else res()
|
||||
if ( err ) {
|
||||
rej(err)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this._ssh?.end()
|
||||
await this.sshClient?.end()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,13 +204,15 @@ export class SSHFilesystem extends Filesystem {
|
||||
* If a connection already exists, re-use it.
|
||||
*/
|
||||
async getSSH(): Promise<ssh2.Client> {
|
||||
if ( this._ssh ) return this._ssh
|
||||
if ( this.sshClient ) {
|
||||
return this.sshClient
|
||||
}
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
const client = new ssh2.Client()
|
||||
|
||||
client.on('ready', () => {
|
||||
this._ssh = client
|
||||
this.sshClient = client
|
||||
res(client)
|
||||
}).connect(this.baseConfig.ssh)
|
||||
|
||||
@@ -206,8 +228,11 @@ export class SSHFilesystem extends Filesystem {
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
ssh.sftp((err, sftp) => {
|
||||
if ( err ) rej(err)
|
||||
else res(sftp)
|
||||
if ( err ) {
|
||||
rej(err)
|
||||
} else {
|
||||
res(sftp)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -236,7 +261,7 @@ export class SSHFilesystem extends Filesystem {
|
||||
* @protected
|
||||
*/
|
||||
protected streamToString(stream: NodeJS.ReadableStream): Promise<string> {
|
||||
const chunks: Buffer[] = [];
|
||||
const chunks: Buffer[] = []
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)))
|
||||
stream.on('error', (err) => reject(err))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @param length
|
||||
* @param padWith
|
||||
*/
|
||||
export function padRight(string: string, length: number, padWith: string = ' ') {
|
||||
export function padRight(string: string, length: number, padWith = ' '): string {
|
||||
while ( string.length < length ) {
|
||||
string += padWith
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export function padRight(string: string, length: number, padWith: string = ' ')
|
||||
* @param length
|
||||
* @param padWith
|
||||
*/
|
||||
export function padLeft(string: string, length: number, padWith: string = ' ') {
|
||||
export function padLeft(string: string, length: number, padWith = ' '): string {
|
||||
while ( string.length < length ) {
|
||||
string = `${padWith}${string}`
|
||||
}
|
||||
@@ -35,8 +35,8 @@ export function padLeft(string: string, length: number, padWith: string = ' ') {
|
||||
* @param length
|
||||
* @param padWith
|
||||
*/
|
||||
export function padCenter(string: string, length: number, padWith: string = ' ') {
|
||||
let bit = false
|
||||
export function padCenter(string: string, length: number, padWith = ' '): string {
|
||||
const bit = false
|
||||
while ( string.length < length ) {
|
||||
if ( bit ) {
|
||||
string = `${padWith}${string}`
|
||||
|
||||
@@ -31,10 +31,10 @@ export interface TimeoutSubscriber<T> {
|
||||
* @param {number} timeout - timeout in milliseconds
|
||||
* @param {Promise} promise - the promise to subscribe to
|
||||
*/
|
||||
export function withTimeout<T>(timeout: number, promise: Promise<T>) {
|
||||
let onTimeHandler: (arg: T) => any = (arg) => {}
|
||||
let lateHandler: (arg: T) => any = (arg) => {}
|
||||
let timeoutHandler: () => any = () => {}
|
||||
export function withTimeout<T>(timeout: number, promise: Promise<T>): TimeoutSubscriber<T> {
|
||||
let onTimeHandler: (arg: T) => any = () => {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
let lateHandler: (arg: T) => any = () => {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
let timeoutHandler: () => any = () => {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
|
||||
const sub = {
|
||||
onTime: handler => {
|
||||
@@ -54,7 +54,9 @@ export function withTimeout<T>(timeout: number, promise: Promise<T>) {
|
||||
let resolved = false
|
||||
setTimeout(() => {
|
||||
expired = true
|
||||
if ( !resolved ) timeoutHandler()
|
||||
if ( !resolved ) {
|
||||
timeoutHandler()
|
||||
}
|
||||
}, timeout)
|
||||
|
||||
const result: T = await promise
|
||||
@@ -67,7 +69,7 @@ export function withTimeout<T>(timeout: number, promise: Promise<T>) {
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
},
|
||||
} as TimeoutSubscriber<T>
|
||||
|
||||
return sub
|
||||
|
||||
Reference in New Issue
Block a user