platform/packages/presentation/src/pipeline.ts
Andrey Sobolev 4eac1927f0
UBERF-7532: Bulk operations for triggers (#6023)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
2024-07-09 00:04:05 +07:00

356 lines
9.2 KiB
TypeScript

import { Analytics } from '@hcengineering/analytics'
import {
toFindResult,
type Class,
type Client,
type Doc,
type DocumentQuery,
type FindOptions,
type FindResult,
type Hierarchy,
type ModelDb,
type Ref,
type SearchOptions,
type SearchQuery,
type SearchResult,
type Tx,
type TxResult,
type WithLookup
} from '@hcengineering/core'
import { setPlatformStatus, unknownError, type Resource } from '@hcengineering/platform'
/**
* @public
*/
export interface PresentationMiddleware {
next?: PresentationMiddleware
tx: (tx: Tx) => Promise<TxResult>
notifyTx: (...tx: Tx[]) => Promise<void>
findAll: <T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
) => Promise<FindResult<T>>
findOne: <T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
) => Promise<WithLookup<T> | undefined>
subscribe: <T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options: FindOptions<T> | undefined,
refresh: () => void
) => Promise<{
unsubscribe: () => void
query?: DocumentQuery<T>
options?: FindOptions<T>
}>
close: () => Promise<void>
}
/**
* @public
*/
export type PresentationMiddlewareCreator = (client: Client, next?: PresentationMiddleware) => PresentationMiddleware
/**
* @public
*/
export interface PresentationPipeline extends Client, Exclude<PresentationMiddleware, 'next'> {
close: () => Promise<void>
}
/**
* @public
*/
export class PresentationPipelineImpl implements PresentationPipeline {
private head: PresentationMiddleware | undefined
private constructor (readonly client: Client) {}
getHierarchy (): Hierarchy {
return this.client.getHierarchy()
}
getModel (): ModelDb {
return this.client.getModel()
}
async notifyTx (...tx: Tx[]): Promise<void> {
await this.head?.notifyTx(...tx)
}
static create (client: Client, 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<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<FindResult<T>> {
try {
return this.head !== undefined
? await this.head.findAll(_class, query, options)
: await this.client.findAll(_class, query, options)
} catch (err) {
Analytics.handleError(err as Error)
const status = unknownError(err)
await setPlatformStatus(status)
return toFindResult([], -1)
}
}
async searchFulltext (query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
return await this.client.searchFulltext(query, options)
}
async findOne<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<WithLookup<T> | undefined> {
try {
return this.head !== undefined
? await this.head.findOne(_class, query, options)
: await this.client.findOne(_class, query, options)
} catch (err) {
Analytics.handleError(err as Error)
const status = unknownError(err)
await setPlatformStatus(status)
}
}
async subscribe<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options: FindOptions<T> | undefined,
refresh: () => void
): Promise<{
unsubscribe: () => void
query?: DocumentQuery<T>
options?: FindOptions<T>
}> {
return this.head !== undefined
? await this.head.subscribe(_class, query, options, refresh)
: { unsubscribe: () => {} }
}
async tx (tx: Tx): Promise<TxResult> {
try {
if (this.head === undefined) {
return await this.client.tx(tx)
} else {
return await this.head.tx(tx)
}
} catch (err) {
Analytics.handleError(err as Error)
const status = unknownError(err)
await setPlatformStatus(status)
return {}
}
}
async close (): Promise<void> {
if (this.head !== undefined) {
await this.head.close()
return
}
await this.client.close()
}
}
/**
* @public
*/
export abstract class BasePresentationMiddleware {
constructor (
protected readonly client: Client,
readonly next?: PresentationMiddleware
) {}
async provideNotifyTx (...tx: Tx[]): Promise<void> {
await this.next?.notifyTx(...tx)
}
async provideClose (): Promise<void> {
if (this.next !== undefined) {
await this.next.close()
return
}
await this.client.close()
}
async findAll<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<FindResult<T>> {
return await this.provideFindAll(_class, query, options)
}
async findOne<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<WithLookup<T> | undefined> {
return await this.provideFindOne(_class, query, options)
}
async subscribe<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options: FindOptions<T> | undefined,
refresh: () => void
): Promise<{
unsubscribe: () => void
query?: DocumentQuery<T>
options?: FindOptions<T>
}> {
return await this.provideSubscribe(_class, query, options, refresh)
}
protected async provideTx (tx: Tx): Promise<TxResult> {
if (this.next !== undefined) {
return await this.next.tx(tx)
}
return await this.client.tx(tx)
}
protected async provideFindAll<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<FindResult<T>> {
if (this.next !== undefined) {
return await this.next.findAll(_class, query, options)
}
return await this.client.findAll(_class, query, options)
}
protected async provideFindOne<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<WithLookup<T> | undefined> {
if (this.next !== undefined) {
return await this.next.findOne(_class, query, options)
}
return await this.client.findOne(_class, query, options)
}
protected async provideSubscribe<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options: FindOptions<T> | undefined,
refresh: () => void
): Promise<{
unsubscribe: () => void
query?: DocumentQuery<T>
options?: FindOptions<T>
}> {
if (this.next !== undefined) {
return await this.next.subscribe(_class, query, options, refresh)
}
return { unsubscribe: () => {} }
}
}
/**
* @public
*/
export interface PresentationMiddlewareFactory extends Doc {
createPresentationMiddleware: Resource<PresentationMiddlewareCreator>
}
/**
* @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<void> {
await this.provideNotifyTx(...tx)
}
async close (): Promise<void> {
await this.provideClose()
}
async tx (tx: Tx): Promise<TxResult> {
return await this.provideTx(tx)
}
async subscribe<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options: FindOptions<T> | undefined,
refresh: () => void
): Promise<{
unsubscribe: () => void
query?: DocumentQuery<T>
options?: FindOptions<T>
}> {
return await this.provideSubscribe(_class, query, options, refresh)
}
async findAll<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T> | undefined
): Promise<FindResult<T>> {
if (_class == null || typeof query !== 'object' || ('_class' in query && query._class == null)) {
console.error('_class must be specified in query', query)
return toFindResult([], 0)
}
const fQuery = { ...query }
const fOptions = { ...options }
this.optimizeQuery<T>(fQuery, fOptions)
return await this.provideFindAll(_class, fQuery, fOptions)
}
private optimizeQuery<T extends Doc>(fQuery: DocumentQuery<T>, fOptions: FindOptions<T>): void {
if (typeof fQuery._id === 'string' && fOptions.sort !== undefined) {
delete fOptions.sort
}
if (fOptions.lookup !== undefined && Object.keys(fOptions.lookup).length === 0) {
delete fOptions.lookup
}
}
async findOne<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T> | undefined
): Promise<WithLookup<T> | undefined> {
const fQuery = { ...query }
const fOptions = { ...options }
this.optimizeQuery<T>(fQuery, fOptions)
return await this.provideFindOne(_class, fQuery, fOptions)
}
}