Start JSDocs
This commit is contained in:
parent
673fbc84f8
commit
c2a7c3f914
@ -11,17 +11,28 @@ type MaybeFactory = AbstractFactory | undefined
|
|||||||
type MaybeDependency = any | undefined
|
type MaybeDependency = any | undefined
|
||||||
type ResolvedDependency = { param_index: number, key: DependencyKey, resolved: any }
|
type ResolvedDependency = { param_index: number, key: DependencyKey, resolved: any }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface used to keep track of singleton factory values, by their dependency key.
|
||||||
|
*/
|
||||||
interface InstanceRef {
|
interface InstanceRef {
|
||||||
key: DependencyKey,
|
key: DependencyKey,
|
||||||
value: any,
|
value: any,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when a factory is registered with a duplicate dependency key.
|
||||||
|
* @extends Error
|
||||||
|
*/
|
||||||
class DuplicateFactoryKeyError extends Error {
|
class DuplicateFactoryKeyError extends Error {
|
||||||
constructor(key: DependencyKey) {
|
constructor(key: DependencyKey) {
|
||||||
super(`A factory definition already exists with the key for ${key}.`)
|
super(`A factory definition already exists with the key for ${key}.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when a dependency key that has not been registered is passed to a resolver.
|
||||||
|
* @extends Error
|
||||||
|
*/
|
||||||
class InvalidDependencyKeyError extends Error {
|
class InvalidDependencyKeyError extends Error {
|
||||||
constructor(key: DependencyKey) {
|
constructor(key: DependencyKey) {
|
||||||
super(`No such dependency is registered with this container: ${key}`)
|
super(`No such dependency is registered with this container: ${key}`)
|
||||||
@ -29,8 +40,20 @@ class InvalidDependencyKeyError extends Error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory-based inversion-of-control container.
|
||||||
|
*/
|
||||||
class Container {
|
class Container {
|
||||||
|
/**
|
||||||
|
* Collection of factories registered with this container.
|
||||||
|
* @type Collection<AbstractFactory>
|
||||||
|
*/
|
||||||
private factories: Collection<AbstractFactory> = new Collection<AbstractFactory>()
|
private factories: Collection<AbstractFactory> = new Collection<AbstractFactory>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of singleton instances produced by this container.
|
||||||
|
* @type Collection<InstanceRef>
|
||||||
|
*/
|
||||||
private instances: Collection<InstanceRef> = new Collection<InstanceRef>()
|
private instances: Collection<InstanceRef> = new Collection<InstanceRef>()
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -43,6 +66,10 @@ class Container {
|
|||||||
this.register_singleton('injector', this)
|
this.register_singleton('injector', this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a basic instantiable class as a standard Factory with this container.
|
||||||
|
* @param {Instantiable} dependency
|
||||||
|
*/
|
||||||
register(dependency: Instantiable<any>) {
|
register(dependency: Instantiable<any>) {
|
||||||
if ( this.resolve(dependency) )
|
if ( this.resolve(dependency) )
|
||||||
throw new DuplicateFactoryKeyError(dependency)
|
throw new DuplicateFactoryKeyError(dependency)
|
||||||
@ -51,6 +78,11 @@ class Container {
|
|||||||
this.factories.push(factory)
|
this.factories.push(factory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the given function as a factory within the container.
|
||||||
|
* @param {string} name - unique name to identify the factory in the container
|
||||||
|
* @param {function} producer - factory to produce a value
|
||||||
|
*/
|
||||||
register_producer(name: string, producer: () => any) {
|
register_producer(name: string, producer: () => any) {
|
||||||
if ( this.resolve(name) )
|
if ( this.resolve(name) )
|
||||||
throw new DuplicateFactoryKeyError(name)
|
throw new DuplicateFactoryKeyError(name)
|
||||||
@ -59,6 +91,12 @@ class Container {
|
|||||||
this.factories.push(factory)
|
this.factories.push(factory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a basic instantiable class as a standard Factory with this container,
|
||||||
|
* identified by a string name rather than static class.
|
||||||
|
* @param {string} name - unique name to identify the factory in the container
|
||||||
|
* @param {Instantiable} dependency
|
||||||
|
*/
|
||||||
register_named(name: string, dependency: Instantiable<any>) {
|
register_named(name: string, dependency: Instantiable<any>) {
|
||||||
if ( this.resolve(name) )
|
if ( this.resolve(name) )
|
||||||
throw new DuplicateFactoryKeyError(name)
|
throw new DuplicateFactoryKeyError(name)
|
||||||
@ -67,6 +105,12 @@ class Container {
|
|||||||
this.factories.push(factory)
|
this.factories.push(factory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a value as a singleton in the container. It will not be instantiated, but
|
||||||
|
* can be injected by its unique name.
|
||||||
|
* @param {string} key - unique name to identify the singleton in the container
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
register_singleton(key: string, value: any) {
|
register_singleton(key: string, value: any) {
|
||||||
if ( this.resolve(key) )
|
if ( this.resolve(key) )
|
||||||
throw new DuplicateFactoryKeyError(key)
|
throw new DuplicateFactoryKeyError(key)
|
||||||
@ -74,29 +118,55 @@ class Container {
|
|||||||
this.factories.push(new SingletonFactory(value, key))
|
this.factories.push(new SingletonFactory(value, key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a given factory with the container.
|
||||||
|
* @param {AbstractFactory} factory
|
||||||
|
*/
|
||||||
register_factory(factory: AbstractFactory) {
|
register_factory(factory: AbstractFactory) {
|
||||||
if ( !this.factories.includes(factory) )
|
if ( !this.factories.includes(factory) )
|
||||||
this.factories.push(factory)
|
this.factories.push(factory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the container has an already-produced value for the given key.
|
||||||
|
* @param {DependencyKey} key
|
||||||
|
*/
|
||||||
has_instance(key: DependencyKey): boolean {
|
has_instance(key: DependencyKey): boolean {
|
||||||
return this.instances.where('key', '=', key).isNotEmpty()
|
return this.instances.where('key', '=', key).isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the container has a factory for the given key.
|
||||||
|
* @param {DependencyKey} key
|
||||||
|
*/
|
||||||
has_key(key: DependencyKey): boolean {
|
has_key(key: DependencyKey): boolean {
|
||||||
return !!this.resolve(key)
|
return !!this.resolve(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the already-produced value for the given key, if one exists.
|
||||||
|
* @param {DependencyKey} key
|
||||||
|
*/
|
||||||
get_existing_instance(key: DependencyKey): MaybeDependency {
|
get_existing_instance(key: DependencyKey): MaybeDependency {
|
||||||
const instances = this.instances.where('key', '=', key)
|
const instances = this.instances.where('key', '=', key)
|
||||||
if ( instances.isNotEmpty() ) return instances.first()
|
if ( instances.isNotEmpty() ) return instances.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the factory for the given key, if one is registered with this container.
|
||||||
|
* @param {DependencyKey} key
|
||||||
|
*/
|
||||||
resolve(key: DependencyKey): MaybeFactory {
|
resolve(key: DependencyKey): MaybeFactory {
|
||||||
const factory = this.factories.firstWhere(item => item.match(key))
|
const factory = this.factories.firstWhere(item => item.match(key))
|
||||||
if ( factory ) return factory
|
if ( factory ) return factory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the dependency key. If a singleton value for that key already exists in this container,
|
||||||
|
* return that value. Otherwise, use the factory an given parameters to produce and return the value.
|
||||||
|
* @param {DependencyKey} key
|
||||||
|
* @param {...any} parameters
|
||||||
|
*/
|
||||||
resolve_and_create(key: DependencyKey, ...parameters: any[]): any {
|
resolve_and_create(key: DependencyKey, ...parameters: any[]): any {
|
||||||
// If we've already instantiated this, just return that
|
// If we've already instantiated this, just return that
|
||||||
const instance = this.get_existing_instance(key)
|
const instance = this.get_existing_instance(key)
|
||||||
@ -117,6 +187,12 @@ class Container {
|
|||||||
return new_instance
|
return new_instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a factory and manually-provided parameters, resolve the dependencies for the
|
||||||
|
* factory and produce its value.
|
||||||
|
* @param {AbstractFactory} factory
|
||||||
|
* @param {array} parameters
|
||||||
|
*/
|
||||||
protected produce_factory(factory: AbstractFactory, parameters: any[]) {
|
protected produce_factory(factory: AbstractFactory, parameters: any[]) {
|
||||||
// Create the dependencies for the factory
|
// Create the dependencies for the factory
|
||||||
const keys = factory.get_dependency_keys().filter(req => this.has_key(req.key))
|
const keys = factory.get_dependency_keys().filter(req => this.has_key(req.key))
|
||||||
@ -143,6 +219,15 @@ class Container {
|
|||||||
return factory.produce(construction_args, params.reverse().all())
|
return factory.produce(construction_args, params.reverse().all())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of the given target. The target can either be a DependencyKey registered with
|
||||||
|
* this container (in which case, the singleton value will be returned), or an instantiable class.
|
||||||
|
*
|
||||||
|
* If the instantiable class has the Injectable decorator, its injectable parameters will be automatically
|
||||||
|
* injected into the instance.
|
||||||
|
* @param {DependencyKey} target
|
||||||
|
* @param {...any} parameters
|
||||||
|
*/
|
||||||
make(target: DependencyKey, ...parameters: any[]) {
|
make(target: DependencyKey, ...parameters: any[]) {
|
||||||
if ( this.has_key(target) )
|
if ( this.has_key(target) )
|
||||||
return this.resolve_and_create(target, ...parameters)
|
return this.resolve_and_create(target, ...parameters)
|
||||||
@ -152,6 +237,10 @@ class Container {
|
|||||||
throw new TypeError(`Invalid or unknown make target: ${target}`)
|
throw new TypeError(`Invalid or unknown make target: ${target}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a collection of dependency keys required by the given target, if it is registered with this container.
|
||||||
|
* @param {DependencyKey} target
|
||||||
|
*/
|
||||||
get_dependencies(target: DependencyKey): Collection<DependencyKey> {
|
get_dependencies(target: DependencyKey): Collection<DependencyKey> {
|
||||||
const factory = this.resolve(target)
|
const factory = this.resolve(target)
|
||||||
|
|
||||||
|
@ -3,6 +3,11 @@ import {DEPENDENCY_KEYS_METADATA_KEY, DependencyKey} from '../type/DependencyKey
|
|||||||
import {collect, Collection} from '../../../lib/src/collection/Collection.ts'
|
import {collect, Collection} from '../../../lib/src/collection/Collection.ts'
|
||||||
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a collection of dependency requirements for the given target object.
|
||||||
|
* @param {Object} target
|
||||||
|
* @return Collection<DependencyRequirement>
|
||||||
|
*/
|
||||||
const initDependencyMetadata = (target: Object): Collection<DependencyRequirement> => {
|
const initDependencyMetadata = (target: Object): Collection<DependencyRequirement> => {
|
||||||
const param_types = Reflect.getMetadata('design:paramtypes', target)
|
const param_types = Reflect.getMetadata('design:paramtypes', target)
|
||||||
return collect<DependencyKey>(param_types).map<DependencyRequirement>((type, index) => {
|
return collect<DependencyKey>(param_types).map<DependencyRequirement>((type, index) => {
|
||||||
@ -14,6 +19,11 @@ const initDependencyMetadata = (target: Object): Collection<DependencyRequiremen
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class decorator that marks a class as injectable. When this is applied, dependency
|
||||||
|
* metadata for the constructors params is resolved and stored in metadata.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
const Injectable = (): ClassDecorator => {
|
const Injectable = (): ClassDecorator => {
|
||||||
return (target) => {
|
return (target) => {
|
||||||
const meta = initDependencyMetadata(target)
|
const meta = initDependencyMetadata(target)
|
||||||
@ -47,6 +57,12 @@ const Injectable = (): ClassDecorator => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameter decorator to manually mark a parameter as being an injection target on injectable
|
||||||
|
* classes. This can be used to override the dependency key of a given parameter.
|
||||||
|
* @param {DependencyKey} key
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
const Inject = (key: DependencyKey): ParameterDecorator => {
|
const Inject = (key: DependencyKey): ParameterDecorator => {
|
||||||
return (target, property, param_index) => {
|
return (target, property, param_index) => {
|
||||||
if ( !Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, target) ) {
|
if ( !Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, target) ) {
|
||||||
|
@ -4,6 +4,13 @@ import { Injectable } from './Injection.ts'
|
|||||||
|
|
||||||
const injectable = Injectable()
|
const injectable = Injectable()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class decorator that marks a class as injectable and singleton in the dependency container.
|
||||||
|
* When this is applied, the Injectable decorator is automatically applied, and the class is
|
||||||
|
* marked so that, when it is injected into classes, a singleton instance is used.
|
||||||
|
* @param {string} [name] - optionally, refer to the dependency by a name, rather than by class type
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
const Service = (name?: string): ClassDecorator => {
|
const Service = (name?: string): ClassDecorator => {
|
||||||
return (target) => {
|
return (target) => {
|
||||||
if ( isInstantiable(target) ) {
|
if ( isInstantiable(target) ) {
|
||||||
|
@ -1,12 +1,38 @@
|
|||||||
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
||||||
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for dependency container factories.
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
export default abstract class AbstractFactory {
|
export default abstract class AbstractFactory {
|
||||||
protected constructor(
|
protected constructor(
|
||||||
|
/**
|
||||||
|
* Token that was registered for this factory. In most cases, this is the static
|
||||||
|
* form of the item that is to be produced by this factory.
|
||||||
|
* @var
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
protected token: any
|
protected token: any
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce an instance of the token.
|
||||||
|
* @param {Array} dependencies - the resolved dependencies, in order
|
||||||
|
* @param {Array} parameters - the bound constructor parameters, in order
|
||||||
|
*/
|
||||||
abstract produce(dependencies: any[], parameters: any[]): any
|
abstract produce(dependencies: any[], parameters: any[]): any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should return true if the given identifier matches the token for this factory.
|
||||||
|
* @param something
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
abstract match(something: any): boolean
|
abstract match(something: any): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the dependency requirements required by this factory's token.
|
||||||
|
* @return Collection<DependencyRequirement>
|
||||||
|
*/
|
||||||
abstract get_dependency_keys(): Collection<DependencyRequirement>
|
abstract get_dependency_keys(): Collection<DependencyRequirement>
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ import { Collection } from '../../../lib/src/collection/Collection.ts'
|
|||||||
import { DependencyRequirement } from '../type/DependencyRequirement.ts'
|
import { DependencyRequirement } from '../type/DependencyRequirement.ts'
|
||||||
import AbstractFactory from './AbstractFactory.ts'
|
import AbstractFactory from './AbstractFactory.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard factory that produces injected versions of instantiable classes.
|
||||||
|
* @extends AbstractFactory
|
||||||
|
*/
|
||||||
export default class Factory extends AbstractFactory {
|
export default class Factory extends AbstractFactory {
|
||||||
constructor(
|
constructor(
|
||||||
protected token: Instantiable<any>
|
protected token: Instantiable<any>
|
||||||
|
@ -2,9 +2,22 @@ import AbstractFactory from './AbstractFactory.ts'
|
|||||||
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
||||||
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container factory that produces an item by calling the token as a function.
|
||||||
|
* @extends AbstractFactory
|
||||||
|
*/
|
||||||
export default class FunctionFactory extends AbstractFactory {
|
export default class FunctionFactory extends AbstractFactory {
|
||||||
constructor(
|
constructor(
|
||||||
|
/**
|
||||||
|
* The name identifying this factory in the container.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
protected name: string,
|
protected name: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The token, which is a function that returns the value of this factory.
|
||||||
|
* @type {function}
|
||||||
|
*/
|
||||||
protected token: () => any,
|
protected token: () => any,
|
||||||
) {
|
) {
|
||||||
super(token)
|
super(token)
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
import Factory from './Factory.ts'
|
import Factory from './Factory.ts'
|
||||||
import Instantiable from "../type/Instantiable.ts";
|
import Instantiable from '../type/Instantiable.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container factory that produces an instance of the token, however the token
|
||||||
|
* is identified by a string name rather than a class reference.
|
||||||
|
* @extends Factory
|
||||||
|
*/
|
||||||
export default class NamedFactory extends Factory {
|
export default class NamedFactory extends Factory {
|
||||||
constructor(
|
constructor(
|
||||||
|
/**
|
||||||
|
* The name identifying this factory in the container.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
protected name: string,
|
protected name: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The token to be instantiated.
|
||||||
|
* @type {Instantiable}
|
||||||
|
*/
|
||||||
protected token: Instantiable<any>,
|
protected token: Instantiable<any>,
|
||||||
) {
|
) {
|
||||||
super(token)
|
super(token)
|
||||||
|
@ -2,9 +2,24 @@ import Factory from './Factory.ts'
|
|||||||
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
||||||
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container factory which returns its token as its value, without attempting
|
||||||
|
* to instantiate anything. This is used to register already-produced-singletons
|
||||||
|
* with the container.
|
||||||
|
* @extends Factory
|
||||||
|
*/
|
||||||
export default class SingletonFactory extends Factory {
|
export default class SingletonFactory extends Factory {
|
||||||
constructor(
|
constructor(
|
||||||
|
/**
|
||||||
|
* Instantiated value of this factory.
|
||||||
|
* @type FunctionConstructor
|
||||||
|
*/
|
||||||
protected token: FunctionConstructor,
|
protected token: FunctionConstructor,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String name of this singleton identifying it in the container.
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
protected key: string,
|
protected key: string,
|
||||||
) {
|
) {
|
||||||
super(token)
|
super(token)
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import Instantiable from './Instantiable.ts'
|
import Instantiable from './Instantiable.ts'
|
||||||
import {StaticClass} from './StaticClass.ts'
|
import {StaticClass} from './StaticClass.ts'
|
||||||
const DEPENDENCY_KEYS_METADATA_KEY = 'daton:di:dependencyKeys.ts'
|
const DEPENDENCY_KEYS_METADATA_KEY = 'daton:di:dependencyKeys.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type used to represent a value that can identify a factory in the container.
|
||||||
|
*/
|
||||||
type DependencyKey = Instantiable<any> | StaticClass<any, any> | string
|
type DependencyKey = Instantiable<any> | StaticClass<any, any> | string
|
||||||
|
|
||||||
export { DependencyKey, DEPENDENCY_KEYS_METADATA_KEY }
|
export { DependencyKey, DEPENDENCY_KEYS_METADATA_KEY }
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { DependencyKey } from './DependencyKey.ts'
|
import { DependencyKey } from './DependencyKey.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface used to store dependency requirements by their place in the injectable
|
||||||
|
* target's constructor parameters.
|
||||||
|
*/
|
||||||
interface DependencyRequirement {
|
interface DependencyRequirement {
|
||||||
param_index: number,
|
param_index: number,
|
||||||
key: DependencyKey,
|
key: DependencyKey,
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Interface that designates a particular value as able to be constructed.
|
||||||
|
*/
|
||||||
export default interface Instantiable<T> {
|
export default interface Instantiable<T> {
|
||||||
new(...args: any[]): T
|
new(...args: any[]): T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given value is instantiable.
|
||||||
|
* @param what
|
||||||
|
*/
|
||||||
const isInstantiable = <T>(what: any): what is Instantiable<T> => {
|
const isInstantiable = <T>(what: any): what is Instantiable<T> => {
|
||||||
return (typeof what === 'object' || typeof what === 'function') && 'constructor' in what && typeof what.constructor === 'function'
|
return (typeof what === 'object' || typeof what === 'function') && 'constructor' in what && typeof what.constructor === 'function'
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Type that identifies a value as a static class, even if it is not instantiable.
|
||||||
|
*/
|
||||||
export type StaticClass<T, T2> = Function & {prototype: T} & T2
|
export type StaticClass<T, T2> = Function & {prototype: T} & T2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the parameter is a static class.
|
||||||
|
* @param something
|
||||||
|
*/
|
||||||
export function isStaticClass<T, T2>(something: any): something is StaticClass<T, T2> {
|
export function isStaticClass<T, T2>(something: any): something is StaticClass<T, T2> {
|
||||||
return typeof something === 'function' && typeof something.prototype !== 'undefined'
|
return typeof something === 'function' && typeof something.prototype !== 'undefined'
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
import { Iterable } from './Iterable.ts'
|
import { Iterable } from './Iterable.ts'
|
||||||
import { collect } from './Collection.ts'
|
import { collect } from './Collection.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic Iterable implementation that uses an array as a backend.
|
||||||
|
* @extends Iterable
|
||||||
|
*/
|
||||||
export class ArrayIterable<T> extends Iterable<T> {
|
export class ArrayIterable<T> extends Iterable<T> {
|
||||||
constructor(
|
constructor(
|
||||||
|
/**
|
||||||
|
* Items to use for this iterable.
|
||||||
|
*/
|
||||||
protected items: T[],
|
protected items: T[],
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
@ -11,9 +11,21 @@ type AsyncCollectionComparable<T> = CollectionItem<T>[] | Collection<T> | AsyncC
|
|||||||
type AsyncKeyFunction<T, T2> = (item: CollectionItem<T>, index: number) => CollectionItem<T2> | Promise<CollectionItem<T2>>
|
type AsyncKeyFunction<T, T2> = (item: CollectionItem<T>, index: number) => CollectionItem<T2> | Promise<CollectionItem<T2>>
|
||||||
type AsyncCollectionFunction<T, T2> = (items: AsyncCollection<T>) => T2
|
type AsyncCollectionFunction<T, T2> = (items: AsyncCollection<T>) => T2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like a collection, but asynchronous.
|
||||||
|
*/
|
||||||
export class AsyncCollection<T> {
|
export class AsyncCollection<T> {
|
||||||
constructor(
|
constructor(
|
||||||
|
/**
|
||||||
|
* Iterable of items to base this collction on.
|
||||||
|
* @type Iterable
|
||||||
|
*/
|
||||||
private _items: Iterable<T>,
|
private _items: Iterable<T>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size to use when chunking results for memory-optimization.
|
||||||
|
* @type number
|
||||||
|
*/
|
||||||
private _chunk_size: number = 1000, // TODO fix this. It's just for testing
|
private _chunk_size: number = 1000, // TODO fix this. It's just for testing
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -57,14 +69,27 @@ export class AsyncCollection<T> {
|
|||||||
await this._items.reset()
|
await this._items.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all items in this collection as an array.
|
||||||
|
* @return Promise<array>
|
||||||
|
*/
|
||||||
async all(): Promise<CollectionItem<T>[]> {
|
async all(): Promise<CollectionItem<T>[]> {
|
||||||
return (await this._items.from_range(0, await this._items.count())).all()
|
return (await this._items.from_range(0, await this._items.count())).all()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all items in this collection as a synchronous Collection
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async collect(): Promise<Collection<T>> {
|
async collect(): Promise<Collection<T>> {
|
||||||
return this._items.from_range(0, await this._items.count())
|
return this._items.from_range(0, await this._items.count())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the average value of the collection or one of its keys.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
async average<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
async average<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||||
let running_total = 0
|
let running_total = 0
|
||||||
let running_items = 0
|
let running_items = 0
|
||||||
@ -82,6 +107,11 @@ export class AsyncCollection<T> {
|
|||||||
return running_total / running_items
|
return running_total / running_items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the median value of the collection or one of its keys.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
async median<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
async median<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||||
let items: number[] = []
|
let items: number[] = []
|
||||||
|
|
||||||
@ -100,6 +130,11 @@ export class AsyncCollection<T> {
|
|||||||
else return (items[middle] + items[middle + 1]) / 2
|
else return (items[middle] + items[middle + 1]) / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mode value of the collection or one of its keys.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
async mode<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
async mode<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||||
let counts: any = {}
|
let counts: any = {}
|
||||||
|
|
||||||
@ -118,11 +153,24 @@ export class AsyncCollection<T> {
|
|||||||
return Number(Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b)[0])
|
return Number(Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b)[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this collection contains nested collections, collapse them to a single level.
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async collapse(): Promise<Collection<any>> {
|
async collapse(): Promise<Collection<any>> {
|
||||||
const items = await this.collect()
|
const items = await this.collect()
|
||||||
return items.collapse() as Collection<any>
|
return items.collapse() as Collection<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the collection contains an item satisfying the given collection.
|
||||||
|
* @example
|
||||||
|
* collection.contains('id', '>', 4)
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {WhereOperator} operator
|
||||||
|
* @param [operand]
|
||||||
|
* @return Promise<boolean>
|
||||||
|
*/
|
||||||
async contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
|
async contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
|
||||||
let contains = false
|
let contains = false
|
||||||
|
|
||||||
@ -137,10 +185,19 @@ export class AsyncCollection<T> {
|
|||||||
return contains
|
return contains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a clean instance of this collection pointing to the same result set of the iterable.
|
||||||
|
* @return Promise<AsyncCollection>
|
||||||
|
*/
|
||||||
async clone(): Promise<AsyncCollection<T>> {
|
async clone(): Promise<AsyncCollection<T>> {
|
||||||
return new AsyncCollection<T>(await this._items.clone())
|
return new AsyncCollection<T>(await this._items.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the elements that are different between the two collections.
|
||||||
|
* @param {AsyncCollectionComparable} items
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async diff(items: AsyncCollectionComparable<T>): Promise<Collection<T>> {
|
async diff(items: AsyncCollectionComparable<T>): Promise<Collection<T>> {
|
||||||
const matches: T[] = []
|
const matches: T[] = []
|
||||||
|
|
||||||
@ -155,6 +212,13 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(matches)
|
return new Collection<T>(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the elements that are different between the two collections, using the given function
|
||||||
|
* as a comparator for the elements.
|
||||||
|
* @param {AsyncCollectionComparable} items
|
||||||
|
* @param {DeterminesEquality} compare
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async diffUsing(items: AsyncCollectionComparable<T>, compare: DeterminesEquality<T>): Promise<Collection<T>> {
|
async diffUsing(items: AsyncCollectionComparable<T>, compare: DeterminesEquality<T>): Promise<Collection<T>> {
|
||||||
const matches: T[] = []
|
const matches: T[] = []
|
||||||
|
|
||||||
@ -168,6 +232,11 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(matches)
|
return new Collection<T>(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given item is present in the collection.
|
||||||
|
* @param item
|
||||||
|
* @return Promise<boolean>
|
||||||
|
*/
|
||||||
async includes(item: CollectionItem<T>): Promise<boolean> {
|
async includes(item: CollectionItem<T>): Promise<boolean> {
|
||||||
let contains = false
|
let contains = false
|
||||||
|
|
||||||
@ -181,6 +250,11 @@ export class AsyncCollection<T> {
|
|||||||
return contains
|
return contains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there is an item in the collection for which the given operator returns true.
|
||||||
|
* @param {function} operator - item => boolean
|
||||||
|
* @return Promise<boolean>
|
||||||
|
*/
|
||||||
async some(operator: (item: T) => boolean): Promise<boolean> {
|
async some(operator: (item: T) => boolean): Promise<boolean> {
|
||||||
let contains = false
|
let contains = false
|
||||||
|
|
||||||
@ -196,6 +270,11 @@ export class AsyncCollection<T> {
|
|||||||
return contains
|
return contains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a callback to each item in the collection.
|
||||||
|
* @param {AsyncKeyFunction} func
|
||||||
|
* @return Promise<void>
|
||||||
|
*/
|
||||||
async each<T2>(func: AsyncKeyFunction<T, T2>): Promise<void> {
|
async each<T2>(func: AsyncKeyFunction<T, T2>): Promise<void> {
|
||||||
let index = 0
|
let index = 0
|
||||||
|
|
||||||
@ -207,6 +286,11 @@ export class AsyncCollection<T> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a callback to each item in the collection and returns the results as a collection.
|
||||||
|
* @param {AsyncKeyFunction} func
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async map<T2>(func: AsyncKeyFunction<T, T2>): Promise<Collection<T2>> {
|
async map<T2>(func: AsyncKeyFunction<T, T2>): Promise<Collection<T2>> {
|
||||||
const new_items: CollectionItem<T2>[] = []
|
const new_items: CollectionItem<T2>[] = []
|
||||||
await this.each(async (item, index) => {
|
await this.each(async (item, index) => {
|
||||||
@ -215,6 +299,11 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T2>(new_items)
|
return new Collection<T2>(new_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given operator returns true for every item in the collection.
|
||||||
|
* @param {AsyncKeyFunction} func
|
||||||
|
* @return Promise<boolean>
|
||||||
|
*/
|
||||||
async every<T2>(func: AsyncKeyFunction<T, T2>): Promise<boolean> {
|
async every<T2>(func: AsyncKeyFunction<T, T2>): Promise<boolean> {
|
||||||
let pass = true
|
let pass = true
|
||||||
let index = 0
|
let index = 0
|
||||||
@ -233,6 +322,12 @@ export class AsyncCollection<T> {
|
|||||||
return pass
|
return pass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if every item in the collection satisfies the given where clause.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {WhereOperator} operator
|
||||||
|
* @param [operand]
|
||||||
|
*/
|
||||||
async everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
|
async everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
|
||||||
let pass = true
|
let pass = true
|
||||||
|
|
||||||
@ -246,6 +341,11 @@ export class AsyncCollection<T> {
|
|||||||
return pass
|
return pass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a filter to every item in the collection and returns the results that pass the filter.
|
||||||
|
* @param {KeyFunction} func
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async filter<T2>(func: KeyFunction<T, T2>): Promise<Collection<T>> {
|
async filter<T2>(func: KeyFunction<T, T2>): Promise<Collection<T>> {
|
||||||
let new_items: CollectionItem<T>[] = []
|
let new_items: CollectionItem<T>[] = []
|
||||||
|
|
||||||
@ -256,16 +356,35 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(new_items)
|
return new Collection<T>(new_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the passed in function if the boolean condition is true. Allows for functional syntax.
|
||||||
|
* @param {boolean} bool
|
||||||
|
* @param {AsyncCollectionFunction} then
|
||||||
|
* @return AsyncCollection
|
||||||
|
*/
|
||||||
when<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
|
when<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
|
||||||
if ( bool ) then(this)
|
if ( bool ) then(this)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the passed in function if the boolean condition is false. Allows for functional syntax.
|
||||||
|
* @param {boolean} bool
|
||||||
|
* @param {AsyncCollectionFunction} then
|
||||||
|
* @return AsyncCollection
|
||||||
|
*/
|
||||||
unless<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
|
unless<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
|
||||||
if ( !bool ) then(this)
|
if ( !bool ) then(this)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the given where condition to the collection and returns a new collection of the results.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {WhereOperator} operator
|
||||||
|
* @param [operand]
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
|
async where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
|
||||||
let new_items: CollectionItem<T>[] = []
|
let new_items: CollectionItem<T>[] = []
|
||||||
await this._chunk(async items => {
|
await this._chunk(async items => {
|
||||||
@ -274,6 +393,14 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(new_items)
|
return new Collection<T>(new_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the given where condition to the collection and returns a new collection of the items
|
||||||
|
* that did not satisfy the condition.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {WhereOperator} operator
|
||||||
|
* @param [operand]
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
|
async whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
|
||||||
let new_items: CollectionItem<T>[] = []
|
let new_items: CollectionItem<T>[] = []
|
||||||
await this._chunk(async items => {
|
await this._chunk(async items => {
|
||||||
@ -282,6 +409,12 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(new_items)
|
return new Collection<T>(new_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a WHERE ... IN ... condition to the collection an returns a new collection of the results.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {AsyncCollectionComparable} items
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async whereIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
|
async whereIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
|
||||||
let new_items: CollectionItem<T>[] = []
|
let new_items: CollectionItem<T>[] = []
|
||||||
await this._chunk_all_associate(key,async chunk => {
|
await this._chunk_all_associate(key,async chunk => {
|
||||||
@ -294,6 +427,13 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(new_items)
|
return new Collection<T>(new_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a WHERE ... IN ... condition to the collection and returns a new collection of the items
|
||||||
|
* that did not satisfy the condition.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {AsyncCollectionComparable} items
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async whereNotIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
|
async whereNotIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
|
||||||
let new_items: CollectionItem<T>[] = []
|
let new_items: CollectionItem<T>[] = []
|
||||||
await this._chunk_all_associate(key,async chunk => {
|
await this._chunk_all_associate(key,async chunk => {
|
||||||
@ -306,12 +446,23 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(new_items)
|
return new Collection<T>(new_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first item in the collection, if one exists.
|
||||||
|
* @return Promise<MaybeCollectionItem>
|
||||||
|
*/
|
||||||
async first(): Promise<MaybeCollectionItem<T>> {
|
async first(): Promise<MaybeCollectionItem<T>> {
|
||||||
if ( await this._items.count() > 0 ) {
|
if ( await this._items.count() > 0 ) {
|
||||||
return this._items.at_index(0)
|
return this._items.at_index(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first item in the collection that satisfies the given where condition, if one exists.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {WhereOperator} [operator = '=']
|
||||||
|
* @param [operand = true]
|
||||||
|
* @return Promise<MaybeCollectionItem>
|
||||||
|
*/
|
||||||
async firstWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
|
async firstWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
|
||||||
let item = undefined
|
let item = undefined
|
||||||
await this._chunk_all_associate(key, async items => {
|
await this._chunk_all_associate(key, async items => {
|
||||||
@ -324,6 +475,12 @@ export class AsyncCollection<T> {
|
|||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first item in the collection that does not satisfy the given where condition, if one exists.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {WhereOperator} [operator = '=']
|
||||||
|
* @param [operand = true]
|
||||||
|
*/
|
||||||
async firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
|
async firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
|
||||||
let item: MaybeCollectionItem<T> = undefined
|
let item: MaybeCollectionItem<T> = undefined
|
||||||
await this._chunk(async items => {
|
await this._chunk(async items => {
|
||||||
@ -336,31 +493,67 @@ export class AsyncCollection<T> {
|
|||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of elements in this collection.
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
async count() {
|
async count() {
|
||||||
return this._items.count()
|
return this._items.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of elements in this collection.
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
async length() {
|
async length() {
|
||||||
return this._items.count()
|
return this._items.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the item at the given index of this collection, if one exists.
|
||||||
|
* If none exists and a fallback value is provided, that value will be returned.
|
||||||
|
* @param {number} index
|
||||||
|
* @param [fallback]
|
||||||
|
* @return Promise<any>
|
||||||
|
*/
|
||||||
async get(index: number, fallback?: any) {
|
async get(index: number, fallback?: any) {
|
||||||
if ( (await this.count()) > index ) return this._items.at_index(index)
|
if ( (await this.count()) > index ) return this._items.at_index(index)
|
||||||
else return fallback
|
else return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the item at the given index of this collection, if one exists.
|
||||||
|
* @param {number} index
|
||||||
|
*/
|
||||||
async at(index: number): Promise<MaybeCollectionItem<T>> {
|
async at(index: number): Promise<MaybeCollectionItem<T>> {
|
||||||
return this.get(index)
|
return this.get(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an object which maps key values to arrays of items in the collection that satisfy that value.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<object>
|
||||||
|
*/
|
||||||
async groupBy<T2>(key: KeyOperator<T, T2>): Promise<any> {
|
async groupBy<T2>(key: KeyOperator<T, T2>): Promise<any> {
|
||||||
return (await this.collect()).groupBy(key)
|
return (await this.collect()).groupBy(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an object mapping the given key value to items in this collection.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<object>
|
||||||
|
*/
|
||||||
async associate<T2>(key: KeyOperator<T, T2>): Promise<any> {
|
async associate<T2>(key: KeyOperator<T, T2>): Promise<any> {
|
||||||
return (await this.collect()).associate(key)
|
return (await this.collect()).associate(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join the items in this collection with the given delimiter.
|
||||||
|
* @example
|
||||||
|
* await collection.join(',') // => '1,2,3,4'
|
||||||
|
* @param {string} delimiter
|
||||||
|
* @return Promise<string>
|
||||||
|
*/
|
||||||
async join(delimiter: string): Promise<string> {
|
async join(delimiter: string): Promise<string> {
|
||||||
let running_strings: string[] = []
|
let running_strings: string[] = []
|
||||||
|
|
||||||
@ -371,33 +564,74 @@ export class AsyncCollection<T> {
|
|||||||
return running_strings.join(delimiter)
|
return running_strings.join(delimiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join the items in this collection with the given delimiter.
|
||||||
|
* @example
|
||||||
|
* await collection.implode(',') // => '1,2,3,4'
|
||||||
|
* @param {string} delimiter
|
||||||
|
* @return Promise<string>
|
||||||
|
*/
|
||||||
async implode(delimiter: string): Promise<string> {
|
async implode(delimiter: string): Promise<string> {
|
||||||
return this.join(delimiter)
|
return this.join(delimiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO intersect
|
// TODO intersect
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are no items in this collection.
|
||||||
|
* @return Promise<boolean>
|
||||||
|
*/
|
||||||
async isEmpty(): Promise<boolean> {
|
async isEmpty(): Promise<boolean> {
|
||||||
return (await this._items.count()) < 1
|
return (await this._items.count()) < 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there is at least one item in this collection.
|
||||||
|
* @return Promise<boolean>
|
||||||
|
*/
|
||||||
async isNotEmpty(): Promise<boolean> {
|
async isNotEmpty(): Promise<boolean> {
|
||||||
return (await this._items.count()) > 0
|
return (await this._items.count()) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the last item in this collection, if one exists.
|
||||||
|
* @return Promise<MaybeCollectionItem>
|
||||||
|
*/
|
||||||
async last(): Promise<MaybeCollectionItem<T>> {
|
async last(): Promise<MaybeCollectionItem<T>> {
|
||||||
const length = await this._items.count()
|
const length = await this._items.count()
|
||||||
if ( length > 0 ) return this._items.at_index(length - 1)
|
if ( length > 0 ) return this._items.at_index(length - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the last item in this collection which satisfies the given where condition, if one exists.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {WhereOperator} operator
|
||||||
|
* @param [operand]
|
||||||
|
* @return Promise<MaybeCollectionItem>
|
||||||
|
*/
|
||||||
async lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
|
async lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
|
||||||
return (await this.where(key, operator, operand)).last()
|
return (await this.where(key, operator, operand)).last()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the last item in this collection which does not satisfy the given condition, if one exists.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @param {WhereOperator} operator
|
||||||
|
* @param [operand]
|
||||||
|
* @return Promise<MaybeCollectionItem>
|
||||||
|
*/
|
||||||
async lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
|
async lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
|
||||||
return (await this.whereNot(key, operator, operand)).last()
|
return (await this.whereNot(key, operator, operand)).last()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a collection of the values of a given key for each item in this collection.
|
||||||
|
* @example
|
||||||
|
* // collection has { a: 1 }, { a: 2 }, { a: 3 }
|
||||||
|
* await collection.pluck('a') // => [1, 2, 3]
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async pluck<T2>(key: KeyOperator<T, T2>): Promise<Collection<T2>> {
|
async pluck<T2>(key: KeyOperator<T, T2>): Promise<Collection<T2>> {
|
||||||
let new_items: CollectionItem<T2>[] = []
|
let new_items: CollectionItem<T2>[] = []
|
||||||
|
|
||||||
@ -408,6 +642,11 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T2>(new_items)
|
return new Collection<T2>(new_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the max value of the given key.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
async max<T2>(key: KeyOperator<T, T2>): Promise<number> {
|
async max<T2>(key: KeyOperator<T, T2>): Promise<number> {
|
||||||
let running_max: number
|
let running_max: number
|
||||||
|
|
||||||
@ -421,10 +660,20 @@ export class AsyncCollection<T> {
|
|||||||
return running_max
|
return running_max
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a collection of items that have the max value of the given key.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async whereMax<T2>(key: KeyOperator<T, T2>): Promise<Collection<T>> {
|
async whereMax<T2>(key: KeyOperator<T, T2>): Promise<Collection<T>> {
|
||||||
return this.where(key, '=', await this.max(key))
|
return this.where(key, '=', await this.max(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the min value of the given key.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
async min<T2>(key: KeyOperator<T, T2>): Promise<number> {
|
async min<T2>(key: KeyOperator<T, T2>): Promise<number> {
|
||||||
let running_min: number
|
let running_min: number
|
||||||
|
|
||||||
@ -438,10 +687,20 @@ export class AsyncCollection<T> {
|
|||||||
return running_min
|
return running_min
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a collection of items that have the min value of the given key.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async whereMin<T2>(key: KeyOperator<T, T2>): Promise<Collection<T>> {
|
async whereMin<T2>(key: KeyOperator<T, T2>): Promise<Collection<T>> {
|
||||||
return this.where(key, '=', await this.min(key))
|
return this.where(key, '=', await this.min(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the two collections.
|
||||||
|
* @param {AsyncCollectionComparable} merge_with
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async merge<T2>(merge_with: AsyncCollectionComparable<T2>): Promise<Collection<T|T2>> {
|
async merge<T2>(merge_with: AsyncCollectionComparable<T2>): Promise<Collection<T|T2>> {
|
||||||
let items: T2[]
|
let items: T2[]
|
||||||
if ( merge_with instanceof Collection ) items = await merge_with.all()
|
if ( merge_with instanceof Collection ) items = await merge_with.all()
|
||||||
@ -452,6 +711,11 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T|T2>([...items, ...await this.all()])
|
return new Collection<T|T2>([...items, ...await this.all()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a collection of every nth item in this collection.
|
||||||
|
* @param {number} n
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async nth(n: number): Promise<Collection<T>> {
|
async nth(n: number): Promise<Collection<T>> {
|
||||||
const matches: CollectionItem<T>[] = []
|
const matches: CollectionItem<T>[] = []
|
||||||
let current = 1
|
let current = 1
|
||||||
@ -467,12 +731,21 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(matches)
|
return new Collection<T>(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a collection containing the items that would be on the given page, with the given number of items per page.
|
||||||
|
* @param {number} page
|
||||||
|
* @param {number} per_page
|
||||||
|
*/
|
||||||
async forPage(page: number, per_page: number): Promise<Collection<T>> {
|
async forPage(page: number, per_page: number): Promise<Collection<T>> {
|
||||||
const start = page * per_page - per_page
|
const start = page * per_page - per_page
|
||||||
const end = page * per_page - 1
|
const end = page * per_page - 1
|
||||||
return this._items.from_range(start, end)
|
return this._items.from_range(start, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the value of the function, passing this collection to it.
|
||||||
|
* @param {AsyncCollectionFunction} func
|
||||||
|
*/
|
||||||
pipe<T2>(func: AsyncCollectionFunction<T, T2>): any {
|
pipe<T2>(func: AsyncCollectionFunction<T, T2>): any {
|
||||||
return func(this)
|
return func(this)
|
||||||
}
|
}
|
||||||
@ -484,6 +757,12 @@ export class AsyncCollection<T> {
|
|||||||
}
|
}
|
||||||
}*/ // TODO Fix this
|
}*/ // TODO Fix this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get n random items from this collection.
|
||||||
|
* @todo add safety check for it loop exceeds max number of items
|
||||||
|
* @param {number} n
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async random(n: number): Promise<Collection<T>> {
|
async random(n: number): Promise<Collection<T>> {
|
||||||
const random_items: CollectionItem<T>[] = []
|
const random_items: CollectionItem<T>[] = []
|
||||||
const fetched_indices: number[] = []
|
const fetched_indices: number[] = []
|
||||||
@ -505,6 +784,12 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(random_items)
|
return new Collection<T>(random_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse the collection into a single value using a reducer function.
|
||||||
|
* @param {KeyReducerFunction} reducer
|
||||||
|
* @param [initial_value]
|
||||||
|
* @return Promise<any>
|
||||||
|
*/
|
||||||
async reduce<T2>(reducer: KeyReducerFunction<T, T2>, initial_value?: T2): Promise<T2 | undefined> {
|
async reduce<T2>(reducer: KeyReducerFunction<T, T2>, initial_value?: T2): Promise<T2 | undefined> {
|
||||||
let current_value = initial_value
|
let current_value = initial_value
|
||||||
let index = 0
|
let index = 0
|
||||||
@ -519,6 +804,11 @@ export class AsyncCollection<T> {
|
|||||||
return current_value
|
return current_value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a collection of items that fail the truth test.
|
||||||
|
* @param {AsyncKeyFunction} truth_test
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async reject<T2>(truth_test: AsyncKeyFunction<T, T2>): Promise<Collection<T>> {
|
async reject<T2>(truth_test: AsyncKeyFunction<T, T2>): Promise<Collection<T>> {
|
||||||
let rejected: CollectionItem<T>[] = []
|
let rejected: CollectionItem<T>[] = []
|
||||||
|
|
||||||
@ -531,10 +821,19 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T>(rejected)
|
return new Collection<T>(rejected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a reversed collection of this collection's items.
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async reverse(): Promise<Collection<T>> {
|
async reverse(): Promise<Collection<T>> {
|
||||||
return (await this.collect()).reverse()
|
return (await this.collect()).reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search the collection and return the index of that item, if one exists.
|
||||||
|
* @param {CollectionItem} item
|
||||||
|
* @return Promise<MaybeCollectionIndex>
|
||||||
|
*/
|
||||||
async search(item: CollectionItem<T>): Promise<MaybeCollectionIndex> {
|
async search(item: CollectionItem<T>): Promise<MaybeCollectionIndex> {
|
||||||
let found_index
|
let found_index
|
||||||
let index = 0
|
let index = 0
|
||||||
@ -554,6 +853,10 @@ export class AsyncCollection<T> {
|
|||||||
return found_index
|
return found_index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next item in the collection and remove it.
|
||||||
|
* @return Promise<MaybeCollectionItem>
|
||||||
|
*/
|
||||||
async shift(): Promise<MaybeCollectionItem<T>> {
|
async shift(): Promise<MaybeCollectionItem<T>> {
|
||||||
const next_item = await this._items.next()
|
const next_item = await this._items.next()
|
||||||
if ( !next_item.done ) {
|
if ( !next_item.done ) {
|
||||||
@ -561,34 +864,75 @@ export class AsyncCollection<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuffle the items in the collection to a random order.
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async shuffle(): Promise<Collection<T>> {
|
async shuffle(): Promise<Collection<T>> {
|
||||||
return (await this.collect()).shuffle()
|
return (await this.collect()).shuffle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a slice of this collection.
|
||||||
|
* @param {number} start - the starting index
|
||||||
|
* @param {number} end - the ending index
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async slice(start: number, end: number): Promise<Collection<T>> {
|
async slice(start: number, end: number): Promise<Collection<T>> {
|
||||||
return this._items.from_range(start, end - 1)
|
return this._items.from_range(start, end - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the collection, optionally with the given comparison function.
|
||||||
|
* @param {ComparisonFunction} compare_func
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async sort(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
|
async sort(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
|
||||||
return (await this.collect()).sort(compare_func)
|
return (await this.collect()).sort(compare_func)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the collection by the given key.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async sortBy<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T>> {
|
async sortBy<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T>> {
|
||||||
return (await this.collect()).sortBy(key)
|
return (await this.collect()).sortBy(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse sort the collection, optionally with the given comparison function.
|
||||||
|
* @param {ComparisonFunction} compare_func
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async sortDesc(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
|
async sortDesc(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
|
||||||
return (await this.collect()).sortDesc(compare_func)
|
return (await this.collect()).sortDesc(compare_func)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse sort the collection by the given key.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async sortByDesc<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T>> {
|
async sortByDesc<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T>> {
|
||||||
return (await this.collect()).sortByDesc(key)
|
return (await this.collect()).sortByDesc(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splice the collection at the given index. Optionally, removing the given number of items.
|
||||||
|
* @param {CollectionIndex} start
|
||||||
|
* @param {number} [deleteCount]
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async splice(start: CollectionIndex, deleteCount?: number): Promise<Collection<T>> {
|
async splice(start: CollectionIndex, deleteCount?: number): Promise<Collection<T>> {
|
||||||
return (await this.collect()).splice(start, deleteCount)
|
return (await this.collect()).splice(start, deleteCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sum the items in the collection, or the values of the given key.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
async sum<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
async sum<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||||
let running_sum: number = 0
|
let running_sum: number = 0
|
||||||
|
|
||||||
@ -606,6 +950,11 @@ export class AsyncCollection<T> {
|
|||||||
return running_sum
|
return running_sum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the first n items from the front or back of the collection.
|
||||||
|
* @param {number} limit
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async take(limit: number): Promise<Collection<T>> {
|
async take(limit: number): Promise<Collection<T>> {
|
||||||
if ( limit === 0 ) return new Collection<T>()
|
if ( limit === 0 ) return new Collection<T>()
|
||||||
else if ( limit > 0 ) {
|
else if ( limit > 0 ) {
|
||||||
@ -616,11 +965,21 @@ export class AsyncCollection<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the given function, passing in this collection. Allows functional syntax.
|
||||||
|
* @param {AsyncCollectionFunction} func
|
||||||
|
* @return Promise<AsyncCollection>
|
||||||
|
*/
|
||||||
async tap<T2>(func: AsyncCollectionFunction<T, T2>): Promise<AsyncCollection<T>> {
|
async tap<T2>(func: AsyncCollectionFunction<T, T2>): Promise<AsyncCollection<T>> {
|
||||||
await func(this)
|
await func(this)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all the unique values in the collection, or the unique values of the given key.
|
||||||
|
* @param {KeyOperator} key
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
async unique<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T|T2>> {
|
async unique<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T|T2>> {
|
||||||
const has: CollectionItem<T|T2>[] = []
|
const has: CollectionItem<T|T2>[] = []
|
||||||
|
|
||||||
@ -641,6 +1000,10 @@ export class AsyncCollection<T> {
|
|||||||
return new Collection<T|T2>(has)
|
return new Collection<T|T2>(has)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast this collection to an array.
|
||||||
|
* @return Promise<array>
|
||||||
|
*/
|
||||||
async toArray(): Promise<any[]> {
|
async toArray(): Promise<any[]> {
|
||||||
const returns: any = []
|
const returns: any = []
|
||||||
for ( const item of (await this.all()) ) {
|
for ( const item of (await this.all()) ) {
|
||||||
@ -651,14 +1014,29 @@ export class AsyncCollection<T> {
|
|||||||
return returns
|
return returns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast this collection to a JSON string.
|
||||||
|
* @param [replacer] - the replacer to use
|
||||||
|
* @param {number} [space = 4] number of indentation spaces to use
|
||||||
|
*/
|
||||||
async toJSON(replacer = undefined, space = 4): Promise<string> {
|
async toJSON(replacer = undefined, space = 4): Promise<string> {
|
||||||
return JSON.stringify(this.toArray(), replacer, space)
|
return JSON.stringify(this.toArray(), replacer, space)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator to support async iteration:
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* for await (const item of collection) {}
|
||||||
|
*/
|
||||||
[Symbol.asyncIterator]() {
|
[Symbol.asyncIterator]() {
|
||||||
return this._items.clone()
|
return this._items.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a clone of the underlying iterator of this collection.
|
||||||
|
* @return Iterable
|
||||||
|
*/
|
||||||
iterator() {
|
iterator() {
|
||||||
return this._items.clone()
|
return this._items.clone()
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,48 @@ export type ChunkCallback<T> = (items: Collection<T>) => any
|
|||||||
|
|
||||||
export class StopIteration extends Error {}
|
export class StopIteration extends Error {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class representing an iterable, lazy-loaded dataset.
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
export abstract class Iterable<T> {
|
export abstract class Iterable<T> {
|
||||||
|
/**
|
||||||
|
* The current index of the iterable.
|
||||||
|
* @type number
|
||||||
|
*/
|
||||||
protected index = 0
|
protected index = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the item of this iterable at the given index, if one exists.
|
||||||
|
* @param {number} i
|
||||||
|
* @return Promise<any|undefined>
|
||||||
|
*/
|
||||||
abstract async at_index(i: number): Promise<T | undefined>
|
abstract async at_index(i: number): Promise<T | undefined>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the collection of items in the given range of this iterable.
|
||||||
|
* @param {number} start
|
||||||
|
* @param {number} end
|
||||||
|
* @return Promise<Collection>
|
||||||
|
*/
|
||||||
abstract async from_range(start: number, end: number): Promise<Collection<T>>
|
abstract async from_range(start: number, end: number): Promise<Collection<T>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of items in this collection.
|
||||||
|
* @return Promise<number>
|
||||||
|
*/
|
||||||
abstract async count(): Promise<number>
|
abstract async count(): Promise<number>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a copy of this iterable.
|
||||||
|
* @return Iterable
|
||||||
|
*/
|
||||||
abstract clone(): Iterable<T>
|
abstract clone(): Iterable<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advance to the next value of this iterable.
|
||||||
|
* @return Promise<MaybeIterationItem>
|
||||||
|
*/
|
||||||
public async next(): Promise<MaybeIterationItem<T>> {
|
public async next(): Promise<MaybeIterationItem<T>> {
|
||||||
const i = this.index
|
const i = this.index
|
||||||
|
|
||||||
@ -24,10 +58,22 @@ export abstract class Iterable<T> {
|
|||||||
return { done: false, value: await this.at_index(i) }
|
return { done: false, value: await this.at_index(i) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to enable async iteration.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* for await (const item of iterable) {}
|
||||||
|
*/
|
||||||
[Symbol.asyncIterator]() {
|
[Symbol.asyncIterator]() {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunk the iterable into the given size and call the callback passing the chunk along.
|
||||||
|
* @param {number} size
|
||||||
|
* @param {ChunkCallback} callback
|
||||||
|
* @return Promise<void>
|
||||||
|
*/
|
||||||
public async chunk(size: number, callback: ChunkCallback<T>) {
|
public async chunk(size: number, callback: ChunkCallback<T>) {
|
||||||
const total = await this.count()
|
const total = await this.count()
|
||||||
|
|
||||||
@ -45,17 +91,30 @@ export abstract class Iterable<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advance the iterable to the given index.
|
||||||
|
* @param {number} index
|
||||||
|
* @return Promise<void>
|
||||||
|
*/
|
||||||
public async seek(index: number) {
|
public async seek(index: number) {
|
||||||
if ( index < 0 ) throw new TypeError('Cannot seek to negative index.')
|
if ( index < 0 ) throw new TypeError('Cannot seek to negative index.')
|
||||||
else if ( index >= await this.count() ) throw new TypeError('Cannot seek past last item.')
|
else if ( index >= await this.count() ) throw new TypeError('Cannot seek past last item.')
|
||||||
this.index = index
|
this.index = index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Peek at the next value of the iterable, without advancing.
|
||||||
|
* @return Promise<any|undefined>
|
||||||
|
*/
|
||||||
public async peek(): Promise<T | undefined> {
|
public async peek(): Promise<T | undefined> {
|
||||||
if ( this.index + 1 >= await this.count() ) return undefined
|
if ( this.index + 1 >= await this.count() ) return undefined
|
||||||
else return this.at_index(this.index + 1)
|
else return this.at_index(this.index + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the iterable to the first index.
|
||||||
|
* @return Promise<any>
|
||||||
|
*/
|
||||||
public async reset() {
|
public async reset() {
|
||||||
this.index = 0
|
this.index = 0
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Type representing a valid where operator.
|
||||||
|
*/
|
||||||
type WhereOperator = '&' | '>' | '>=' | '<' | '<=' | '!=' | '<=>' | '%' | '|' | '!' | '~' | '=' | '^'
|
type WhereOperator = '&' | '>' | '>=' | '<' | '<=' | '!=' | '<=>' | '%' | '|' | '!' | '~' | '=' | '^'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type associating search items with a key.
|
||||||
|
*/
|
||||||
type AssociatedSearchItem = { key: any, item: any }
|
type AssociatedSearchItem = { key: any, item: any }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type representing the result of a where.
|
||||||
|
*/
|
||||||
type WhereResult = any[]
|
type WhereResult = any[]
|
||||||
|
|
||||||
// bitwise and
|
/**
|
||||||
// greater than
|
* Returns true if the given item satisfies the given where clause.
|
||||||
// greater than equal
|
* @param {AssociatedSearchItem} item
|
||||||
// less than
|
* @param {WhereOperator} operator
|
||||||
// less than equal
|
* @param [operand]
|
||||||
// not equal
|
* @return boolean
|
||||||
// null-safe equal
|
*/
|
||||||
// modulo
|
|
||||||
// bitwise or
|
|
||||||
// not
|
|
||||||
// bitwise negation
|
|
||||||
// equal
|
|
||||||
|
|
||||||
const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand?: any): boolean => {
|
const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand?: any): boolean => {
|
||||||
switch ( operator ) {
|
switch ( operator ) {
|
||||||
case '&':
|
case '&':
|
||||||
@ -62,6 +67,12 @@ const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the given where clause to the items and return those that match.
|
||||||
|
* @param {Array<AssociatedSearchItem>} items
|
||||||
|
* @param {WhereOperator} operator
|
||||||
|
* @param [operand]
|
||||||
|
*/
|
||||||
const applyWhere = (items: AssociatedSearchItem[], operator: WhereOperator, operand?: any): WhereResult => {
|
const applyWhere = (items: AssociatedSearchItem[], operator: WhereOperator, operand?: any): WhereResult => {
|
||||||
const matches: WhereResult = []
|
const matches: WhereResult = []
|
||||||
for ( const item of items ) {
|
for ( const item of items ) {
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Enum of HTTP statuses.
|
||||||
|
* @example
|
||||||
|
* HTTPStatus.http200 // => 200
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* HTTPStatus.REQUEST_TIMEOUT // => 408
|
||||||
|
*/
|
||||||
export enum HTTPStatus {
|
export enum HTTPStatus {
|
||||||
http100 = 100,
|
http100 = 100,
|
||||||
http101 = 101,
|
http101 = 101,
|
||||||
@ -110,6 +118,9 @@ export enum HTTPStatus {
|
|||||||
NETWORK_AUTHENTICATION_REQUIRED = 511,
|
NETWORK_AUTHENTICATION_REQUIRED = 511,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps HTTP status code to default status message.
|
||||||
|
*/
|
||||||
export const Message = {
|
export const Message = {
|
||||||
100: 'Continue',
|
100: 'Continue',
|
||||||
101: 'Switching Protocols',
|
101: 'Switching Protocols',
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Enum of unit statuses.
|
||||||
|
*/
|
||||||
enum Status {
|
enum Status {
|
||||||
Stopped = 'stopped',
|
Stopped = 'stopped',
|
||||||
Starting = 'starting',
|
Starting = 'starting',
|
||||||
@ -6,6 +9,11 @@ enum Status {
|
|||||||
Error = 'error',
|
Error = 'error',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given value is a valid unit status.
|
||||||
|
* @param something
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
const isStatus = (something: any) => [
|
const isStatus = (something: any) => [
|
||||||
Status.Stopped,
|
Status.Stopped,
|
||||||
Status.Starting,
|
Status.Starting,
|
||||||
|
@ -1,8 +1,20 @@
|
|||||||
import {HTTPStatus, Message} from '../const/http.ts'
|
import {HTTPStatus, Message} from '../const/http.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class representing an HTTP error.
|
||||||
|
* @extends Error
|
||||||
|
*/
|
||||||
export default class HTTPError extends Error {
|
export default class HTTPError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
|
/**
|
||||||
|
* The associated HTTP status code.
|
||||||
|
* @type HTTPStatus
|
||||||
|
*/
|
||||||
public readonly http_status: HTTPStatus,
|
public readonly http_status: HTTPStatus,
|
||||||
|
/**
|
||||||
|
* The associated message.
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
public readonly http_message?: string
|
public readonly http_message?: string
|
||||||
) {
|
) {
|
||||||
super(`HTTP ${http_status}: ${http_message || Message[http_status]}`)
|
super(`HTTP ${http_status}: ${http_message || Message[http_status]}`)
|
||||||
|
@ -2,12 +2,19 @@ import { red, bgRed } from '../external/std.ts'
|
|||||||
import { Service } from '../../../di/src/decorator/Service.ts'
|
import { Service } from '../../../di/src/decorator/Service.ts'
|
||||||
import { Logging } from '../service/logging/Logging.ts'
|
import { Logging } from '../service/logging/Logging.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service class for handling and displaying top-level errors.
|
||||||
|
*/
|
||||||
@Service()
|
@Service()
|
||||||
export default class RunLevelErrorHandler {
|
export default class RunLevelErrorHandler {
|
||||||
constructor(
|
constructor(
|
||||||
protected logger: Logging,
|
protected logger: Logging,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error handler function.
|
||||||
|
* @type (e: Error) => void
|
||||||
|
*/
|
||||||
get handle(): (e: Error) => void {
|
get handle(): (e: Error) => void {
|
||||||
return (e: Error) => {
|
return (e: Error) => {
|
||||||
this.display(e)
|
this.display(e)
|
||||||
@ -15,6 +22,10 @@ export default class RunLevelErrorHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the error to the logger.
|
||||||
|
* @param {Error} e
|
||||||
|
*/
|
||||||
display(e: Error) {
|
display(e: Error) {
|
||||||
try {
|
try {
|
||||||
const error_string = `RunLevelErrorHandler invoked:
|
const error_string = `RunLevelErrorHandler invoked:
|
||||||
|
@ -6,6 +6,9 @@ import {Service} from '../../../../di/src/decorator/Service.ts'
|
|||||||
import {Request} from '../Request.ts'
|
import {Request} from '../Request.ts'
|
||||||
import {Logging} from '../../service/logging/Logging.ts'
|
import {Logging} from '../../service/logging/Logging.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for fluently registering kernel modules into the kernel.
|
||||||
|
*/
|
||||||
export interface ModuleRegistrationFluency {
|
export interface ModuleRegistrationFluency {
|
||||||
before: (other?: Instantiable<Module>) => Kernel,
|
before: (other?: Instantiable<Module>) => Kernel,
|
||||||
after: (other?: Instantiable<Module>) => Kernel,
|
after: (other?: Instantiable<Module>) => Kernel,
|
||||||
@ -14,18 +17,45 @@ export interface ModuleRegistrationFluency {
|
|||||||
core: () => Kernel,
|
core: () => Kernel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when a kernel module is requested that does not exist w/in the kernel.
|
||||||
|
* @extends Error
|
||||||
|
*/
|
||||||
export class KernelModuleNotFoundError extends Error {
|
export class KernelModuleNotFoundError extends Error {
|
||||||
constructor(mod_name: string) {
|
constructor(mod_name: string) {
|
||||||
super(`The kernel module ${mod_name} is not registered with the kernel.`)
|
super(`The kernel module ${mod_name} is not registered with the kernel.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic HTTP kernel used to process incoming and outgoing middleware.
|
||||||
|
* @extends AppClass
|
||||||
|
*/
|
||||||
@Service()
|
@Service()
|
||||||
export default class Kernel extends AppClass {
|
export default class Kernel extends AppClass {
|
||||||
|
/**
|
||||||
|
* Collection of preflight modules to apply.
|
||||||
|
* @type Collection<Module>
|
||||||
|
*/
|
||||||
protected preflight: Collection<Module> = new Collection<Module>()
|
protected preflight: Collection<Module> = new Collection<Module>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module considered to be the main handler.
|
||||||
|
* @type Module
|
||||||
|
*/
|
||||||
protected inflight?: Module
|
protected inflight?: Module
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of postflight modules to apply.
|
||||||
|
* @type Collection<Module>
|
||||||
|
*/
|
||||||
protected postflight: Collection<Module> = new Collection<Module>()
|
protected postflight: Collection<Module> = new Collection<Module>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the incoming request, applying the preflight modules, inflight module, then postflight modules.
|
||||||
|
* @param {Request} request
|
||||||
|
* @return Promise<Request>
|
||||||
|
*/
|
||||||
public async handle(request: Request): Promise<Request> {
|
public async handle(request: Request): Promise<Request> {
|
||||||
const logger = this.make(Logging)
|
const logger = this.make(Logging)
|
||||||
|
|
||||||
@ -47,6 +77,11 @@ export default class Kernel extends AppClass {
|
|||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a fluent interface for registering the given kernel module.
|
||||||
|
* @param {Instantiable<Module>} module
|
||||||
|
* @return ModuleRegistrationFluency
|
||||||
|
*/
|
||||||
public register(module: Instantiable<Module>): ModuleRegistrationFluency {
|
public register(module: Instantiable<Module>): ModuleRegistrationFluency {
|
||||||
this.make(Logging).verbose(`Registering HTTP kernel module: ${module.name}`)
|
this.make(Logging).verbose(`Registering HTTP kernel module: ${module.name}`)
|
||||||
return {
|
return {
|
||||||
|
@ -2,15 +2,33 @@ import {Request} from '../Request.ts'
|
|||||||
import Kernel from './Kernel.ts'
|
import Kernel from './Kernel.ts'
|
||||||
import AppClass from '../../lifecycle/AppClass.ts'
|
import AppClass from '../../lifecycle/AppClass.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for HTTP kernel modules.
|
||||||
|
* @extends AppClass
|
||||||
|
*/
|
||||||
export default class Module extends AppClass {
|
export default class Module extends AppClass {
|
||||||
|
/**
|
||||||
|
* Returns true if the given module should be applied to the incoming request.
|
||||||
|
* @param {Request} request
|
||||||
|
* @return Promise<boolean>
|
||||||
|
*/
|
||||||
public async match(request: Request): Promise<boolean> {
|
public async match(request: Request): Promise<boolean> {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the module to the incoming request.
|
||||||
|
* @param {Request} request
|
||||||
|
* @return Promise<Request>
|
||||||
|
*/
|
||||||
public async apply(request: Request): Promise<Request> {
|
public async apply(request: Request): Promise<Request> {
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register this module with the given HTTP kernel.
|
||||||
|
* @param {Kernel} kernel
|
||||||
|
*/
|
||||||
public static register(kernel: Kernel) {
|
public static register(kernel: Kernel) {
|
||||||
kernel.register(this).before()
|
kernel.register(this).before()
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ import ResponseFactory from '../../response/ResponseFactory.ts'
|
|||||||
import {http, error} from '../../response/helpers.ts'
|
import {http, error} from '../../response/helpers.ts'
|
||||||
import {HTTPStatus} from '../../../const/http.ts'
|
import {HTTPStatus} from '../../../const/http.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP kernel module to apply route handlers to the incoming request.
|
||||||
|
* @extends Module
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class ApplyRouteHandlers extends Module {
|
export default class ApplyRouteHandlers extends Module {
|
||||||
public static register(kernel: Kernel) {
|
public static register(kernel: Kernel) {
|
||||||
@ -19,6 +23,11 @@ export default class ApplyRouteHandlers extends Module {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the route handler to the request.
|
||||||
|
* @param {Request} request
|
||||||
|
* @return Promise<Request>
|
||||||
|
*/
|
||||||
public async apply(request: Request): Promise<Request> {
|
public async apply(request: Request): Promise<Request> {
|
||||||
if ( !request.route ) { // Route not found
|
if ( !request.route ) { // Route not found
|
||||||
const factory = http(HTTPStatus.NOT_FOUND)
|
const factory = http(HTTPStatus.NOT_FOUND)
|
||||||
|
@ -6,6 +6,10 @@ import SessionManager from '../../session/SessionManager.ts'
|
|||||||
import {Logging} from '../../../service/logging/Logging.ts'
|
import {Logging} from '../../../service/logging/Logging.ts'
|
||||||
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP kernel module to retrieve and inject the session into the request.
|
||||||
|
* @extends Module
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class InjectSession extends Module {
|
export default class InjectSession extends Module {
|
||||||
public static register(kernel: Kernel) {
|
public static register(kernel: Kernel) {
|
||||||
@ -19,6 +23,10 @@ export default class InjectSession extends Module {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup or create the session object and inject it into the request.
|
||||||
|
* @param {Request} request
|
||||||
|
*/
|
||||||
public async apply(request: Request): Promise<Request> {
|
public async apply(request: Request): Promise<Request> {
|
||||||
if ( request.session ) return request
|
if ( request.session ) return request
|
||||||
|
|
||||||
|
@ -8,6 +8,10 @@ import Config from '../../../unit/Config.ts'
|
|||||||
import ActivatedRoute from '../../routing/ActivatedRoute.ts'
|
import ActivatedRoute from '../../routing/ActivatedRoute.ts'
|
||||||
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP kernel middleware to resolve and mount the registered route onto the request.
|
||||||
|
* @extends Module
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class MountActivatedRoute extends Module {
|
export default class MountActivatedRoute extends Module {
|
||||||
public static register(kernel: Kernel) {
|
public static register(kernel: Kernel) {
|
||||||
@ -22,6 +26,10 @@ export default class MountActivatedRoute extends Module {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and resolve the route and mount it into the request object.
|
||||||
|
* @param {Request} request
|
||||||
|
*/
|
||||||
public async apply(request: Request): Promise<Request> {
|
public async apply(request: Request): Promise<Request> {
|
||||||
let incoming = this.routing.resolve([request.path])
|
let incoming = this.routing.resolve([request.path])
|
||||||
this.logger.info(`${request.method} ${incoming}`)
|
this.logger.info(`${request.method} ${incoming}`)
|
||||||
|
@ -2,11 +2,19 @@ import Module from '../Module.ts'
|
|||||||
import Kernel from '../Kernel.ts'
|
import Kernel from '../Kernel.ts'
|
||||||
import {Request} from '../../Request.ts'
|
import {Request} from '../../Request.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the session data before closing the request.
|
||||||
|
* @extends Module
|
||||||
|
*/
|
||||||
export default class PersistSession extends Module {
|
export default class PersistSession extends Module {
|
||||||
public static register(kernel: Kernel) {
|
public static register(kernel: Kernel) {
|
||||||
kernel.register(this).last()
|
kernel.register(this).last()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the session.
|
||||||
|
* @param {Request} request
|
||||||
|
*/
|
||||||
public async apply(request: Request): Promise<Request> {
|
public async apply(request: Request): Promise<Request> {
|
||||||
await request.session.persist()
|
await request.session.persist()
|
||||||
return request
|
return request
|
||||||
|
@ -2,12 +2,20 @@ import Module from '../Module.ts'
|
|||||||
import Kernel from '../Kernel.ts'
|
import Kernel from '../Kernel.ts'
|
||||||
import {Request} from '../../Request.ts'
|
import {Request} from '../../Request.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP kernel module to call the request's prepare method.
|
||||||
|
* @extends Module
|
||||||
|
*/
|
||||||
export default class PrepareRequest extends Module {
|
export default class PrepareRequest extends Module {
|
||||||
|
|
||||||
public static register(kernel: Kernel) {
|
public static register(kernel: Kernel) {
|
||||||
kernel.register(this).first()
|
kernel.register(this).first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the request for Daton processing.
|
||||||
|
* @param {Request} request
|
||||||
|
*/
|
||||||
public async apply(request: Request): Promise<Request> {
|
public async apply(request: Request): Promise<Request> {
|
||||||
await request.prepare()
|
await request.prepare()
|
||||||
return request
|
return request
|
||||||
|
@ -4,6 +4,10 @@ import {Request} from '../../Request.ts'
|
|||||||
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
||||||
import Config from '../../../unit/Config.ts'
|
import Config from '../../../unit/Config.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the default Daton headers to the outgoing response.
|
||||||
|
* @extends Module
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class SetDatonHeaders extends Module {
|
export default class SetDatonHeaders extends Module {
|
||||||
public static register(kernel: Kernel) {
|
public static register(kernel: Kernel) {
|
||||||
@ -16,6 +20,10 @@ export default class SetDatonHeaders extends Module {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the outgoing response headers.
|
||||||
|
* @param request
|
||||||
|
*/
|
||||||
public async apply(request: Request): Promise<Request> {
|
public async apply(request: Request): Promise<Request> {
|
||||||
const text = this.config.get('server.powered_by.text', 'Daton')
|
const text = this.config.get('server.powered_by.text', 'Daton')
|
||||||
request.response.headers.set('X-Powered-By', text)
|
request.response.headers.set('X-Powered-By', text)
|
||||||
|
@ -6,6 +6,10 @@ import Utility from '../../../service/utility/Utility.ts'
|
|||||||
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
||||||
import {Logging} from '../../../service/logging/Logging.ts'
|
import {Logging} from '../../../service/logging/Logging.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP kernel module to set the Daton session cookie, if it doesn't exist.
|
||||||
|
* @extends Module
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class SetSessionCookie extends Module {
|
export default class SetSessionCookie extends Module {
|
||||||
|
|
||||||
@ -19,6 +23,10 @@ export default class SetSessionCookie extends Module {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If one doesn't exist, generate and set the daton.session cookie.
|
||||||
|
* @param {Request} request
|
||||||
|
*/
|
||||||
public async apply(request: Request): Promise<Request> {
|
public async apply(request: Request): Promise<Request> {
|
||||||
if ( !(await request.cookies.has('daton.session')) ) {
|
if ( !(await request.cookies.has('daton.session')) ) {
|
||||||
const cookie = `${this.utility.uuid()}-${this.utility.uuid()}`
|
const cookie = `${this.utility.uuid()}-${this.utility.uuid()}`
|
||||||
|
Loading…
Reference in New Issue
Block a user