Implement scopes on models and support interacting with them via ModelBuilder
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
d92c8b5409
commit
0a9dd30909
@ -69,6 +69,13 @@ export abstract class AbstractBuilder<T> extends AppClass {
|
|||||||
*/
|
*/
|
||||||
public abstract getResultIterable(): AbstractResultIterable<T>
|
public abstract getResultIterable(): AbstractResultIterable<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a copy of this builder with its values finalized.
|
||||||
|
*/
|
||||||
|
public finalize(): AbstractBuilder<T> {
|
||||||
|
return this.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clone the current query to a new AbstractBuilder instance with the same properties.
|
* Clone the current query to a new AbstractBuilder instance with the same properties.
|
||||||
*/
|
*/
|
||||||
@ -489,7 +496,7 @@ export abstract class AbstractBuilder<T> extends AppClass {
|
|||||||
throw new ErrorWithContext(`No connection specified to execute update query.`)
|
throw new ErrorWithContext(`No connection specified to execute update query.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = this.registeredConnection.dialect().renderUpdate(this, data)
|
const query = this.registeredConnection.dialect().renderUpdate(this.finalize(), data)
|
||||||
return this.registeredConnection.query(query)
|
return this.registeredConnection.query(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,7 +522,7 @@ export abstract class AbstractBuilder<T> extends AppClass {
|
|||||||
throw new ErrorWithContext(`No connection specified to execute update query.`)
|
throw new ErrorWithContext(`No connection specified to execute update query.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = this.registeredConnection.dialect().renderDelete(this)
|
const query = this.registeredConnection.dialect().renderDelete(this.finalize())
|
||||||
return this.registeredConnection.query(query)
|
return this.registeredConnection.query(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +555,7 @@ export abstract class AbstractBuilder<T> extends AppClass {
|
|||||||
throw new ErrorWithContext(`No connection specified to execute update query.`)
|
throw new ErrorWithContext(`No connection specified to execute update query.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = this.registeredConnection.dialect().renderInsert(this, rowOrRows)
|
const query = this.registeredConnection.dialect().renderInsert(this.finalize(), rowOrRows)
|
||||||
return this.registeredConnection.query(query)
|
return this.registeredConnection.query(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,7 +567,7 @@ export abstract class AbstractBuilder<T> extends AppClass {
|
|||||||
throw new ErrorWithContext(`No connection specified to execute update query.`)
|
throw new ErrorWithContext(`No connection specified to execute update query.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = this.registeredConnection.dialect().renderExistential(this)
|
const query = this.registeredConnection.dialect().renderExistential(this.finalize())
|
||||||
const result = await this.registeredConnection.query(query)
|
const result = await this.registeredConnection.query(query)
|
||||||
return Boolean(result.rows.first())
|
return Boolean(result.rows.first())
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,6 @@ export class Builder extends AbstractBuilder<QueryRow> {
|
|||||||
throw new ErrorWithContext(`No connection specified to fetch iterator for query.`)
|
throw new ErrorWithContext(`No connection specified to fetch iterator for query.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container.getContainer().make<ResultIterable>(ResultIterable, this, this.registeredConnection)
|
return Container.getContainer().make<ResultIterable>(ResultIterable, this.finalize(), this.registeredConnection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ export * from './model/relation/HasOne'
|
|||||||
export * from './model/relation/HasMany'
|
export * from './model/relation/HasMany'
|
||||||
export * from './model/relation/decorators'
|
export * from './model/relation/decorators'
|
||||||
|
|
||||||
|
export * from './model/scope/Scope'
|
||||||
|
export * from './model/scope/ActiveScope'
|
||||||
|
|
||||||
export * from './support/SessionModel'
|
export * from './support/SessionModel'
|
||||||
export * from './support/ORMSession'
|
export * from './support/ORMSession'
|
||||||
export * from './support/CacheModel'
|
export * from './support/CacheModel'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {ModelKey, QueryRow, QuerySource} from '../types'
|
import {ModelKey, QueryRow, QuerySource} from '../types'
|
||||||
import {Container, Inject, Instantiable, StaticClass} from '../../di'
|
import {Container, Inject, Instantiable, isInstantiable, StaticClass} from '../../di'
|
||||||
import {DatabaseService} from '../DatabaseService'
|
import {DatabaseService} from '../DatabaseService'
|
||||||
import {ModelBuilder} from './ModelBuilder'
|
import {ModelBuilder} from './ModelBuilder'
|
||||||
import {getFieldsMeta, ModelField} from './Field'
|
import {getFieldsMeta, ModelField} from './Field'
|
||||||
@ -21,6 +21,7 @@ import {Relation, RelationValue} from './relation/Relation'
|
|||||||
import {HasOne} from './relation/HasOne'
|
import {HasOne} from './relation/HasOne'
|
||||||
import {HasMany} from './relation/HasMany'
|
import {HasMany} from './relation/HasMany'
|
||||||
import {HasOneOrMany} from './relation/HasOneOrMany'
|
import {HasOneOrMany} from './relation/HasOneOrMany'
|
||||||
|
import {Scope, ScopeClosure} from './scope/Scope'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base for classes that are mapped to tables in a database.
|
* Base for classes that are mapped to tables in a database.
|
||||||
@ -87,6 +88,12 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
|||||||
*/
|
*/
|
||||||
protected static masks: string[] = []
|
protected static masks: string[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relations that should be eager-loaded by default.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected with: (keyof T)[] = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The original row fetched from the database.
|
* The original row fetched from the database.
|
||||||
* @protected
|
* @protected
|
||||||
@ -105,6 +112,8 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
|||||||
*/
|
*/
|
||||||
public relationCache: Collection<{ accessor: string | symbol, relation: Relation<T, any, any> }> = new Collection<{accessor: string | symbol; relation: Relation<T, any, any>}>()
|
public relationCache: Collection<{ accessor: string | symbol, relation: Relation<T, any, any> }> = new Collection<{accessor: string | symbol; relation: Relation<T, any, any>}>()
|
||||||
|
|
||||||
|
protected scopes: Collection<{ accessor: string | Instantiable<Scope>, scope: ScopeClosure }> = new Collection<{accessor: string | Instantiable<Scope>; scope: ScopeClosure}>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the table name for this model.
|
* Get the table name for this model.
|
||||||
*/
|
*/
|
||||||
@ -165,6 +174,14 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
|||||||
builder.field(field.databaseKey)
|
builder.field(field.databaseKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for ( const relation of this.prototype.with ) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
builder.with(relation)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.withScopes(this.prototype.scopes)
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,6 +334,12 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
|||||||
builder.field(field.databaseKey)
|
builder.field(field.databaseKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for ( const relation of this.with ) {
|
||||||
|
builder.with(relation)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.withScopes(this.scopes)
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1013,7 +1036,7 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
|||||||
* @param name
|
* @param name
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected getRelation<T2 extends Model<T2>>(name: keyof this): Relation<T, T2, RelationValue<T2>> {
|
public getRelation<T2 extends Model<T2>>(name: keyof this): Relation<T, T2, RelationValue<T2>> {
|
||||||
const relFn = this[name]
|
const relFn = this[name]
|
||||||
|
|
||||||
if ( relFn instanceof Relation ) {
|
if ( relFn instanceof Relation ) {
|
||||||
@ -1029,4 +1052,60 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
|||||||
|
|
||||||
throw new TypeError(`Cannot get relation of name: ${name}. Method does not return a Relation.`)
|
throw new TypeError(`Cannot get relation of name: ${name}. Method does not return a Relation.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a scope on the model.
|
||||||
|
* @param scope
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected scope(scope: Instantiable<Scope> | ScopeClosure): this {
|
||||||
|
if ( isInstantiable(scope) ) {
|
||||||
|
if ( !this.hasScope(scope) ) {
|
||||||
|
this.scopes.push({
|
||||||
|
accessor: scope,
|
||||||
|
scope: builder => (this.make<Scope>(scope)).apply(builder),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.scopes.push({
|
||||||
|
accessor: uuid4(),
|
||||||
|
scope,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a scope on the model with a specific name.
|
||||||
|
* @param name
|
||||||
|
* @param scope
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected namedScope(name: string, scope: Instantiable<Scope> | ScopeClosure): this {
|
||||||
|
if ( isInstantiable(scope) ) {
|
||||||
|
if ( !this.hasScope(scope) ) {
|
||||||
|
this.scopes.push({
|
||||||
|
accessor: name,
|
||||||
|
scope: builder => (this.make<Scope>(scope)).apply(builder),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.scopes.push({
|
||||||
|
accessor: name,
|
||||||
|
scope,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the current model has a scope with the given identifier.
|
||||||
|
* @param name
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected hasScope(name: string | Instantiable<Scope>): boolean {
|
||||||
|
return Boolean(this.scopes.firstWhere('accessor', '=', name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,16 @@ import {ModelResultIterable} from './ModelResultIterable'
|
|||||||
import {Collection} from '../../util'
|
import {Collection} from '../../util'
|
||||||
import {ConstraintOperator, ModelKey, ModelKeys} from '../types'
|
import {ConstraintOperator, ModelKey, ModelKeys} from '../types'
|
||||||
import {EscapeValue} from '../dialect/SQLDialect'
|
import {EscapeValue} from '../dialect/SQLDialect'
|
||||||
|
import {Scope, ScopeClosure} from './scope/Scope'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the abstract builder whose results yield instances of a given Model, `T`.
|
* Implementation of the abstract builder whose results yield instances of a given Model, `T`.
|
||||||
*/
|
*/
|
||||||
export class ModelBuilder<T extends Model<T>> extends AbstractBuilder<T> {
|
export class ModelBuilder<T extends Model<T>> extends AbstractBuilder<T> {
|
||||||
|
protected eagerLoadRelations: (keyof T)[] = []
|
||||||
|
|
||||||
|
protected appliedScopes: Collection<{ accessor: string | Instantiable<Scope>, scope: ScopeClosure }> = new Collection<{accessor: string | Instantiable<Scope>; scope: ScopeClosure}>()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
/** The model class that is created for results of this query. */
|
/** The model class that is created for results of this query. */
|
||||||
protected readonly ModelClass: StaticClass<T, typeof Model> & Instantiable<T>,
|
protected readonly ModelClass: StaticClass<T, typeof Model> & Instantiable<T>,
|
||||||
@ -18,12 +23,27 @@ export class ModelBuilder<T extends Model<T>> extends AbstractBuilder<T> {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public withScopes(scopes: Collection<{ accessor: string | Instantiable<Scope>, scope: ScopeClosure }>): this {
|
||||||
|
this.appliedScopes = scopes.clone()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
public getNewInstance(): AbstractBuilder<T> {
|
public getNewInstance(): AbstractBuilder<T> {
|
||||||
return this.app().make<ModelBuilder<T>>(ModelBuilder, this.ModelClass)
|
return this.app().make<ModelBuilder<T>>(ModelBuilder, this.ModelClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getResultIterable(): AbstractResultIterable<T> {
|
public getResultIterable(): AbstractResultIterable<T> {
|
||||||
return this.app().make<ModelResultIterable<T>>(ModelResultIterable, this, this.registeredConnection, this.ModelClass)
|
return this.app().make<ModelResultIterable<T>>(ModelResultIterable, this.finalize(), this.registeredConnection, this.ModelClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a copy of this builder with all of its values finalized.
|
||||||
|
* @override to apply scopes
|
||||||
|
*/
|
||||||
|
public finalize(): AbstractBuilder<T> {
|
||||||
|
const inst = super.finalize()
|
||||||
|
this.appliedScopes.each(rec => rec.scope(inst))
|
||||||
|
return inst
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,6 +72,42 @@ export class ModelBuilder<T extends Model<T>> extends AbstractBuilder<T> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a relation to be eager-loaded.
|
||||||
|
* @param relationName
|
||||||
|
*/
|
||||||
|
public with(relationName: keyof T): this {
|
||||||
|
if ( !this.eagerLoadRelations.includes(relationName) ) {
|
||||||
|
// Try to load the Relation so we fail if the name is invalid
|
||||||
|
this.make<T>(this.ModelClass).getRelation(relationName)
|
||||||
|
this.eagerLoadRelations.push(relationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all global scopes from this query.
|
||||||
|
*/
|
||||||
|
public withoutGlobalScopes(): this {
|
||||||
|
this.appliedScopes = new Collection<{accessor: string | Instantiable<Scope>; scope: ScopeClosure}>()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific scope from this query by its identifier.
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
public withoutGlobalScope(name: string | Instantiable<Scope>): this {
|
||||||
|
this.appliedScopes = this.appliedScopes.where('accessor', '=', name)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the list of relations to eager-load. */
|
||||||
|
public getEagerLoadedRelations(): (keyof T)[] {
|
||||||
|
return [...this.eagerLoadRelations]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given some format of keys of the model, try to normalize them to a flat array.
|
* Given some format of keys of the model, try to normalize them to a flat array.
|
||||||
* @param keys
|
* @param keys
|
||||||
@ -66,4 +122,15 @@ export class ModelBuilder<T extends Model<T>> extends AbstractBuilder<T> {
|
|||||||
|
|
||||||
return [keys]
|
return [keys]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a copy of this builder.
|
||||||
|
* @override to add implementation-specific pass-alongs.
|
||||||
|
*/
|
||||||
|
public clone(): ModelBuilder<T> {
|
||||||
|
const inst = super.clone() as ModelBuilder<T>
|
||||||
|
inst.eagerLoadRelations = [...this.eagerLoadRelations]
|
||||||
|
inst.appliedScopes = this.appliedScopes.clone()
|
||||||
|
return inst
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import {Connection} from '../connection/Connection'
|
|||||||
import {ModelBuilder} from './ModelBuilder'
|
import {ModelBuilder} from './ModelBuilder'
|
||||||
import {Container, Instantiable} from '../../di'
|
import {Container, Instantiable} from '../../di'
|
||||||
import {QueryRow} from '../types'
|
import {QueryRow} from '../types'
|
||||||
import {Collection} from '../../util'
|
import {collect, Collection} from '../../util'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the result iterable that returns query results as instances of the defined model.
|
* Implementation of the result iterable that returns query results as instances of the defined model.
|
||||||
@ -28,13 +28,17 @@ export class ModelResultIterable<T extends Model<T>> extends AbstractResultItera
|
|||||||
const row = (await this.connection.query(query)).rows.first()
|
const row = (await this.connection.query(query)).rows.first()
|
||||||
|
|
||||||
if ( row ) {
|
if ( row ) {
|
||||||
return this.inflateRow(row)
|
const inflated = await this.inflateRow(row)
|
||||||
|
await this.processEagerLoads(collect([inflated]))
|
||||||
|
return inflated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async range(start: number, end: number): Promise<Collection<T>> {
|
async range(start: number, end: number): Promise<Collection<T>> {
|
||||||
const query = this.connection.dialect().renderRangedSelect(this.selectSQL, start, end)
|
const query = this.connection.dialect().renderRangedSelect(this.selectSQL, start, end)
|
||||||
return (await this.connection.query(query)).rows.promiseMap<T>(row => this.inflateRow(row))
|
const inflated = await (await this.connection.query(query)).rows.promiseMap<T>(row => this.inflateRow(row))
|
||||||
|
await this.processEagerLoads(inflated)
|
||||||
|
return inflated
|
||||||
}
|
}
|
||||||
|
|
||||||
async count(): Promise<number> {
|
async count(): Promise<number> {
|
||||||
@ -45,7 +49,9 @@ export class ModelResultIterable<T extends Model<T>> extends AbstractResultItera
|
|||||||
|
|
||||||
async all(): Promise<Collection<T>> {
|
async all(): Promise<Collection<T>> {
|
||||||
const result = await this.connection.query(this.selectSQL)
|
const result = await this.connection.query(this.selectSQL)
|
||||||
return result.rows.promiseMap<T>(row => this.inflateRow(row))
|
const inflated = await result.rows.promiseMap<T>(row => this.inflateRow(row))
|
||||||
|
await this.processEagerLoads(inflated)
|
||||||
|
return inflated
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,6 +64,30 @@ export class ModelResultIterable<T extends Model<T>> extends AbstractResultItera
|
|||||||
.assumeFromSource(row)
|
.assumeFromSource(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eager-load eager-loaded relations for the models in the query result.
|
||||||
|
* @param results
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected async processEagerLoads(results: Collection<T>): Promise<void> {
|
||||||
|
const eagers = this.builder.getEagerLoadedRelations()
|
||||||
|
const model = this.make<T>(this.ModelClass)
|
||||||
|
|
||||||
|
for ( const name of eagers ) {
|
||||||
|
// TODO support nested eager loads?
|
||||||
|
|
||||||
|
const relation = model.getRelation(name)
|
||||||
|
const select = relation.buildEagerQuery(this.builder, results)
|
||||||
|
|
||||||
|
const allRelated = await select.get().collect()
|
||||||
|
allRelated.each(result => {
|
||||||
|
const resultRelation = result.getRelation(name as any)
|
||||||
|
const resultRelated = resultRelation.matchResults(allRelated as any)
|
||||||
|
resultRelation.setValue(resultRelated as any)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clone(): ModelResultIterable<T> {
|
clone(): ModelResultIterable<T> {
|
||||||
return new ModelResultIterable(this.builder, this.connection, this.ModelClass)
|
return new ModelResultIterable(this.builder, this.connection, this.ModelClass)
|
||||||
}
|
}
|
||||||
|
11
src/orm/model/scope/ActiveScope.ts
Normal file
11
src/orm/model/scope/ActiveScope.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {Scope} from './Scope'
|
||||||
|
import {AbstractBuilder} from '../../builder/AbstractBuilder'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic scope to limit results where `active` = true.
|
||||||
|
*/
|
||||||
|
export class ActiveScope extends Scope {
|
||||||
|
apply(query: AbstractBuilder<any>): void {
|
||||||
|
query.where('active', '=', true)
|
||||||
|
}
|
||||||
|
}
|
18
src/orm/model/scope/Scope.ts
Normal file
18
src/orm/model/scope/Scope.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import {Injectable, InjectionAware} from '../../../di'
|
||||||
|
import {Awaitable} from '../../../util'
|
||||||
|
import {AbstractBuilder} from '../../builder/AbstractBuilder'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A closure that takes a query and applies some scope to it.
|
||||||
|
*/
|
||||||
|
export type ScopeClosure = (query: AbstractBuilder<any>) => Awaitable<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for scopes that can be applied to queries.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export abstract class Scope extends InjectionAware {
|
||||||
|
|
||||||
|
abstract apply(query: AbstractBuilder<any>): Awaitable<void>
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import {Collection} from './Collection'
|
import {Collection} from './Collection'
|
||||||
|
import {InjectionAware} from '../../di'
|
||||||
|
|
||||||
export type MaybeIterationItem<T> = { done: boolean, value?: T }
|
export type MaybeIterationItem<T> = { done: boolean, value?: T }
|
||||||
export type ChunkCallback<T> = (items: Collection<T>) => any
|
export type ChunkCallback<T> = (items: Collection<T>) => any
|
||||||
@ -9,7 +10,7 @@ export class StopIteration extends Error {}
|
|||||||
* Abstract class representing an iterable, lazy-loaded dataset.
|
* Abstract class representing an iterable, lazy-loaded dataset.
|
||||||
* @abstract
|
* @abstract
|
||||||
*/
|
*/
|
||||||
export abstract class Iterable<T> {
|
export abstract class Iterable<T> extends InjectionAware {
|
||||||
/**
|
/**
|
||||||
* The current index of the iterable.
|
* The current index of the iterable.
|
||||||
* @type number
|
* @type number
|
||||||
|
Loading…
Reference in New Issue
Block a user