import { type Class, type Client, type Doc, type DocumentQuery, type FindOptions, type FindResult, type Hierarchy, type ModelDb, type Ref, type Tx, type TxResult, type WithLookup, toFindResult, type SearchQuery, type SearchOptions, type SearchResult, type MeasureClient, type MeasureDoneOperation } from '@hcengineering/core' import { type Resource } from '@hcengineering/platform' /** * @public */ export interface PresentationMiddleware { next?: PresentationMiddleware tx: (tx: Tx) => Promise notifyTx: (tx: Tx) => Promise findAll: ( _class: Ref>, query: DocumentQuery, options?: FindOptions ) => Promise> findOne: ( _class: Ref>, query: DocumentQuery, options?: FindOptions ) => Promise | undefined> subscribe: ( _class: Ref>, query: DocumentQuery, options: FindOptions | undefined, refresh: () => void ) => Promise<{ unsubscribe: () => void query?: DocumentQuery options?: FindOptions }> close: () => Promise } /** * @public */ export type PresentationMiddlewareCreator = (client: Client, next?: PresentationMiddleware) => PresentationMiddleware /** * @public */ export interface PresentationPipeline extends MeasureClient, Exclude { close: () => Promise } /** * @public */ export class PresentationPipelineImpl implements PresentationPipeline { private head: PresentationMiddleware | undefined private constructor (readonly client: MeasureClient) {} getHierarchy (): Hierarchy { return this.client.getHierarchy() } getModel (): ModelDb { return this.client.getModel() } async notifyTx (tx: Tx): Promise { await this.head?.notifyTx(tx) } async measure (operationName: string): Promise { return await this.client.measure(operationName) } static create (client: MeasureClient, constructors: PresentationMiddlewareCreator[]): PresentationPipeline { const pipeline = new PresentationPipelineImpl(client) pipeline.head = pipeline.buildChain(constructors) return pipeline } private buildChain (constructors: PresentationMiddlewareCreator[]): PresentationMiddleware | undefined { let current: PresentationMiddleware | undefined for (let index = constructors.length - 1; index >= 0; index--) { const element = constructors[index] current = element(this.client, current) } return current } async findAll( _class: Ref>, query: DocumentQuery, options?: FindOptions ): Promise> { return this.head !== undefined ? await this.head.findAll(_class, query, options) : await this.client.findAll(_class, query, options) } async searchFulltext (query: SearchQuery, options: SearchOptions): Promise { return await this.client.searchFulltext(query, options) } async findOne( _class: Ref>, query: DocumentQuery, options?: FindOptions ): Promise | undefined> { return this.head !== undefined ? await this.head.findOne(_class, query, options) : await this.client.findOne(_class, query, options) } async subscribe( _class: Ref>, query: DocumentQuery, options: FindOptions | undefined, refresh: () => void ): Promise<{ unsubscribe: () => void query?: DocumentQuery options?: FindOptions }> { return this.head !== undefined ? await this.head.subscribe(_class, query, options, refresh) : { unsubscribe: () => {} } } async tx (tx: Tx): Promise { if (this.head === undefined) { return await this.client.tx(tx) } else { return await this.head.tx(tx) } } async close (): Promise { await this.head?.close() } } /** * @public */ export abstract class BasePresentationMiddleware { constructor ( protected readonly client: Client, readonly next?: PresentationMiddleware ) {} async provideNotifyTx (tx: Tx): Promise { await this.next?.notifyTx(tx) } async provideClose (): Promise { await this.next?.close() } async findAll( _class: Ref>, query: DocumentQuery, options?: FindOptions ): Promise> { return await this.provideFindAll(_class, query, options) } async findOne( _class: Ref>, query: DocumentQuery, options?: FindOptions ): Promise | undefined> { return await this.provideFindOne(_class, query, options) } async subscribe( _class: Ref>, query: DocumentQuery, options: FindOptions | undefined, refresh: () => void ): Promise<{ unsubscribe: () => void query?: DocumentQuery options?: FindOptions }> { return await this.provideSubscribe(_class, query, options, refresh) } protected async provideTx (tx: Tx): Promise { if (this.next !== undefined) { return await this.next.tx(tx) } return await this.client.tx(tx) } protected async provideFindAll( _class: Ref>, query: DocumentQuery, options?: FindOptions ): Promise> { if (this.next !== undefined) { return await this.next.findAll(_class, query, options) } return await this.client.findAll(_class, query, options) } protected async provideFindOne( _class: Ref>, query: DocumentQuery, options?: FindOptions ): Promise | undefined> { if (this.next !== undefined) { return await this.next.findOne(_class, query, options) } return await this.client.findOne(_class, query, options) } protected async provideSubscribe( _class: Ref>, query: DocumentQuery, options: FindOptions | undefined, refresh: () => void ): Promise<{ unsubscribe: () => void query?: DocumentQuery options?: FindOptions }> { if (this.next !== undefined) { return await this.next.subscribe(_class, query, options, refresh) } return { unsubscribe: () => {} } } } /** * @public */ export interface PresentationMiddlewareFactory extends Doc { createPresentationMiddleware: Resource } /** * @public */ export class OptimizeQueryMiddleware extends BasePresentationMiddleware implements PresentationMiddleware { private constructor (client: Client, next?: PresentationMiddleware) { super(client, next) } static create (client: Client, next?: PresentationMiddleware): OptimizeQueryMiddleware { return new OptimizeQueryMiddleware(client, next) } async notifyTx (tx: Tx): Promise { await this.provideNotifyTx(tx) } async close (): Promise { await this.provideClose() } async tx (tx: Tx): Promise { return await this.provideTx(tx) } async subscribe( _class: Ref>, query: DocumentQuery, options: FindOptions | undefined, refresh: () => void ): Promise<{ unsubscribe: () => void query?: DocumentQuery options?: FindOptions }> { return await this.provideSubscribe(_class, query, options, refresh) } async findAll( _class: Ref>, query: DocumentQuery, options?: FindOptions | undefined ): Promise> { if (_class == null || typeof query !== 'object' || ('_class' in query && query._class == null)) { console.error('_class must be specified in query', query) return toFindResult([], 0) } return await this.provideFindAll(_class, query, options) } }