commit 6c4696227b35916241e455cd32fe6b9893e9ad97 Author: garrettmills Date: Mon Jun 15 20:35:30 2020 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d4daa4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea* diff --git a/di/module.ts b/di/module.ts new file mode 100644 index 0000000..27ea424 --- /dev/null +++ b/di/module.ts @@ -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' diff --git a/di/src/Container.ts b/di/src/Container.ts new file mode 100755 index 0000000..ce5126e --- /dev/null +++ b/di/src/Container.ts @@ -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 = new Collection() + private instances: Collection = new Collection() + + register(dependency: Instantiable) { + 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) { + 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(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(target: Instantiable|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, +} diff --git a/di/src/decorator/Injection.ts b/di/src/decorator/Injection.ts new file mode 100755 index 0000000..cb062c2 --- /dev/null +++ b/di/src/decorator/Injection.ts @@ -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 => { + const param_types = Reflect.getMetadata('design:paramtypes', target) + return collect(param_types).map((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() + + 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 } diff --git a/di/src/decorator/Service.ts b/di/src/decorator/Service.ts new file mode 100755 index 0000000..9403563 --- /dev/null +++ b/di/src/decorator/Service.ts @@ -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 } diff --git a/di/src/factory/AbstractFactory.ts b/di/src/factory/AbstractFactory.ts new file mode 100644 index 0000000..1cf338f --- /dev/null +++ b/di/src/factory/AbstractFactory.ts @@ -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 +} diff --git a/di/src/factory/Factory.ts b/di/src/factory/Factory.ts new file mode 100755 index 0000000..55bc9cb --- /dev/null +++ b/di/src/factory/Factory.ts @@ -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 + ) { + 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 { + const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.token) + if ( meta ) return meta + return new Collection() + } +} diff --git a/di/src/factory/FunctionFactory.ts b/di/src/factory/FunctionFactory.ts new file mode 100644 index 0000000..419f8c0 --- /dev/null +++ b/di/src/factory/FunctionFactory.ts @@ -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 { + return new Collection() + } + + match(something: any) { + return something === this.name + } + + produce(dependencies: any[], parameters: any[]): any { + return this.token() + } +} diff --git a/di/src/factory/NamedFactory.ts b/di/src/factory/NamedFactory.ts new file mode 100644 index 0000000..aad8d55 --- /dev/null +++ b/di/src/factory/NamedFactory.ts @@ -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, + ) { + super(token) + } + + match(something: any) { + return something === this.name + } +} diff --git a/di/src/factory/SingletonFactory.ts b/di/src/factory/SingletonFactory.ts new file mode 100644 index 0000000..28095d9 --- /dev/null +++ b/di/src/factory/SingletonFactory.ts @@ -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 { + return new Collection() + } +} diff --git a/di/src/global.ts b/di/src/global.ts new file mode 100644 index 0000000..34639bd --- /dev/null +++ b/di/src/global.ts @@ -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 = (target: Instantiable|DependencyKey, ...parameters: any[]) => container.make(target, ...parameters) +export { container, make } diff --git a/di/src/type/DependencyKey.ts b/di/src/type/DependencyKey.ts new file mode 100644 index 0000000..ccb46fc --- /dev/null +++ b/di/src/type/DependencyKey.ts @@ -0,0 +1,4 @@ +import Instantiable from './Instantiable.ts' +const DEPENDENCY_KEYS_METADATA_KEY = 'daton:di:dependencyKeys.ts' +type DependencyKey = Instantiable | string +export { DependencyKey, DEPENDENCY_KEYS_METADATA_KEY } diff --git a/di/src/type/DependencyRequirement.ts b/di/src/type/DependencyRequirement.ts new file mode 100644 index 0000000..ed090c9 --- /dev/null +++ b/di/src/type/DependencyRequirement.ts @@ -0,0 +1,9 @@ +import { DependencyKey } from './DependencyKey.ts' + +interface DependencyRequirement { + param_index: number, + key: DependencyKey, + overridden: boolean, +} + +export { DependencyRequirement } diff --git a/di/src/type/Instantiable.ts b/di/src/type/Instantiable.ts new file mode 100644 index 0000000..74ddb33 --- /dev/null +++ b/di/src/type/Instantiable.ts @@ -0,0 +1,9 @@ +export default interface Instantiable { + new(...args: any[]): T +} + +const isInstantiable = (what: any): what is Instantiable => { + return (typeof what === 'object' || typeof what === 'function') && 'constructor' in what && typeof what.constructor === 'function' +} + +export { isInstantiable } diff --git a/lib/src/collection/Collection.ts b/lib/src/collection/Collection.ts new file mode 100755 index 0000000..a0bfb8c --- /dev/null +++ b/lib/src/collection/Collection.ts @@ -0,0 +1,595 @@ +type CollectionItem = T +type MaybeCollectionItem = CollectionItem | undefined +type KeyFunction = (item: CollectionItem, index: number) => CollectionItem +type KeyReducerFunction = (current: any, item: CollectionItem, index: number) => T2 +type CollectionFunction = (items: Collection) => T2 +type KeyOperator = string | KeyFunction +type AssociatedCollectionItem = { key: T2, item: CollectionItem } +type CollectionComparable = CollectionItem[] | Collection +type DeterminesEquality = (item: CollectionItem, other: any) => boolean +type CollectionIndex = number +type MaybeCollectionIndex = CollectionIndex | undefined +type ComparisonFunction = (item: CollectionItem, other_item: CollectionItem) => number + +import { WhereOperator, applyWhere, whereMatch } from './Where.ts' + +const collect = (items: CollectionItem[]): Collection => Collection.collect(items) +export { + collect, + Collection, + + // Types + CollectionItem, + MaybeCollectionItem, + KeyFunction, + KeyReducerFunction, + CollectionFunction, + KeyOperator, + AssociatedCollectionItem, + CollectionComparable, + DeterminesEquality, + CollectionIndex, + MaybeCollectionIndex, + ComparisonFunction, +} + +class Collection { + private _items: CollectionItem[] = [] + + public static collect(items: CollectionItem[]): Collection { + return new Collection(items) + } + + public static size(size: number): Collection { + const arr = Array(size).fill(undefined) + return new Collection(arr) + } + + public static fill(size: number, item: T): Collection { + const arr = Array(size).fill(item) + return new Collection(arr) + } + + constructor( + items?: CollectionItem[] + ) { + if ( items ) + this._items = items + } + + private _all(key: KeyOperator): CollectionItem[] { + let items: CollectionItem[] = [] + if ( typeof key === 'function' ) { + items = this._items.map(key) + } else if ( typeof key === 'string' ) { + items = this._items.map((item: CollectionItem) => (item)[key]) + } + return items + } + + private _all_numbers(key: KeyOperator): number[] { + return this._all(key).map(value => Number(value)) + } + + private _all_associate(key: KeyOperator): AssociatedCollectionItem[] { + const assoc_items: AssociatedCollectionItem[] = [] + 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: (item)[key], item }) + }) + } + return assoc_items + } + + all(): CollectionItem[] { + return [...this._items] + } + + average(key?: KeyOperator): 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(key?: KeyOperator): 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(key?: KeyOperator): 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 { + const new_items: CollectionItem[] = [] + const items = [...this._items] + const get_layer = (current: CollectionItem|CollectionItem[]) => { + if ( typeof (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(key: KeyOperator, operator: WhereOperator, operand?: any): boolean { + const associate = this._all_associate(key) + const matches = applyWhere(associate, operator, operand) + return matches.length > 0 + } + + // TODO crossJoin + + diff(items: CollectionComparable): Collection { + 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(items: CollectionComparable, compare: DeterminesEquality): Collection { + 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(func: KeyFunction): Collection { + return new Collection(this._items.map(func)) + } + + map(func: KeyFunction): Collection { + return this.each(func) + } + + every(func: KeyFunction): boolean { + return this._items.every(func) + } + + everyWhere(key: KeyOperator, operator: WhereOperator, operand?: any): boolean { + const items = this._all_associate(key) + return items.every(item => whereMatch(item, operator, operand)) + } + + filter(func: KeyFunction): Collection { + return new Collection(this._items.filter(func)) + } + + when(bool: boolean, then: CollectionFunction): Collection { + if ( bool ) then(this) + return this + } + + unless(bool: boolean, then: CollectionFunction): Collection { + if ( !bool ) then(this) + return this + } + + where(key: KeyOperator, operator: WhereOperator, operand?: any): Collection { + const items = this._all_associate(key) + return new Collection(applyWhere(items, operator, operand)) + } + + whereNot(key: KeyOperator, operator: WhereOperator, operand?: any): Collection { + return this.diff(this.where(key, operator, operand)) + } + + whereIn(key: KeyOperator, items: CollectionComparable): Collection { + 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(key: KeyOperator, items: CollectionComparable): Collection { + return this.diff(this.whereIn(key, items)) + } + + first(): MaybeCollectionItem { + if ( this.length > 0 ) return this._items[0] + } + + firstWhere(key: KeyOperator, operator: WhereOperator = '=', operand: any = true): MaybeCollectionItem { + const items = this.where(key, operator, operand).all() + if ( items.length > 0 ) return items[0] + } + + firstWhereNot(key: KeyOperator, operator: WhereOperator, operand?: any): MaybeCollectionItem { + 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 { + return this.get(index) + } + + groupBy(key: KeyOperator): 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(key: KeyOperator): 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, key?: KeyOperator): Collection { + 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 { + if ( this.length > 0 ) return this._items.reverse()[0] + } + + lastWhere(key: KeyOperator, operator: WhereOperator, operand?: any): MaybeCollectionItem { + const items = this.where(key, operator, operand).all() + if ( items.length > 0 ) return items.reverse()[0] + } + + lastWhereNot(key: KeyOperator, operator: WhereOperator, operand?: any): MaybeCollectionItem { + const items = this.whereNot(key, operator, operand).all() + if ( items.length > 0 ) return items.reverse()[0] + } + + pluck(key: KeyOperator): Collection { + return new Collection(this._all(key)) + } + + max(key: KeyOperator): number { + const values = this._all_numbers(key) + return Math.max(...values) + } + + whereMax(key: KeyOperator): Collection { + return this.where(key, '=', this.max(key)) + } + + min(key: KeyOperator): number { + const values = this._all_numbers(key) + return Math.min(...values) + } + + whereMin(key: KeyOperator): Collection { + return this.where(key, '=', this.min(key)) + } + + merge(items: CollectionComparable): Collection { + const merge = items instanceof Collection ? items.all() : items + return new Collection([...this._items, ...merge]) + } + + nth(n: number): Collection { + const matches: CollectionItem[] = [] + 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(func: CollectionFunction): any { + return func(this) + } + + pop(): MaybeCollectionItem { + if ( this.length > 0 ) { + return this._items.pop() + } + } + + prepend(item: CollectionItem): Collection { + this._items = [item, ...this._items] + return this + } + + push(item: CollectionItem): Collection { + this._items.push(item) + return this + } + + concat(items: CollectionComparable): Collection { + 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 { + 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): Collection { + 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 { + const random_items: CollectionItem[] = [] + 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(reducer: KeyReducerFunction, 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(truth_test: KeyFunction): Collection { + const rejected = this._items.filter((item, index) => { + return !truth_test(item, index) + }) + return new Collection(rejected) + } + + reverse(): Collection { + return new Collection([...this._items.reverse()]) + } + + search(item: CollectionItem): MaybeCollectionIndex { + let found_index + this._items.some((possible_item, index) => { + if ( possible_item === item ) { + found_index = index + return true + } + }) + return found_index + } + + shift(): MaybeCollectionItem { + if ( this.length > 0 ) { + return this._items.shift() + } + } + + shuffle(): Collection { + 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): Collection { + const items = this._items + if ( compare_func ) items.sort(compare_func) + else items.sort() + return new Collection(items) + } + + sortBy(key?: KeyOperator): Collection { + 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) => item.item)) + } + + sortDesc(compare_func?: ComparisonFunction): Collection { + return this.sort(compare_func).reverse() + } + + sortByDesc(key?: KeyOperator): Collection { + return this.sortBy(key).reverse() + } + + splice(start: CollectionIndex, deleteCount?: number): Collection { + return new Collection([...this._items].splice(start, deleteCount)) + } + + sum(key?: KeyOperator): 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 { + 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(func: CollectionFunction): Collection { + func(this) + return this + } + + unique(key?: KeyOperator): Collection { + const has: CollectionItem[] = [] + let items + if ( key ) items = this._all(key) + else items = [...this._items] + for ( const item of items ) { + if ( !has.includes(item) ) has.push(item) + } + return new Collection(has) + } + + includes(item: CollectionItem): boolean { + return this._items.includes(item) + } + + pad(length: number, value: CollectionItem): Collection { + 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 } + }, + } + } +} diff --git a/lib/src/collection/Where.ts b/lib/src/collection/Where.ts new file mode 100755 index 0000000..5d259ad --- /dev/null +++ b/lib/src/collection/Where.ts @@ -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 } diff --git a/lib/src/external/reflect.ts b/lib/src/external/reflect.ts new file mode 100644 index 0000000..67b8b0c --- /dev/null +++ b/lib/src/external/reflect.ts @@ -0,0 +1,2155 @@ +type ObjectKeyAny = { [key: string]: any }; + +/*! ***************************************************************************** +Copyright (C) Microsoft. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +export namespace Reflect { + // Metadata Proposal + // https://rbuckton.github.io/reflect-metadata/ + + type HashMap = Record; + + interface BufferLike { + [offset: number]: number; + length: number; + } + + type IteratorResult = { value: T; done: false } | { + value: never; + done: true; + }; + + interface Iterator { + next(value?: any): IteratorResult; + throw?(value: any): IteratorResult; + return?(value?: T): IteratorResult; + } + + interface Iterable { + "@@iterator"(): Iterator; + } + + interface IterableIterator extends Iterator { + "@@iterator"(): IterableIterator; + } + + interface Map extends Iterable<[K, V]> { + size: number; + has(key: K): boolean; + get(key: K): V; + set(key: K, value?: V): this; + delete(key: K): boolean; + clear(): void; + keys(): IterableIterator; + values(): IterableIterator; + entries(): IterableIterator<[K, V]>; + } + + interface MapConstructor { + new (): Map; + new (): Map; + prototype: Map; + } + + interface Set extends Iterable { + size: number; + has(value: T): boolean; + add(value: T): this; + delete(value: T): boolean; + clear(): void; + keys(): IterableIterator; + values(): IterableIterator; + entries(): IterableIterator<[T, T]>; + } + + interface SetConstructor { + new (): Set; + new (): Set; + prototype: Set; + } + + interface WeakMap { + clear(): void; + delete(key: K): boolean; + get(key: K): V; + has(key: K): boolean; + set(key: K, value?: V): WeakMap; + } + + interface WeakMapConstructor { + new (): WeakMap; + new (): WeakMap; + prototype: WeakMap; + } + + type MemberDecorator = ( + target: Object, + propertyKey: string | symbol, + descriptor?: TypedPropertyDescriptor, + ) => TypedPropertyDescriptor | void; + declare const Symbol: { iterator: symbol; toPrimitive: symbol }; + declare const Set: SetConstructor; + declare const WeakMap: WeakMapConstructor; + declare const Map: MapConstructor; + declare const global: any; + declare const crypto: Crypto; + declare const msCrypto: Crypto; + declare const process: any; + + /** + * Applies a set of decorators to a target object. + * @param decorators An array of decorators. + * @param target The target object. + * @returns The result of applying the provided decorators. + * @remarks Decorators are applied in reverse order of their positions in the array. + * @example + * + * class Example { } + * + * // constructor + * Example = Reflect.decorate(decoratorsArray, Example); + * + */ + export declare function decorate( + decorators: ClassDecorator[], + target: Function, + ): Function; + + /** + * Applies a set of decorators to a property of a target object. + * @param decorators An array of decorators. + * @param target The target object. + * @param propertyKey The property key to decorate. + * @param attributes A property descriptor. + * @remarks Decorators are applied in reverse order. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod() { } + * method() { } + * } + * + * // property (on constructor) + * Reflect.decorate(decoratorsArray, Example, "staticProperty"); + * + * // property (on prototype) + * Reflect.decorate(decoratorsArray, Example.prototype, "property"); + * + * // method (on constructor) + * Object.defineProperty(Example, "staticMethod", + * Reflect.decorate(decoratorsArray, Example, "staticMethod", + * Object.getOwnPropertyDescriptor(Example, "staticMethod"))); + * + * // method (on prototype) + * Object.defineProperty(Example.prototype, "method", + * Reflect.decorate(decoratorsArray, Example.prototype, "method", + * Object.getOwnPropertyDescriptor(Example.prototype, "method"))); + * + */ + export declare function decorate( + decorators: (PropertyDecorator | MethodDecorator)[], + target: any, + propertyKey: string | symbol, + attributes?: PropertyDescriptor | null, + ): PropertyDescriptor | undefined; + + /** + * Applies a set of decorators to a property of a target object. + * @param decorators An array of decorators. + * @param target The target object. + * @param propertyKey The property key to decorate. + * @param attributes A property descriptor. + * @remarks Decorators are applied in reverse order. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod() { } + * method() { } + * } + * + * // property (on constructor) + * Reflect.decorate(decoratorsArray, Example, "staticProperty"); + * + * // property (on prototype) + * Reflect.decorate(decoratorsArray, Example.prototype, "property"); + * + * // method (on constructor) + * Object.defineProperty(Example, "staticMethod", + * Reflect.decorate(decoratorsArray, Example, "staticMethod", + * Object.getOwnPropertyDescriptor(Example, "staticMethod"))); + * + * // method (on prototype) + * Object.defineProperty(Example.prototype, "method", + * Reflect.decorate(decoratorsArray, Example.prototype, "method", + * Object.getOwnPropertyDescriptor(Example.prototype, "method"))); + * + */ + export declare function decorate( + decorators: (PropertyDecorator | MethodDecorator)[], + target: any, + propertyKey: string | symbol, + attributes: PropertyDescriptor, + ): PropertyDescriptor; + + /** + * A default metadata decorator factory that can be used on a class, class member, or parameter. + * @param metadataKey The key for the metadata entry. + * @param metadataValue The value for the metadata entry. + * @returns A decorator function. + * @remarks + * If `metadataKey` is already defined for the target and target key, the + * metadataValue for that key will be overwritten. + * @example + * + * // constructor + * @Reflect.metadata(key, value) + * class Example { + * } + * + * // property (on constructor, TypeScript only) + * class Example { + * @Reflect.metadata(key, value) + * static staticProperty; + * } + * + * // property (on prototype, TypeScript only) + * class Example { + * @Reflect.metadata(key, value) + * property; + * } + * + * // method (on constructor) + * class Example { + * @Reflect.metadata(key, value) + * static staticMethod() { } + * } + * + * // method (on prototype) + * class Example { + * @Reflect.metadata(key, value) + * method() { } + * } + * + */ + export declare function metadata( + metadataKey: any, + metadataValue: any, + ): { + (target: Function): void; + (target: any, propertyKey: string | symbol): void; + }; + + /** + * Define a unique metadata entry on the target. + * @param metadataKey A key used to store and retrieve metadata. + * @param metadataValue A value that contains attached metadata. + * @param target The target object on which to define metadata. + * @example + * + * class Example { + * } + * + * // constructor + * Reflect.defineMetadata("custom:annotation", options, Example); + * + * // decorator factory as metadata-producing annotation. + * function MyAnnotation(options): ClassDecorator { + * return target => Reflect.defineMetadata("custom:annotation", options, target); + * } + * + */ + export declare function defineMetadata( + metadataKey: any, + metadataValue: any, + target: any, + ): void; + + /** + * Define a unique metadata entry on the target. + * @param metadataKey A key used to store and retrieve metadata. + * @param metadataValue A value that contains attached metadata. + * @param target The target object on which to define metadata. + * @param propertyKey The property key for the target. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod(p) { } + * method(p) { } + * } + * + * // property (on constructor) + * Reflect.defineMetadata("custom:annotation", Number, Example, "staticProperty"); + * + * // property (on prototype) + * Reflect.defineMetadata("custom:annotation", Number, Example.prototype, "property"); + * + * // method (on constructor) + * Reflect.defineMetadata("custom:annotation", Number, Example, "staticMethod"); + * + * // method (on prototype) + * Reflect.defineMetadata("custom:annotation", Number, Example.prototype, "method"); + * + * // decorator factory as metadata-producing annotation. + * function MyAnnotation(options): PropertyDecorator { + * return (target, key) => Reflect.defineMetadata("custom:annotation", options, target, key); + * } + * + */ + export declare function defineMetadata( + metadataKey: any, + metadataValue: any, + target: any, + propertyKey: string | symbol, + ): void; + + /** + * Gets a value indicating whether the target object or its prototype chain has the provided metadata key defined. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @returns `true` if the metadata key was defined on the target object or its prototype chain; otherwise, `false`. + * @example + * + * class Example { + * } + * + * // constructor + * result = Reflect.hasMetadata("custom:annotation", Example); + * + */ + export declare function hasMetadata(metadataKey: any, target: any): boolean; + + /** + * Gets a value indicating whether the target object or its prototype chain has the provided metadata key defined. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey The property key for the target. + * @returns `true` if the metadata key was defined on the target object or its prototype chain; otherwise, `false`. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod(p) { } + * method(p) { } + * } + * + * // property (on constructor) + * result = Reflect.hasMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.hasMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.hasMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.hasMetadata("custom:annotation", Example.prototype, "method"); + * + */ + export declare function hasMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): boolean; + + /** + * Gets a value indicating whether the target object has the provided metadata key defined. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @returns `true` if the metadata key was defined on the target object; otherwise, `false`. + * @example + * + * class Example { + * } + * + * // constructor + * result = Reflect.hasOwnMetadata("custom:annotation", Example); + * + */ + export declare function hasOwnMetadata( + metadataKey: any, + target: any, + ): boolean; + + /** + * Gets a value indicating whether the target object has the provided metadata key defined. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey The property key for the target. + * @returns `true` if the metadata key was defined on the target object; otherwise, `false`. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod(p) { } + * method(p) { } + * } + * + * // property (on constructor) + * result = Reflect.hasOwnMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.hasOwnMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.hasOwnMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.hasOwnMetadata("custom:annotation", Example.prototype, "method"); + * + */ + export declare function hasOwnMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): boolean; + + /** + * Gets the metadata value for the provided metadata key on the target object or its prototype chain. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @returns The metadata value for the metadata key if found; otherwise, `undefined`. + * @example + * + * class Example { + * } + * + * // constructor + * result = Reflect.getMetadata("custom:annotation", Example); + * + */ + export declare function getMetadata(metadataKey: any, target: any): any; + + /** + * Gets the metadata value for the provided metadata key on the target object or its prototype chain. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey The property key for the target. + * @returns The metadata value for the metadata key if found; otherwise, `undefined`. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod(p) { } + * method(p) { } + * } + * + * // property (on constructor) + * result = Reflect.getMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.getMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.getMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.getMetadata("custom:annotation", Example.prototype, "method"); + * + */ + export declare function getMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): any; + + /** + * Gets the metadata value for the provided metadata key on the target object. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @returns The metadata value for the metadata key if found; otherwise, `undefined`. + * @example + * + * class Example { + * } + * + * // constructor + * result = Reflect.getOwnMetadata("custom:annotation", Example); + * + */ + export declare function getOwnMetadata(metadataKey: any, target: any): any; + + /** + * Gets the metadata value for the provided metadata key on the target object. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey The property key for the target. + * @returns The metadata value for the metadata key if found; otherwise, `undefined`. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod(p) { } + * method(p) { } + * } + * + * // property (on constructor) + * result = Reflect.getOwnMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.getOwnMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.getOwnMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.getOwnMetadata("custom:annotation", Example.prototype, "method"); + * + */ + export declare function getOwnMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): any; + + /** + * Gets the metadata keys defined on the target object or its prototype chain. + * @param target The target object on which the metadata is defined. + * @returns An array of unique metadata keys. + * @example + * + * class Example { + * } + * + * // constructor + * result = Reflect.getMetadataKeys(Example); + * + */ + export declare function getMetadataKeys(target: any): any[]; + + /** + * Gets the metadata keys defined on the target object or its prototype chain. + * @param target The target object on which the metadata is defined. + * @param propertyKey The property key for the target. + * @returns An array of unique metadata keys. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod(p) { } + * method(p) { } + * } + * + * // property (on constructor) + * result = Reflect.getMetadataKeys(Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.getMetadataKeys(Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.getMetadataKeys(Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.getMetadataKeys(Example.prototype, "method"); + * + */ + export declare function getMetadataKeys( + target: any, + propertyKey: string | symbol, + ): any[]; + + /** + * Gets the unique metadata keys defined on the target object. + * @param target The target object on which the metadata is defined. + * @returns An array of unique metadata keys. + * @example + * + * class Example { + * } + * + * // constructor + * result = Reflect.getOwnMetadataKeys(Example); + * + */ + export declare function getOwnMetadataKeys(target: any): any[]; + + /** + * Gets the unique metadata keys defined on the target object. + * @param target The target object on which the metadata is defined. + * @param propertyKey The property key for the target. + * @returns An array of unique metadata keys. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod(p) { } + * method(p) { } + * } + * + * // property (on constructor) + * result = Reflect.getOwnMetadataKeys(Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.getOwnMetadataKeys(Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.getOwnMetadataKeys(Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.getOwnMetadataKeys(Example.prototype, "method"); + * + */ + export declare function getOwnMetadataKeys( + target: any, + propertyKey: string | symbol, + ): any[]; + + /** + * Deletes the metadata entry from the target object with the provided key. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @returns `true` if the metadata entry was found and deleted; otherwise, false. + * @example + * + * class Example { + * } + * + * // constructor + * result = Reflect.deleteMetadata("custom:annotation", Example); + * + */ + export declare function deleteMetadata( + metadataKey: any, + target: any, + ): boolean; + + /** + * Deletes the metadata entry from the target object with the provided key. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey The property key for the target. + * @returns `true` if the metadata entry was found and deleted; otherwise, false. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * static staticMethod(p) { } + * method(p) { } + * } + * + * // property (on constructor) + * result = Reflect.deleteMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.deleteMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.deleteMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.deleteMetadata("custom:annotation", Example.prototype, "method"); + * + */ + export declare function deleteMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): boolean; + declare var self: ObjectKeyAny; + (function ( + this: any, + factory: ( + exporter: ( + key: K, + value: typeof Reflect[K], + ) => void, + ) => void, + ) { + const root = typeof global === "object" + ? global + : typeof self === "object" + ? self + : typeof this === "object" + ? this + : Function("return this;")(); + + let exporter = makeExporter(Reflect); + if (typeof root.Reflect === "undefined") { + root.Reflect = Reflect; + } else { + exporter = makeExporter(root.Reflect, exporter); + } + + factory(exporter); + + function makeExporter( + target: typeof Reflect, + previous?: ( + key: K, + value: typeof Reflect[K], + ) => void, + ) { + return ( + key: K, + value: typeof Reflect[K], + ) => { + if (typeof target[key] !== "function") { + Object.defineProperty( + target, + key, + { configurable: true, writable: true, value }, + ); + } + if (previous) previous(key, value); + }; + } + })(function (exporter) { + const hasOwn = Object.prototype.hasOwnProperty; + + // feature test for Symbol support + const supportsSymbol = typeof Symbol === "function"; + const toPrimitiveSymbol = + supportsSymbol && typeof Symbol.toPrimitive !== "undefined" + ? Symbol.toPrimitive + : "@@toPrimitive"; + const iteratorSymbol = + supportsSymbol && typeof Symbol.iterator !== "undefined" + ? Symbol.iterator + : "@@iterator"; + const supportsCreate = typeof Object.create === "function"; // feature test for Object.create support + const supportsProto = { __proto__: [] } instanceof Array; // feature test for __proto__ support + const downLevel = !supportsCreate && !supportsProto; + + const HashMap = { + // create an object in dictionary mode (a.k.a. "slow" mode in v8) + create: supportsCreate + ? () => MakeDictionary(Object.create(null) as HashMap) + : supportsProto + ? () => MakeDictionary({ __proto__: null as any } as HashMap) + : () => MakeDictionary({} as HashMap), + + has: downLevel + ? (map: HashMap, key: string | number | symbol) => + hasOwn.call(map, key) + : (map: HashMap, key: string | number | symbol) => key in map, + + get: downLevel + ? (map: HashMap, key: string | number | symbol): V | undefined => + hasOwn.call(map, key) ? map[key as string | number] : undefined + : (map: HashMap, key: string | number | symbol): V | undefined => + map[key as string | number], + }; + + // Load global or shim versions of Map, Set, and WeakMap + const functionPrototype = Object.getPrototypeOf(Function); + const usePolyfill = typeof process === "object" && process.env && + process.env["REFLECT_METADATA_USE_MAP_POLYFILL"] === "true"; + const _Map: typeof Map = !usePolyfill && typeof Map === "function" && + typeof Map.prototype.entries === "function" + ? Map + : CreateMapPolyfill(); + const _Set: typeof Set = !usePolyfill && typeof Set === "function" && + typeof Set.prototype.entries === "function" + ? Set + : CreateSetPolyfill(); + const _WeakMap: typeof WeakMap = + !usePolyfill && typeof WeakMap === "function" + ? WeakMap + : CreateWeakMapPolyfill(); + + // [[Metadata]] internal slot + // https://rbuckton.github.io/reflect-metadata/#ordinary-object-internal-methods-and-internal-slots + const Metadata = new _WeakMap< + any, + Map> + >(); + + // function decorate(decorators: ClassDecorator[], target: Function): Function; + // function decorate(decorators: (PropertyDecorator | MethodDecorator)[], target: any, propertyKey: string | symbol, attributes?: PropertyDescriptor | null): PropertyDescriptor | undefined; + // function decorate(decorators: (PropertyDecorator | MethodDecorator)[], target: any, propertyKey: string | symbol, attributes: PropertyDescriptor): PropertyDescriptor; + + /** + * Applies a set of decorators to a property of a target object. + * @param decorators An array of decorators. + * @param target The target object. + * @param propertyKey (Optional) The property key to decorate. + * @param attributes (Optional) The property descriptor for the target key. + * @remarks Decorators are applied in reverse order. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * Example = Reflect.decorate(decoratorsArray, Example); + * + * // property (on constructor) + * Reflect.decorate(decoratorsArray, Example, "staticProperty"); + * + * // property (on prototype) + * Reflect.decorate(decoratorsArray, Example.prototype, "property"); + * + * // method (on constructor) + * Object.defineProperty(Example, "staticMethod", + * Reflect.decorate(decoratorsArray, Example, "staticMethod", + * Object.getOwnPropertyDescriptor(Example, "staticMethod"))); + * + * // method (on prototype) + * Object.defineProperty(Example.prototype, "method", + * Reflect.decorate(decoratorsArray, Example.prototype, "method", + * Object.getOwnPropertyDescriptor(Example.prototype, "method"))); + * + */ + function decorate( + decorators: (ClassDecorator | MemberDecorator)[], + target: any, + propertyKey?: string | symbol, + attributes?: PropertyDescriptor | null, + ): PropertyDescriptor | Function | undefined { + if (!IsUndefined(propertyKey)) { + if (!IsArray(decorators)) throw new TypeError(); + if (!IsObject(target)) throw new TypeError(); + if ( + !IsObject(attributes) && !IsUndefined(attributes) && + !IsNull(attributes) + ) { + throw new TypeError(); + } + if (IsNull(attributes)) attributes = undefined; + propertyKey = ToPropertyKey(propertyKey); + return DecorateProperty( + decorators, + target, + propertyKey, + attributes, + ); + } else { + if (!IsArray(decorators)) throw new TypeError(); + if (!IsConstructor(target)) throw new TypeError(); + return DecorateConstructor( + decorators, + target, + ); + } + } + + exporter("decorate", decorate); + + // 4.1.2 Reflect.metadata(metadataKey, metadataValue) + // https://rbuckton.github.io/reflect-metadata/#reflect.metadata + + /** + * A default metadata decorator factory that can be used on a class, class member, or parameter. + * @param metadataKey The key for the metadata entry. + * @param metadataValue The value for the metadata entry. + * @returns A decorator function. + * @remarks + * If `metadataKey` is already defined for the target and target key, the + * metadataValue for that key will be overwritten. + * @example + * + * // constructor + * @Reflect.metadata(key, value) + * class Example { + * } + * + * // property (on constructor, TypeScript only) + * class Example { + * @Reflect.metadata(key, value) + * static staticProperty; + * } + * + * // property (on prototype, TypeScript only) + * class Example { + * @Reflect.metadata(key, value) + * property; + * } + * + * // method (on constructor) + * class Example { + * @Reflect.metadata(key, value) + * static staticMethod() { } + * } + * + * // method (on prototype) + * class Example { + * @Reflect.metadata(key, value) + * method() { } + * } + * + */ + function metadata(metadataKey: any, metadataValue: any) { + function decorator(target: Function): void; + function decorator(target: any, propertyKey: string | symbol): void; + function decorator(target: any, propertyKey?: string | symbol): void { + if (!IsObject(target)) throw new TypeError(); + if ( + !IsUndefined(propertyKey) && !IsPropertyKey(propertyKey) + ) { + throw new TypeError(); + } + OrdinaryDefineOwnMetadata( + metadataKey, + metadataValue, + target, + propertyKey, + ); + } + return decorator; + } + + exporter("metadata", metadata); + + // 4.1.3 Reflect.defineMetadata(metadataKey, metadataValue, target [, propertyKey]) + // https://rbuckton.github.io/reflect-metadata/#reflect.definemetadata + + function defineMetadata( + metadataKey: any, + metadataValue: any, + target: any, + ): void; + function defineMetadata( + metadataKey: any, + metadataValue: any, + target: any, + propertyKey: string | symbol, + ): void; + + /** + * Define a unique metadata entry on the target. + * @param metadataKey A key used to store and retrieve metadata. + * @param metadataValue A value that contains attached metadata. + * @param target The target object on which to define metadata. + * @param propertyKey (Optional) The property key for the target. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * Reflect.defineMetadata("custom:annotation", options, Example); + * + * // property (on constructor) + * Reflect.defineMetadata("custom:annotation", options, Example, "staticProperty"); + * + * // property (on prototype) + * Reflect.defineMetadata("custom:annotation", options, Example.prototype, "property"); + * + * // method (on constructor) + * Reflect.defineMetadata("custom:annotation", options, Example, "staticMethod"); + * + * // method (on prototype) + * Reflect.defineMetadata("custom:annotation", options, Example.prototype, "method"); + * + * // decorator factory as metadata-producing annotation. + * function MyAnnotation(options): Decorator { + * return (target, key?) => Reflect.defineMetadata("custom:annotation", options, target, key); + * } + * + */ + function defineMetadata( + metadataKey: any, + metadataValue: any, + target: any, + propertyKey?: string | symbol, + ): void { + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + return OrdinaryDefineOwnMetadata( + metadataKey, + metadataValue, + target, + propertyKey, + ); + } + + exporter("defineMetadata", defineMetadata); + + // 4.1.4 Reflect.hasMetadata(metadataKey, target [, propertyKey]) + // https://rbuckton.github.io/reflect-metadata/#reflect.hasmetadata + + function hasMetadata(metadataKey: any, target: any): boolean; + function hasMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): boolean; + + /** + * Gets a value indicating whether the target object or its prototype chain has the provided metadata key defined. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey (Optional) The property key for the target. + * @returns `true` if the metadata key was defined on the target object or its prototype chain; otherwise, `false`. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * result = Reflect.hasMetadata("custom:annotation", Example); + * + * // property (on constructor) + * result = Reflect.hasMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.hasMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.hasMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.hasMetadata("custom:annotation", Example.prototype, "method"); + * + */ + function hasMetadata( + metadataKey: any, + target: any, + propertyKey?: string | symbol, + ): boolean { + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + return OrdinaryHasMetadata(metadataKey, target, propertyKey); + } + + exporter("hasMetadata", hasMetadata); + + // 4.1.5 Reflect.hasOwnMetadata(metadataKey, target [, propertyKey]) + // https://rbuckton.github.io/reflect-metadata/#reflect-hasownmetadata + + function hasOwnMetadata(metadataKey: any, target: any): boolean; + function hasOwnMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): boolean; + + /** + * Gets a value indicating whether the target object has the provided metadata key defined. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey (Optional) The property key for the target. + * @returns `true` if the metadata key was defined on the target object; otherwise, `false`. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * result = Reflect.hasOwnMetadata("custom:annotation", Example); + * + * // property (on constructor) + * result = Reflect.hasOwnMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.hasOwnMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.hasOwnMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.hasOwnMetadata("custom:annotation", Example.prototype, "method"); + * + */ + function hasOwnMetadata( + metadataKey: any, + target: any, + propertyKey?: string | symbol, + ): boolean { + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + return OrdinaryHasOwnMetadata(metadataKey, target, propertyKey); + } + + exporter("hasOwnMetadata", hasOwnMetadata); + + // 4.1.6 Reflect.getMetadata(metadataKey, target [, propertyKey]) + // https://rbuckton.github.io/reflect-metadata/#reflect-getmetadata + + function getMetadata(metadataKey: any, target: any): any; + function getMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): any; + + /** + * Gets the metadata value for the provided metadata key on the target object or its prototype chain. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey (Optional) The property key for the target. + * @returns The metadata value for the metadata key if found; otherwise, `undefined`. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * result = Reflect.getMetadata("custom:annotation", Example); + * + * // property (on constructor) + * result = Reflect.getMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.getMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.getMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.getMetadata("custom:annotation", Example.prototype, "method"); + * + */ + function getMetadata( + metadataKey: any, + target: any, + propertyKey?: string | symbol, + ): any { + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + return OrdinaryGetMetadata(metadataKey, target, propertyKey); + } + + exporter("getMetadata", getMetadata); + + // 4.1.7 Reflect.getOwnMetadata(metadataKey, target [, propertyKey]) + // https://rbuckton.github.io/reflect-metadata/#reflect-getownmetadata + + function getOwnMetadata(metadataKey: any, target: any): any; + function getOwnMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): any; + + /** + * Gets the metadata value for the provided metadata key on the target object. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey (Optional) The property key for the target. + * @returns The metadata value for the metadata key if found; otherwise, `undefined`. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * result = Reflect.getOwnMetadata("custom:annotation", Example); + * + * // property (on constructor) + * result = Reflect.getOwnMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.getOwnMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.getOwnMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.getOwnMetadata("custom:annotation", Example.prototype, "method"); + * + */ + function getOwnMetadata( + metadataKey: any, + target: any, + propertyKey?: string | symbol, + ): any { + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + return OrdinaryGetOwnMetadata(metadataKey, target, propertyKey); + } + + exporter("getOwnMetadata", getOwnMetadata); + + // 4.1.8 Reflect.getMetadataKeys(target [, propertyKey]) + // https://rbuckton.github.io/reflect-metadata/#reflect-getmetadatakeys + + function getMetadataKeys(target: any): any[]; + function getMetadataKeys(target: any, propertyKey: string | symbol): any[]; + + /** + * Gets the metadata keys defined on the target object or its prototype chain. + * @param target The target object on which the metadata is defined. + * @param propertyKey (Optional) The property key for the target. + * @returns An array of unique metadata keys. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * result = Reflect.getMetadataKeys(Example); + * + * // property (on constructor) + * result = Reflect.getMetadataKeys(Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.getMetadataKeys(Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.getMetadataKeys(Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.getMetadataKeys(Example.prototype, "method"); + * + */ + function getMetadataKeys( + target: any, + propertyKey?: string | symbol, + ): any[] { + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + return OrdinaryMetadataKeys(target, propertyKey); + } + + exporter("getMetadataKeys", getMetadataKeys); + + // 4.1.9 Reflect.getOwnMetadataKeys(target [, propertyKey]) + // https://rbuckton.github.io/reflect-metadata/#reflect-getownmetadata + + function getOwnMetadataKeys(target: any): any[]; + function getOwnMetadataKeys( + target: any, + propertyKey: string | symbol, + ): any[]; + + /** + * Gets the unique metadata keys defined on the target object. + * @param target The target object on which the metadata is defined. + * @param propertyKey (Optional) The property key for the target. + * @returns An array of unique metadata keys. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * result = Reflect.getOwnMetadataKeys(Example); + * + * // property (on constructor) + * result = Reflect.getOwnMetadataKeys(Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.getOwnMetadataKeys(Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.getOwnMetadataKeys(Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.getOwnMetadataKeys(Example.prototype, "method"); + * + */ + function getOwnMetadataKeys( + target: any, + propertyKey?: string | symbol, + ): any[] { + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + return OrdinaryOwnMetadataKeys(target, propertyKey); + } + + exporter("getOwnMetadataKeys", getOwnMetadataKeys); + + // 4.1.10 Reflect.deleteMetadata(metadataKey, target [, propertyKey]) + // https://rbuckton.github.io/reflect-metadata/#reflect-deletemetadata + + function deleteMetadata(metadataKey: any, target: any): boolean; + function deleteMetadata( + metadataKey: any, + target: any, + propertyKey: string | symbol, + ): boolean; + + /** + * Deletes the metadata entry from the target object with the provided key. + * @param metadataKey A key used to store and retrieve metadata. + * @param target The target object on which the metadata is defined. + * @param propertyKey (Optional) The property key for the target. + * @returns `true` if the metadata entry was found and deleted; otherwise, false. + * @example + * + * class Example { + * // property declarations are not part of ES6, though they are valid in TypeScript: + * // static staticProperty; + * // property; + * + * constructor(p) { } + * static staticMethod(p) { } + * method(p) { } + * } + * + * // constructor + * result = Reflect.deleteMetadata("custom:annotation", Example); + * + * // property (on constructor) + * result = Reflect.deleteMetadata("custom:annotation", Example, "staticProperty"); + * + * // property (on prototype) + * result = Reflect.deleteMetadata("custom:annotation", Example.prototype, "property"); + * + * // method (on constructor) + * result = Reflect.deleteMetadata("custom:annotation", Example, "staticMethod"); + * + * // method (on prototype) + * result = Reflect.deleteMetadata("custom:annotation", Example.prototype, "method"); + * + */ + function deleteMetadata( + metadataKey: any, + target: any, + propertyKey?: string | symbol, + ): boolean { + if (!IsObject(target)) throw new TypeError(); + if (!IsUndefined(propertyKey)) propertyKey = ToPropertyKey(propertyKey); + const metadataMap = GetOrCreateMetadataMap( + target, + propertyKey, /*Create*/ + false, + ); + if (IsUndefined(metadataMap)) return false; + if (!metadataMap.delete(metadataKey)) return false; + if (metadataMap.size > 0) return true; + const targetMetadata = Metadata.get(target); + targetMetadata.delete(propertyKey); + if (targetMetadata.size > 0) return true; + Metadata.delete(target); + return true; + } + + exporter("deleteMetadata", deleteMetadata); + + function DecorateConstructor( + decorators: ClassDecorator[], + target: Function, + ): Function { + for (let i = decorators.length - 1; i >= 0; --i) { + const decorator = decorators[i]; + const decorated = decorator(target); + if (!IsUndefined(decorated) && !IsNull(decorated)) { + if (!IsConstructor(decorated)) throw new TypeError(); + target = decorated; + } + } + return target; + } + + function DecorateProperty( + decorators: MemberDecorator[], + target: any, + propertyKey: string | symbol, + descriptor: PropertyDescriptor | undefined, + ): PropertyDescriptor | undefined { + for (let i = decorators.length - 1; i >= 0; --i) { + const decorator = decorators[i]; + const decorated = decorator(target, propertyKey, descriptor); + if (!IsUndefined(decorated) && !IsNull(decorated)) { + if (!IsObject(decorated)) throw new TypeError(); + descriptor = decorated; + } + } + return descriptor; + } + + // 2.1.1 GetOrCreateMetadataMap(O, P, Create) + // https://rbuckton.github.io/reflect-metadata/#getorcreatemetadatamap + function GetOrCreateMetadataMap( + O: any, + P: string | symbol | undefined, + Create: true, + ): Map; + function GetOrCreateMetadataMap( + O: any, + P: string | symbol | undefined, + Create: false, + ): Map | undefined; + function GetOrCreateMetadataMap( + O: any, + P: string | symbol | undefined, + Create: boolean, + ): Map | undefined { + let targetMetadata = Metadata.get(O); + if (IsUndefined(targetMetadata)) { + if (!Create) return undefined; + targetMetadata = new _Map>(); + Metadata.set(O, targetMetadata); + } + let metadataMap = targetMetadata.get(P); + if (IsUndefined(metadataMap)) { + if (!Create) return undefined; + metadataMap = new _Map(); + targetMetadata.set(P, metadataMap); + } + return metadataMap; + } + + // 3.1.1.1 OrdinaryHasMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryhasmetadata + function OrdinaryHasMetadata( + MetadataKey: any, + O: any, + P: string | symbol | undefined, + ): boolean { + const hasOwn = OrdinaryHasOwnMetadata(MetadataKey, O, P); + if (hasOwn) return true; + const parent = OrdinaryGetPrototypeOf(O); + if (!IsNull(parent)) return OrdinaryHasMetadata(MetadataKey, parent, P); + return false; + } + + // 3.1.2.1 OrdinaryHasOwnMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryhasownmetadata + function OrdinaryHasOwnMetadata( + MetadataKey: any, + O: any, + P: string | symbol | undefined, + ): boolean { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return false; + return ToBoolean(metadataMap.has(MetadataKey)); + } + + // 3.1.3.1 OrdinaryGetMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarygetmetadata + function OrdinaryGetMetadata( + MetadataKey: any, + O: any, + P: string | symbol | undefined, + ): any { + const hasOwn = OrdinaryHasOwnMetadata(MetadataKey, O, P); + if (hasOwn) return OrdinaryGetOwnMetadata(MetadataKey, O, P); + const parent = OrdinaryGetPrototypeOf(O); + if (!IsNull(parent)) return OrdinaryGetMetadata(MetadataKey, parent, P); + return undefined; + } + + // 3.1.4.1 OrdinaryGetOwnMetadata(MetadataKey, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarygetownmetadata + function OrdinaryGetOwnMetadata( + MetadataKey: any, + O: any, + P: string | symbol | undefined, + ): any { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return undefined; + return metadataMap.get(MetadataKey); + } + + // 3.1.5.1 OrdinaryDefineOwnMetadata(MetadataKey, MetadataValue, O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarydefineownmetadata + function OrdinaryDefineOwnMetadata( + MetadataKey: any, + MetadataValue: any, + O: any, + P: string | symbol | undefined, + ): void { + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ true); + metadataMap.set(MetadataKey, MetadataValue); + } + + // 3.1.6.1 OrdinaryMetadataKeys(O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinarymetadatakeys + function OrdinaryMetadataKeys( + O: any, + P: string | symbol | undefined, + ): any[] { + const ownKeys = OrdinaryOwnMetadataKeys(O, P); + const parent = OrdinaryGetPrototypeOf(O); + if (parent === null) return ownKeys; + const parentKeys = OrdinaryMetadataKeys(parent, P); + if (parentKeys.length <= 0) return ownKeys; + if (ownKeys.length <= 0) return parentKeys; + const set = new _Set(); + const keys: any[] = []; + for (const key of ownKeys) { + const hasKey = set.has(key); + if (!hasKey) { + set.add(key); + keys.push(key); + } + } + for (const key of parentKeys) { + const hasKey = set.has(key); + if (!hasKey) { + set.add(key); + keys.push(key); + } + } + return keys; + } + + // 3.1.7.1 OrdinaryOwnMetadataKeys(O, P) + // https://rbuckton.github.io/reflect-metadata/#ordinaryownmetadatakeys + function OrdinaryOwnMetadataKeys( + O: any, + P: string | symbol | undefined, + ): any[] { + const keys: any[] = []; + const metadataMap = GetOrCreateMetadataMap(O, P, /*Create*/ false); + if (IsUndefined(metadataMap)) return keys; + const keysObj = metadataMap.keys(); + const iterator = GetIterator(keysObj); + let k = 0; + while (true) { + const next = IteratorStep(iterator); + if (!next) { + keys.length = k; + return keys; + } + const nextValue = IteratorValue(next); + try { + keys[k] = nextValue; + } catch (e) { + try { + IteratorClose(iterator); + } finally { + throw e; + } + } + k++; + } + } + + // 6 ECMAScript Data Typ0es and Values + // https://tc39.github.io/ecma262/#sec-ecmascript-data-types-and-values + function Type(x: any): Tag { + if (x === null) return Tag.Null; + switch (typeof x) { + case "undefined": + return Tag.Undefined; + case "boolean": + return Tag.Boolean; + case "string": + return Tag.String; + case "symbol": + return Tag.Symbol; + case "number": + return Tag.Number; + case "object": + return x === null ? Tag.Null : Tag.Object; + default: + return Tag.Object; + } + } + + // 6.1 ECMAScript Language Types + // https://tc39.github.io/ecma262/#sec-ecmascript-language-types + const enum Tag { + Undefined, + Null, + Boolean, + String, + Symbol, + Number, + Object, + } + + // 6.1.1 The Undefined Type + // https://tc39.github.io/ecma262/#sec-ecmascript-language-types-undefined-type + function IsUndefined(x: any): x is undefined { + return x === undefined; + } + + // 6.1.2 The Null Type + // https://tc39.github.io/ecma262/#sec-ecmascript-language-types-null-type + function IsNull(x: any): x is null { + return x === null; + } + + // 6.1.5 The Symbol Type + // https://tc39.github.io/ecma262/#sec-ecmascript-language-types-symbol-type + function IsSymbol(x: any): x is symbol { + return typeof x === "symbol"; + } + + // 6.1.7 The Object Type + // https://tc39.github.io/ecma262/#sec-object-type + function IsObject( + x: T | undefined | null | boolean | string | symbol | number, + ): x is T { + return typeof x === "object" ? x !== null : typeof x === "function"; + } + + // 7.1 Type Conversion + // https://tc39.github.io/ecma262/#sec-type-conversion + + // 7.1.1 ToPrimitive(input [, PreferredType]) + // https://tc39.github.io/ecma262/#sec-toprimitive + function ToPrimitive( + input: any, + PreferredType?: Tag, + ): undefined | null | boolean | string | symbol | number { + switch (Type(input)) { + case Tag.Undefined: + return input; + case Tag.Null: + return input; + case Tag.Boolean: + return input; + case Tag.String: + return input; + case Tag.Symbol: + return input; + case Tag.Number: + return input; + } + const hint: "string" | "number" | "default" = PreferredType === Tag.String + ? "string" + : PreferredType === Tag.Number + ? "number" + : "default"; + const exoticToPrim = GetMethod(input, toPrimitiveSymbol); + if (exoticToPrim !== undefined) { + const result = exoticToPrim.call(input, hint); + if (IsObject(result)) throw new TypeError(); + return result; + } + return OrdinaryToPrimitive(input, hint === "default" ? "number" : hint); + } + + // 7.1.1.1 OrdinaryToPrimitive(O, hint) + // https://tc39.github.io/ecma262/#sec-ordinarytoprimitive + function OrdinaryToPrimitive( + O: any, + hint: "string" | "number", + ): undefined | null | boolean | string | symbol | number { + if (hint === "string") { + const toString = O.toString; + if (IsCallable(toString)) { + const result = toString.call(O); + if (!IsObject(result)) return result; + } + const valueOf = O.valueOf; + if (IsCallable(valueOf)) { + const result = valueOf.call(O); + if (!IsObject(result)) return result; + } + } else { + const valueOf = O.valueOf; + if (IsCallable(valueOf)) { + const result = valueOf.call(O); + if (!IsObject(result)) return result; + } + const toString = O.toString; + if (IsCallable(toString)) { + const result = toString.call(O); + if (!IsObject(result)) return result; + } + } + throw new TypeError(); + } + + // 7.1.2 ToBoolean(argument) + // https://tc39.github.io/ecma262/2016/#sec-toboolean + function ToBoolean(argument: any): boolean { + return !!argument; + } + + // 7.1.12 ToString(argument) + // https://tc39.github.io/ecma262/#sec-tostring + function ToString(argument: any): string { + return "" + argument; + } + + // 7.1.14 ToPropertyKey(argument) + // https://tc39.github.io/ecma262/#sec-topropertykey + function ToPropertyKey(argument: any): string | symbol { + const key = ToPrimitive(argument, Tag.String); + if (IsSymbol(key)) return key; + return ToString(key); + } + + // 7.2 Testing and Comparison Operations + // https://tc39.github.io/ecma262/#sec-testing-and-comparison-operations + + // 7.2.2 IsArray(argument) + // https://tc39.github.io/ecma262/#sec-isarray + function IsArray(argument: any): argument is any[] { + return Array.isArray + ? Array.isArray(argument) + : argument instanceof Object + ? argument instanceof Array + : Object.prototype.toString.call(argument) === "[object Array]"; + } + + // 7.2.3 IsCallable(argument) + // https://tc39.github.io/ecma262/#sec-iscallable + function IsCallable(argument: any): argument is Function { + // NOTE: This is an approximation as we cannot check for [[Call]] internal method. + return typeof argument === "function"; + } + + // 7.2.4 IsConstructor(argument) + // https://tc39.github.io/ecma262/#sec-isconstructor + function IsConstructor(argument: any): argument is Function { + // NOTE: This is an approximation as we cannot check for [[Construct]] internal method. + return typeof argument === "function"; + } + + // 7.2.7 IsPropertyKey(argument) + // https://tc39.github.io/ecma262/#sec-ispropertykey + function IsPropertyKey(argument: any): argument is string | symbol { + switch (Type(argument)) { + case Tag.String: + return true; + case Tag.Symbol: + return true; + default: + return false; + } + } + + // 7.3 Operations on Objects + // https://tc39.github.io/ecma262/#sec-operations-on-objects + + // 7.3.9 GetMethod(V, P) + // https://tc39.github.io/ecma262/#sec-getmethod + function GetMethod(V: any, P: any): Function | undefined { + const func = V[P]; + if (func === undefined || func === null) return undefined; + if (!IsCallable(func)) throw new TypeError(); + return func; + } + + // 7.4 Operations on Iterator Objects + // https://tc39.github.io/ecma262/#sec-operations-on-iterator-objects + + function GetIterator(obj: Iterable): Iterator { + const method = GetMethod(obj, iteratorSymbol); + if (!IsCallable(method)) throw new TypeError(); // from Call + const iterator = method.call(obj); + if (!IsObject(iterator)) throw new TypeError(); + return iterator; + } + + // 7.4.4 IteratorValue(iterResult) + // https://tc39.github.io/ecma262/2016/#sec-iteratorvalue + function IteratorValue(iterResult: IteratorResult): T { + return iterResult.value; + } + + // 7.4.5 IteratorStep(iterator) + // https://tc39.github.io/ecma262/#sec-iteratorstep + function IteratorStep(iterator: Iterator): IteratorResult | false { + const result = iterator.next(); + return result.done ? false : result; + } + + // 7.4.6 IteratorClose(iterator, completion) + // https://tc39.github.io/ecma262/#sec-iteratorclose + function IteratorClose(iterator: Iterator) { + const f = iterator["return"]; + if (f) f.call(iterator); + } + + // 9.1 Ordinary Object Internal Methods and Internal Slots + // https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots + + // 9.1.1.1 OrdinaryGetPrototypeOf(O) + // https://tc39.github.io/ecma262/#sec-ordinarygetprototypeof + function OrdinaryGetPrototypeOf(O: any): any { + const proto = Object.getPrototypeOf(O); + if (typeof O !== "function" || O === functionPrototype) return proto; + + // TypeScript doesn't set __proto__ in ES5, as it's non-standard. + // Try to determine the superclass constructor. Compatible implementations + // must either set __proto__ on a subclass constructor to the superclass constructor, + // or ensure each class has a valid `constructor` property on its prototype that + // points back to the constructor. + + // If this is not the same as Function.[[Prototype]], then this is definately inherited. + // This is the case when in ES6 or when using __proto__ in a compatible browser. + if (proto !== functionPrototype) return proto; + + // If the super prototype is Object.prototype, null, or undefined, then we cannot determine the heritage. + const prototype = O.prototype; + const prototypeProto = prototype && Object.getPrototypeOf(prototype); + if ( + prototypeProto == null || prototypeProto === Object.prototype + ) { + return proto; + } + + // If the constructor was not a function, then we cannot determine the heritage. + const constructor = prototypeProto.constructor; + if (typeof constructor !== "function") return proto; + + // If we have some kind of self-reference, then we cannot determine the heritage. + if (constructor === O) return proto; + + // we have a pretty good guess at the heritage. + return constructor; + } + + // naive Map shim + function CreateMapPolyfill(): MapConstructor { + const cacheSentinel = {}; + const arraySentinel: any[] = []; + + class MapIterator + implements IterableIterator { + private _keys: K[]; + private _values: V[]; + private _index = 0; + private _selector: (key: K, value: V) => R; + constructor(keys: K[], values: V[], selector: (key: K, value: V) => R) { + this._keys = keys; + this._values = values; + this._selector = selector; + } + "@@iterator"() { + return this; + } + [iteratorSymbol]() { + return this; + } + next(): IteratorResult { + const index = this._index; + if (index >= 0 && index < this._keys.length) { + const result = this._selector( + this._keys[index], + this._values[index], + ); + if (index + 1 >= this._keys.length) { + this._index = -1; + this._keys = arraySentinel; + this._values = arraySentinel; + } else { + this._index++; + } + return { value: result, done: false }; + } + return { value: undefined, done: true }; + } + throw(error: any): IteratorResult { + if (this._index >= 0) { + this._index = -1; + this._keys = arraySentinel; + this._values = arraySentinel; + } + throw error; + } + return(value?: R): IteratorResult { + if (this._index >= 0) { + this._index = -1; + this._keys = arraySentinel; + this._values = arraySentinel; + } + return { value: value, done: true }; + } + } + + return class Map { + private _keys: K[] = []; + private _values: (V | undefined)[] = []; + private _cacheKey = cacheSentinel; + private _cacheIndex = -2; + get size() { + return this._keys.length; + } + has(key: K): boolean { + return this._find(key, /*insert*/ false) >= 0; + } + get(key: K): V | undefined { + const index = this._find(key, /*insert*/ false); + return index >= 0 ? this._values[index] : undefined; + } + set(key: K, value: V): this { + const index = this._find(key, /*insert*/ true); + this._values[index] = value; + return this; + } + delete(key: K): boolean { + const index = this._find(key, /*insert*/ false); + if (index >= 0) { + const size = this._keys.length; + for (let i = index + 1; i < size; i++) { + this._keys[i - 1] = this._keys[i]; + this._values[i - 1] = this._values[i]; + } + this._keys.length--; + this._values.length--; + if (key === this._cacheKey) { + this._cacheKey = cacheSentinel; + this._cacheIndex = -2; + } + return true; + } + return false; + } + clear(): void { + this._keys.length = 0; + this._values.length = 0; + this._cacheKey = cacheSentinel; + this._cacheIndex = -2; + } + keys() { + return new MapIterator(this._keys, this._values, getKey); + } + values() { + return new MapIterator(this._keys, this._values, getValue); + } + entries() { + return new MapIterator(this._keys, this._values, getEntry); + } + "@@iterator"() { + return this.entries(); + } + [iteratorSymbol]() { + return this.entries(); + } + private _find(key: K, insert?: boolean): number { + if (this._cacheKey !== key) { + this._cacheIndex = this._keys.indexOf(this._cacheKey = key); + } + if (this._cacheIndex < 0 && insert) { + this._cacheIndex = this._keys.length; + this._keys.push(key); + this._values.push(undefined); + } + return this._cacheIndex; + } + }; + + function getKey(key: K, _: V) { + return key; + } + + function getValue(_: K, value: V) { + return value; + } + + function getEntry(key: K, value: V) { + return [key, value] as [K, V]; + } + } + + // naive Set shim + function CreateSetPolyfill(): SetConstructor { + return class Set { + private _map = new _Map(); + get size() { + return this._map.size; + } + has(value: T): boolean { + return this._map.has(value); + } + add(value: T): Set { + return this._map.set(value, value), this; + } + delete(value: T): boolean { + return this._map.delete(value); + } + clear(): void { + this._map.clear(); + } + keys() { + return this._map.keys(); + } + values() { + return this._map.values(); + } + entries() { + return this._map.entries(); + } + "@@iterator"() { + return this.keys(); + } + [iteratorSymbol]() { + return this.keys(); + } + }; + } + + // naive WeakMap shim + function CreateWeakMapPolyfill(): WeakMapConstructor { + const UUID_SIZE = 16; + const keys = HashMap.create(); + const rootKey = CreateUniqueKey(); + return class WeakMap { + private _key = CreateUniqueKey(); + has(target: K): boolean { + const table = GetOrCreateWeakMapTable(target, /*create*/ false); + return table !== undefined ? HashMap.has(table, this._key) : false; + } + get(target: K): V { + const table = GetOrCreateWeakMapTable(target, /*create*/ false); + return table !== undefined + ? HashMap.get(table, this._key) + : undefined; + } + set(target: K, value: V): WeakMap { + const table = GetOrCreateWeakMapTable(target, /*create*/ true); + table[this._key] = value; + return this; + } + delete(target: K): boolean { + const table = GetOrCreateWeakMapTable(target, /*create*/ false); + return table !== undefined ? delete table[this._key] : false; + } + clear(): void { + // NOTE: not a real clear, just makes the previous data unreachable + this._key = CreateUniqueKey(); + } + }; + + function CreateUniqueKey(): string { + let key: string; + do key = "@@WeakMap@@" + CreateUUID(); while (HashMap.has(keys, key)); + keys[key] = true; + return key; + } + + function GetOrCreateWeakMapTable( + target: K, + create: true, + ): HashMap; + function GetOrCreateWeakMapTable( + target: K, + create: false, + ): HashMap | undefined; + function GetOrCreateWeakMapTable( + target: K, + create: boolean, + ): HashMap | undefined { + if (!hasOwn.call(target, rootKey)) { + if (!create) return undefined; + Object.defineProperty( + target, + rootKey, + { value: HashMap.create() }, + ); + } + return ( target)[rootKey]; + } + + function FillRandomBytes(buffer: BufferLike, size: number): BufferLike { + for (let i = 0; i < size; ++i) buffer[i] = Math.random() * 0xff | 0; + return buffer; + } + + function GenRandomBytes(size: number): BufferLike { + if (typeof Uint8Array === "function") { + if (typeof crypto !== "undefined") { + return crypto.getRandomValues(new Uint8Array(size)) as Uint8Array; + } + if (typeof msCrypto !== "undefined") { + return msCrypto.getRandomValues(new Uint8Array(size)) as Uint8Array; + } + return FillRandomBytes(new Uint8Array(size), size); + } + return FillRandomBytes(new Array(size), size); + } + + function CreateUUID() { + const data = GenRandomBytes(UUID_SIZE); + // mark as random - RFC 4122 ยง 4.4 + data[6] = data[6] & 0x4f | 0x40; + data[8] = data[8] & 0xbf | 0x80; + let result = ""; + for (let offset = 0; offset < UUID_SIZE; ++offset) { + const byte = data[offset]; + if (offset === 4 || offset === 6 || offset === 8) result += "-"; + if (byte < 16) result += "0"; + result += byte.toString(16).toLowerCase(); + } + return result; + } + } + + // uses a heuristic used by v8 and chakra to force an object into dictionary mode. + function MakeDictionary(obj: T): T { + ( obj).__ = undefined; + delete ( obj).__; + return obj; + } + }); +} diff --git a/lib/src/type/UUID.ts b/lib/src/type/UUID.ts new file mode 100755 index 0000000..b697a5d --- /dev/null +++ b/lib/src/type/UUID.ts @@ -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 } diff --git a/tsconfig.json b/tsconfig.json new file mode 100755 index 0000000..4052b24 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "target": "es2017" + } +} + +