Initial commit

This commit is contained in:
garrettmills 2020-06-15 20:35:30 -05:00
commit 6c4696227b
No known key found for this signature in database
GPG Key ID: 6ACD58D6ADACFC6E
19 changed files with 3225 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea*

5
di/module.ts Normal file
View File

@ -0,0 +1,5 @@
export { container, make } from './src/global.ts'
export { Inject, Injectable } from './src/decorator/Injection.ts'
export { Service } from './src/decorator/Service.ts'
export { default as AbstractFactory } from './src/factory/AbstractFactory.ts'
export { Container, InvalidDependencyKeyError, DuplicateFactoryKeyError, MaybeDependency, MaybeFactory } from './src/Container.ts'

152
di/src/Container.ts Executable file
View File

@ -0,0 +1,152 @@
import AbstractFactory from './factory/AbstractFactory.ts'
import Factory from './factory/Factory.ts'
import SingletonFactory from './factory/SingletonFactory.ts'
import FunctionFactory from './factory/FunctionFactory.ts'
import NamedFactory from './factory/NamedFactory.ts'
import { DependencyKey } from './type/DependencyKey.ts'
import { collect, Collection } from '../../lib/src/collection/Collection.ts'
import Instantiable, {isInstantiable} from './type/Instantiable.ts'
type MaybeFactory = AbstractFactory | undefined
type MaybeDependency = any | undefined
type ResolvedDependency = { param_index: number, key: DependencyKey, resolved: any }
interface InstanceRef {
key: DependencyKey,
value: any,
}
class DuplicateFactoryKeyError extends Error {
constructor(key: DependencyKey) {
super(`A factory definition already exists with the key for ${key}.`)
}
}
class InvalidDependencyKeyError extends Error {
constructor(key: DependencyKey) {
super(`No such dependency is registered with this container: ${key}`)
}
}
class Container {
private factories: Collection<AbstractFactory> = new Collection<AbstractFactory>()
private instances: Collection<InstanceRef> = new Collection<InstanceRef>()
register(dependency: Instantiable<any>) {
if ( this.resolve(dependency) )
throw new DuplicateFactoryKeyError(dependency)
const factory = new Factory(dependency)
this.factories.push(factory)
}
register_producer(name: string, producer: () => any) {
if ( this.resolve(name) )
throw new DuplicateFactoryKeyError(name)
const factory = new FunctionFactory(name, producer)
this.factories.push(factory)
}
register_named(name: string, dependency: Instantiable<any>) {
if ( this.resolve(name) )
throw new DuplicateFactoryKeyError(name)
const factory = new NamedFactory(name, dependency)
this.factories.push(factory)
}
register_singleton(key: string, value: any) {
if ( this.resolve(key) )
throw new DuplicateFactoryKeyError(key)
this.factories.push(new SingletonFactory(value, key))
}
register_factory(factory: AbstractFactory) {
if ( !this.factories.includes(factory) )
this.factories.push(factory)
}
has_instance(key: DependencyKey): boolean {
return this.instances.where('key', '=', key).isNotEmpty()
}
has_key(key: DependencyKey): boolean {
return !!this.resolve(key)
}
get_existing_instance(key: DependencyKey): MaybeDependency {
const instances = this.instances.where('key', '=', key)
if ( instances.isNotEmpty() ) return instances.first()
}
resolve(key: DependencyKey): MaybeFactory {
const factory = this.factories.firstWhere(item => item.match(key))
if ( factory ) return factory
}
resolve_and_create(key: DependencyKey, ...parameters: any[]): any {
// If we've already instantiated this, just return that
const instance = this.get_existing_instance(key)
if ( typeof instance !== 'undefined' ) return instance.value
// Otherwise, attempt to create it
const factory = this.resolve(key)
if ( !factory )
throw new InvalidDependencyKeyError(key)
// Produce and store a new instance
const new_instance = this.produce_factory(factory, parameters)
this.instances.push({
key,
value: new_instance,
})
return new_instance
}
protected produce_factory(factory: AbstractFactory, parameters: any[]) {
// Create the dependencies for the factory
const keys = factory.get_dependency_keys().filter(req => this.has_key(req.key))
const dependencies = keys.map<ResolvedDependency>(req => {
return {
param_index: req.param_index,
key: req.key,
resolved: this.resolve_and_create(req.key),
}
}).sortBy('param_index')
// Build the arguments for the factory, using dependencies in the
// correct param_index positions, or parameters of we don't have
// the dependency.
const construction_args = []
let params = collect(parameters).reverse()
for ( let i = 0; i <= dependencies.max('param_index'); i++ ) {
const dep = dependencies.firstWhere('param_index', '=', i)
if ( dep ) construction_args.push(dep.resolved)
else construction_args.push(params.pop())
}
// Produce a new instance
return factory.produce(construction_args, params.reverse().all())
}
make<T>(target: Instantiable<T>|DependencyKey, ...parameters: any[]): T {
if ( isInstantiable(target) )
return this.produce_factory(new Factory(target), parameters)
else
return this.resolve_and_create(target, ...parameters)
}
}
export {
Container,
InvalidDependencyKeyError,
DuplicateFactoryKeyError,
MaybeDependency,
MaybeFactory,
ResolvedDependency,
InstanceRef,
}

73
di/src/decorator/Injection.ts Executable file
View File

@ -0,0 +1,73 @@
import {Reflect} from '../../../lib/src/external/reflect.ts'
import {DEPENDENCY_KEYS_METADATA_KEY, DependencyKey} from '../type/DependencyKey.ts'
import {collect, Collection} from '../../../lib/src/collection/Collection.ts'
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
const initDependencyMetadata = (target: Object): Collection<DependencyRequirement> => {
const param_types = Reflect.getMetadata('design:paramtypes', target)
return collect<DependencyKey>(param_types).map<DependencyRequirement>((type, index) => {
return {
param_index: index,
key: type,
overridden: false,
}
})
}
const Injectable = (): ClassDecorator => {
return (target) => {
const meta = initDependencyMetadata(target)
const existing = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, target)
const new_meta = new Collection<DependencyRequirement>()
if ( existing ) {
const max_new = meta.max('param_index')
const max_existing = existing.max('param_index')
for ( let i = 0; i <= Math.max(max_new, max_existing); i++ ) {
const existing_dr = existing.firstWhere('param_index', '=', i)
const new_dr = meta.firstWhere('param_index', '=', i)
if ( existing_dr && !new_dr ) {
new_meta.push(existing_dr)
} else if ( new_dr && !existing_dr ) {
new_meta.push(new_dr)
} else if ( new_dr && existing_dr ) {
if ( existing_dr.overridden ) {
new_meta.push(existing_dr)
} else {
new_meta.push(new_dr)
}
}
}
} else {
new_meta.concat(meta)
}
Reflect.defineMetadata(DEPENDENCY_KEYS_METADATA_KEY, new_meta, target)
}
}
const Inject = (key: DependencyKey): ParameterDecorator => {
return (target, property, param_index) => {
if ( !Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, target) ) {
Reflect.defineMetadata(DEPENDENCY_KEYS_METADATA_KEY, initDependencyMetadata(target), target)
}
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, target)
const req = meta.firstWhere('param_index', '=', param_index)
if ( req ) {
req.key = key
req.overridden = true
} else {
meta.push({
param_index,
key,
overridden: true
})
}
Reflect.defineMetadata(DEPENDENCY_KEYS_METADATA_KEY, meta, target)
}
}
export { Inject, Injectable, initDependencyMetadata }

13
di/src/decorator/Service.ts Executable file
View File

@ -0,0 +1,13 @@
import { container } from '../global.ts'
import { isInstantiable } from '../type/Instantiable.ts'
const Service = (name?: string): ClassDecorator => {
return (target) => {
if ( isInstantiable(target) ) {
if ( name ) container.register_named(name, target)
else container.register(target)
}
}
}
export { Service }

View File

@ -0,0 +1,12 @@
import {Collection} from '../../../lib/src/collection/Collection.ts'
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
export default abstract class AbstractFactory {
protected constructor(
protected token: any
) {}
abstract produce(dependencies: any[], parameters: any[]): any
abstract match(something: any): boolean
abstract get_dependency_keys(): Collection<DependencyRequirement>
}

28
di/src/factory/Factory.ts Executable file
View File

@ -0,0 +1,28 @@
import Instantiable from '../type/Instantiable.ts'
import { DEPENDENCY_KEYS_METADATA_KEY } from '../type/DependencyKey.ts'
import { Reflect } from '../../../lib/src/external/reflect.ts'
import { Collection } from '../../../lib/src/collection/Collection.ts'
import { DependencyRequirement } from '../type/DependencyRequirement.ts'
import AbstractFactory from './AbstractFactory.ts'
export default class Factory extends AbstractFactory {
constructor(
protected token: Instantiable<any>
) {
super(token)
}
produce(dependencies: any[], parameters: any[]) {
return new this.token(...dependencies, ...parameters)
}
match(something: any) {
return something === this.token || (something.toString && String(something) === this.token.name)
}
get_dependency_keys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.token)
if ( meta ) return meta
return new Collection<DependencyRequirement>()
}
}

View File

@ -0,0 +1,24 @@
import AbstractFactory from './AbstractFactory.ts'
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
import {Collection} from '../../../lib/src/collection/Collection.ts'
export default class FunctionFactory extends AbstractFactory {
constructor(
protected name: string,
protected token: () => any,
) {
super(token)
}
get_dependency_keys(): Collection<DependencyRequirement> {
return new Collection<DependencyRequirement>()
}
match(something: any) {
return something === this.name
}
produce(dependencies: any[], parameters: any[]): any {
return this.token()
}
}

View File

@ -0,0 +1,15 @@
import Factory from './Factory.ts'
import Instantiable from "../type/Instantiable.ts";
export default class NamedFactory extends Factory {
constructor(
protected name: string,
protected token: Instantiable<any>,
) {
super(token)
}
match(something: any) {
return something === this.name
}
}

View File

@ -0,0 +1,24 @@
import Factory from './Factory.ts'
import {Collection} from '../../../lib/src/collection/Collection.ts'
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
export default class SingletonFactory extends Factory {
constructor(
protected token: FunctionConstructor,
protected key: string,
) {
super(token)
}
produce(dependencies: any[], parameters: any[]) {
return this.token
}
match(something: any) {
return something === this.key
}
get_dependency_keys(): Collection<DependencyRequirement> {
return new Collection<DependencyRequirement>()
}
}

7
di/src/global.ts Normal file
View File

@ -0,0 +1,7 @@
import { Container } from './Container.ts'
import Instantiable from './type/Instantiable.ts'
import { DependencyKey } from './type/DependencyKey.ts'
const container = new Container()
const make = <T>(target: Instantiable<T>|DependencyKey, ...parameters: any[]) => container.make(target, ...parameters)
export { container, make }

View File

@ -0,0 +1,4 @@
import Instantiable from './Instantiable.ts'
const DEPENDENCY_KEYS_METADATA_KEY = 'daton:di:dependencyKeys.ts'
type DependencyKey = Instantiable<any> | string
export { DependencyKey, DEPENDENCY_KEYS_METADATA_KEY }

View File

@ -0,0 +1,9 @@
import { DependencyKey } from './DependencyKey.ts'
interface DependencyRequirement {
param_index: number,
key: DependencyKey,
overridden: boolean,
}
export { DependencyRequirement }

View File

@ -0,0 +1,9 @@
export default interface Instantiable<T> {
new(...args: any[]): T
}
const isInstantiable = (what: any): what is Instantiable<any> => {
return (typeof what === 'object' || typeof what === 'function') && 'constructor' in what && typeof what.constructor === 'function'
}
export { isInstantiable }

595
lib/src/collection/Collection.ts Executable file
View File

@ -0,0 +1,595 @@
type CollectionItem<T> = T
type MaybeCollectionItem<T> = CollectionItem<T> | undefined
type KeyFunction<T, T2> = (item: CollectionItem<T>, index: number) => CollectionItem<T2>
type KeyReducerFunction<T, T2> = (current: any, item: CollectionItem<T>, index: number) => T2
type CollectionFunction<T, T2> = (items: Collection<T>) => T2
type KeyOperator<T, T2> = string | KeyFunction<T, T2>
type AssociatedCollectionItem<T2, T> = { key: T2, item: CollectionItem<T> }
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
import { WhereOperator, applyWhere, whereMatch } from './Where.ts'
const collect = <T>(items: CollectionItem<T>[]): Collection<T> => Collection.collect(items)
export {
collect,
Collection,
// Types
CollectionItem,
MaybeCollectionItem,
KeyFunction,
KeyReducerFunction,
CollectionFunction,
KeyOperator,
AssociatedCollectionItem,
CollectionComparable,
DeterminesEquality,
CollectionIndex,
MaybeCollectionIndex,
ComparisonFunction,
}
class Collection<T> {
private _items: CollectionItem<T>[] = []
public static collect<T>(items: CollectionItem<T>[]): Collection<T> {
return new Collection(items)
}
public static size(size: number): Collection<undefined> {
const arr = Array(size).fill(undefined)
return new Collection<undefined>(arr)
}
public static fill<T>(size: number, item: T): Collection<T> {
const arr = Array(size).fill(item)
return new Collection<T>(arr)
}
constructor(
items?: CollectionItem<T>[]
) {
if ( items )
this._items = items
}
private _all<T2>(key: KeyOperator<T, T2>): CollectionItem<T2>[] {
let items: CollectionItem<T2>[] = []
if ( typeof key === 'function' ) {
items = this._items.map(key)
} else if ( typeof key === 'string' ) {
items = this._items.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 _all_associate<T2>(key: KeyOperator<T, T2>): AssociatedCollectionItem<T2, T>[] {
const assoc_items: AssociatedCollectionItem<T2, T>[] = []
let items = [...this._items]
if ( typeof key === 'function' ) {
items.map((item, index) => {
const key_item = key(item, index)
assoc_items.push({ key: key_item, item })
})
} else if ( typeof key === 'string' ) {
items.map((item, index) => {
assoc_items.push({ key: (<any>item)[key], item })
})
}
return assoc_items
}
all(): CollectionItem<T>[] {
return [...this._items]
}
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
let sum = items.reduce((prev, curr) => prev + curr)
return sum / items.length
}
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)
const middle = Math.floor((items.length - 1) / 2)
if ( items.length % 2 ) return items[middle]
else return (items[middle] + items[middle + 1]) / 2;
}
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)
let counts: any = {}
for ( const item of items ) {
counts[item] = (counts[item] ?? -1) + 1
}
return Math.max(...Object.values(counts).map(Number))
}
collapse(): Collection<any> {
const new_items: CollectionItem<T>[] = []
const items = [...this._items]
const get_layer = (current: CollectionItem<T>|CollectionItem<T>[]) => {
if ( typeof (<any>current)[Symbol.iterator] === 'function' ) {
// @ts-ignore
// TODO fix this
for (const item of current) {
if (Array.isArray(item)) {
get_layer(item)
} else {
new_items.push(item)
}
}
}
}
get_layer(items)
return new Collection(new_items)
}
contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): boolean {
const associate = this._all_associate(key)
const matches = applyWhere(associate, operator, operand)
return matches.length > 0
}
// TODO crossJoin
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)
}
return new Collection(matches)
}
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)) )
matches.push(item)
}
return new Collection(matches)
}
each<T2>(func: KeyFunction<T, T2>): Collection<T2> {
return new Collection(this._items.map(func))
}
map<T2>(func: KeyFunction<T, T2>): Collection<T2> {
return this.each(func)
}
every<T2>(func: KeyFunction<T, T2>): boolean {
return this._items.every(func)
}
everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): boolean {
const items = this._all_associate(key)
return items.every(item => whereMatch(item, operator, operand))
}
filter<T2>(func: KeyFunction<T, T2>): Collection<T> {
return new Collection(this._items.filter(func))
}
when<T2>(bool: boolean, then: CollectionFunction<T, T2>): Collection<T> {
if ( bool ) then(this)
return this
}
unless<T2>(bool: boolean, then: CollectionFunction<T, T2>): Collection<T> {
if ( !bool ) then(this)
return this
}
where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Collection<T> {
const items = this._all_associate(key)
return new Collection(applyWhere(items, operator, operand))
}
whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Collection<T> {
return this.diff(this.where(key, operator, operand))
}
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)
}
return new Collection(matches)
}
whereNotIn<T2>(key: KeyOperator<T, T2>, items: CollectionComparable<T2>): Collection<T> {
return this.diff(this.whereIn(key, items))
}
first(): MaybeCollectionItem<T> {
if ( this.length > 0 ) return this._items[0]
}
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]
}
firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
const items = this.whereNot(key, operator, operand).all()
if ( items.length > 0 ) return items[0]
}
get length() {
return this._items.length
}
count() {
return this._items.length
}
// TODO flatten - depth
get(index: number, fallback?: any) {
if ( this.length > index ) return this._items[index]
else return fallback
}
at(index: number): MaybeCollectionItem<T> {
return this.get(index)
}
groupBy<T2>(key: KeyOperator<T, T2>): any {
const items = this._all_associate(key)
const groups: any = {}
for ( const item of items ) {
const key = String(item.key)
if ( !groups[key] ) groups[key] = []
groups[key].push(item.item)
}
return groups
}
associate<T2>(key: KeyOperator<T, T2>): any {
const items = this._all_associate(key)
const values: any = {}
for ( const item of items ) {
values[String(item.key)] = item.item
}
return values
}
join(delimiter: string): string {
return this._items.join(delimiter)
}
implode(delimiter: string): string {
return this.join(delimiter)
}
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 }
})
for ( const item of all_items ) {
if ( compare.includes(item.key) )
intersect.push(item.item)
}
return new Collection(intersect)
}
isEmpty(): boolean {
return this.length < 1
}
isNotEmpty(): boolean {
return this.length > 0
}
last(): MaybeCollectionItem<T> {
if ( this.length > 0 ) return this._items.reverse()[0]
}
lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
const items = this.where(key, operator, operand).all()
if ( items.length > 0 ) return items.reverse()[0]
}
lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
const items = this.whereNot(key, operator, operand).all()
if ( items.length > 0 ) return items.reverse()[0]
}
pluck<T2>(key: KeyOperator<T, T2>): Collection<T2> {
return new Collection<T2>(this._all(key))
}
max<T2>(key: KeyOperator<T, T2>): number {
const values = this._all_numbers(key)
return Math.max(...values)
}
whereMax<T2>(key: KeyOperator<T, T2>): Collection<T> {
return this.where(key, '=', this.max(key))
}
min<T2>(key: KeyOperator<T, T2>): number {
const values = this._all_numbers(key)
return Math.min(...values)
}
whereMin<T2>(key: KeyOperator<T, T2>): Collection<T> {
return this.where(key, '=', this.min(key))
}
merge<T2>(items: CollectionComparable<T2>): Collection<T|T2> {
const merge = items instanceof Collection ? items.all() : items
return new Collection([...this._items, ...merge])
}
nth(n: number): Collection<T> {
const matches: CollectionItem<T>[] = []
let current = 1
this._items.forEach((item, index) => {
if ( current === 1 ) matches.push(item)
current += 1
if ( current > n ) current = 1
})
return new Collection(matches)
}
forPage(page: number, perPage: number) {
const start = page * perPage - perPage
const end = page * perPage
return new Collection(this._items.slice(start, end))
}
pipe<T2>(func: CollectionFunction<T, T2>): any {
return func(this)
}
pop(): MaybeCollectionItem<T> {
if ( this.length > 0 ) {
return this._items.pop()
}
}
prepend(item: CollectionItem<T>): Collection<T> {
this._items = [item, ...this._items]
return this
}
push(item: CollectionItem<T>): Collection<T> {
this._items.push(item)
return this
}
concat(items: CollectionComparable<T>): Collection<T> {
const concats = items instanceof Collection ? items.all() : items
for ( const item of concats ) {
this._items.push(item)
}
return this
}
pull(index: number, fallback: any): MaybeCollectionItem<T> {
let value
const new_items = []
this._items.forEach((item, item_index) => {
if ( item_index !== index ) new_items.push(item)
else value = item
})
return value ?? fallback
}
put(index: number, item: CollectionItem<T>): Collection<T> {
const new_items = []
let inserted = false
this._items.forEach((existing, existing_index) => {
if ( existing_index === index ) {
new_items.push(item)
inserted = true
}
new_items.push(existing)
})
if ( !inserted ) new_items.push(item)
return new Collection(new_items)
}
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)
}
return new Collection(random_items)
}
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)
})
return current_value
}
reject<T2>(truth_test: KeyFunction<T, T2>): Collection<T> {
const rejected = this._items.filter((item, index) => {
return !truth_test(item, index)
})
return new Collection(rejected)
}
reverse(): Collection<T> {
return new Collection([...this._items.reverse()])
}
search(item: CollectionItem<T>): MaybeCollectionIndex {
let found_index
this._items.some((possible_item, index) => {
if ( possible_item === item ) {
found_index = index
return true
}
})
return found_index
}
shift(): MaybeCollectionItem<T> {
if ( this.length > 0 ) {
return this._items.shift()
}
}
shuffle(): Collection<T> {
const items = [...this._items]
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]]
}
return new Collection(items)
}
slice(start: number, end: number) {
return new Collection(this._items.slice(start, end))
}
// TODO split
// TODO chunk
sort(compare_func?: ComparisonFunction<T>): Collection<T> {
const items = this._items
if ( compare_func ) items.sort(compare_func)
else items.sort()
return new Collection(items)
}
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 }
})
items.sort((a: any, b: any) => {
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))
}
sortDesc(compare_func?: ComparisonFunction<T>): Collection<T> {
return this.sort(compare_func).reverse()
}
sortByDesc<T2>(key?: KeyOperator<T, T2>): Collection<T> {
return this.sortBy(key).reverse()
}
splice(start: CollectionIndex, deleteCount?: number): Collection<T> {
return new Collection([...this._items].splice(start, deleteCount))
}
sum<T2>(key?: KeyOperator<T, T2>): number {
let items
if ( key ) items = this._all_numbers(key)
else items = this._items.map(x => Number(x))
return items.reduce((prev, curr) => prev + curr)
}
take(limit: number): Collection<T> {
if ( limit === 0 ) return new Collection()
else if ( limit > 0 ) {
return new Collection(this._items.slice(0, limit))
} else {
return new Collection(this._items.reverse().slice(0, -1 * limit).reverse())
}
}
tap<T2>(func: CollectionFunction<T, T2>): Collection<T> {
func(this)
return this
}
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]
for ( const item of items ) {
if ( !has.includes(item) ) has.push(item)
}
return new Collection(has)
}
includes(item: CollectionItem<T>): boolean {
return this._items.includes(item)
}
pad(length: number, value: CollectionItem<T>): Collection<T> {
const items = [...this._items]
while ( items.length < length ) items.push(value)
return new Collection(items)
}
toArray(): any[] {
const returns: any = []
for ( const item of this._items ) {
if ( item instanceof Collection ) returns.push(item.toArray())
else returns.push(item)
}
return returns
}
toJSON(replacer = undefined, space = 4): string {
return JSON.stringify(this.toArray(), replacer, space)
}
// TODO getIterator
// TODO getCachingIterator
[Symbol.iterator]() {
const items = this._items
return {
current_index: 0,
next() {
if ( items.length < 1 || this.current_index + 1 >= items.length ) {
return { done: true }
}
const item = items[this.current_index]
this.current_index += 1
return { done: false, value: item }
},
}
}
}

75
lib/src/collection/Where.ts Executable file
View File

@ -0,0 +1,75 @@
type WhereOperator = '&' | '>' | '>=' | '<' | '<=' | '!=' | '<=>' | '%' | '|' | '!' | '~' | '=' | '^'
type AssociatedSearchItem = { key: any, item: any }
type WhereResult = any[]
// bitwise and
// greater than
// greater than equal
// less than
// less than equal
// not equal
// null-safe equal
// modulo
// bitwise or
// not
// bitwise negation
// equal
const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand?: any): 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
}
return false
}
const applyWhere = (items: AssociatedSearchItem[], operator: WhereOperator, operand?: any): WhereResult => {
const matches: WhereResult = []
for ( const item of items ) {
if ( whereMatch(item, operator, operand) )
matches.push(item.item)
}
return matches
}
export { WhereOperator, WhereResult, AssociatedSearchItem, applyWhere, whereMatch }

2155
lib/src/external/reflect.ts vendored Normal file

File diff suppressed because it is too large Load Diff

15
lib/src/type/UUID.ts Executable file
View File

@ -0,0 +1,15 @@
type UUID = string
const isUUID = (possible: any): boolean => {
return typeof possible === 'string' && /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/.test(possible)
}
const uuid = (): UUID => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0
const v = c == 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
export { UUID, isUUID, uuid }

9
tsconfig.json Executable file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "es2017"
}
}