import { Platform } from './models/Platform'; import { WorkItem } from './models/WorkItem'; export class TeaQLNotLoadedError extends Error { readonly details: Record; constructor( readonly root: string, readonly accessPath: string, readonly breakPoint: string, ) { const suggestedFix = `select${breakPoint.charAt(0).toUpperCase()}${breakPoint.slice(1)}(...)`; const details = { error: 'TeaQLNotLoadedError', root, accessPath: accessPath.split('.'), breakPoint, missingPreload: [breakPoint], suggestedFix, severity: 'error', humanMessage: `访问 ${root}.${accessPath} 时缺少预加载。请在查询中加入 ${suggestedFix}`, }; super(JSON.stringify(details)); this.name = 'TeaQLNotLoadedError'; this.details = details; } } function expressionPath(prefix: string, field: string): string { return prefix ? `${prefix}.${field}` : field; } export class ValueExpression { constructor( private readonly value: T | null | undefined, private readonly present = true, private readonly notLoaded?: TeaQLNotLoadedError, ) {} static missing(): ValueExpression { return new ValueExpression(undefined, false); } static notLoaded(error: TeaQLNotLoadedError): ValueExpression { return new ValueExpression(undefined, false, error); } eval(): T | null | undefined { if (this.notLoaded) throw this.notLoaded; return this.present ? this.value : undefined; } isPresent(): boolean { if (this.notLoaded) throw this.notLoaded; return this.present; } orElse(fallback: T): T { const value = this.eval(); return this.present && value != null ? value : fallback; } } export class PlatformExpression { constructor( private readonly value: Platform | null | undefined, private readonly root = 'Platform(null)', private readonly path = '', private readonly notLoaded?: TeaQLNotLoadedError, ) {} eval(): Platform | null | undefined { if (this.notLoaded) throw this.notLoaded; return this.value; } id(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); if (!this.value) return ValueExpression.missing(); const path = expressionPath(this.path, 'id'); if (!this.value.isLoaded('id')) { return ValueExpression.notLoaded( new TeaQLNotLoadedError(this.root, path, 'id'), ); } return new ValueExpression(this.value.id); } name(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); if (!this.value) return ValueExpression.missing(); const path = expressionPath(this.path, 'name'); if (!this.value.isLoaded('name')) { return ValueExpression.notLoaded( new TeaQLNotLoadedError(this.root, path, 'name'), ); } return new ValueExpression(this.value.name); } version(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); if (!this.value) return ValueExpression.missing(); const path = expressionPath(this.path, 'version'); if (!this.value.isLoaded('version')) { return ValueExpression.notLoaded( new TeaQLNotLoadedError(this.root, path, 'version'), ); } return new ValueExpression(this.value.version); } workItemList(): WorkItemListExpression { const path = expressionPath(this.path, 'workItemList'); if (this.notLoaded) { return new WorkItemListExpression([], this.root, path, false, this.notLoaded); } if (!this.value) return WorkItemListExpression.missing(this.root, path); if (!this.value.isLoaded('workItemList')) { return new WorkItemListExpression([], this.root, path, false, new TeaQLNotLoadedError(this.root, path, 'workItemList')); } return new WorkItemListExpression( this.value.workItemList(), this.root, path, ); } } export class WorkItemExpression { constructor( private readonly value: WorkItem | null | undefined, private readonly root = 'WorkItem(null)', private readonly path = '', private readonly notLoaded?: TeaQLNotLoadedError, ) {} eval(): WorkItem | null | undefined { if (this.notLoaded) throw this.notLoaded; return this.value; } id(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); if (!this.value) return ValueExpression.missing(); const path = expressionPath(this.path, 'id'); if (!this.value.isLoaded('id')) { return ValueExpression.notLoaded( new TeaQLNotLoadedError(this.root, path, 'id'), ); } return new ValueExpression(this.value.id); } title(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); if (!this.value) return ValueExpression.missing(); const path = expressionPath(this.path, 'title'); if (!this.value.isLoaded('title')) { return ValueExpression.notLoaded( new TeaQLNotLoadedError(this.root, path, 'title'), ); } return new ValueExpression(this.value.title); } description(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); if (!this.value) return ValueExpression.missing(); const path = expressionPath(this.path, 'description'); if (!this.value.isLoaded('description')) { return ValueExpression.notLoaded( new TeaQLNotLoadedError(this.root, path, 'description'), ); } return new ValueExpression(this.value.description); } version(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); if (!this.value) return ValueExpression.missing(); const path = expressionPath(this.path, 'version'); if (!this.value.isLoaded('version')) { return ValueExpression.notLoaded( new TeaQLNotLoadedError(this.root, path, 'version'), ); } return new ValueExpression(this.value.version); } platformId(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); if (!this.value) return ValueExpression.missing(); const path = expressionPath(this.path, 'platform'); if (!this.value.isLoaded('platform')) { return ValueExpression.notLoaded( new TeaQLNotLoadedError(this.root, path, 'platform'), ); } const reference = this.value.platform; return new ValueExpression( reference instanceof Platform ? reference.id : reference as string | number | undefined, ); } platform(): PlatformExpression { const path = expressionPath(this.path, 'platform'); if (this.notLoaded) { return new PlatformExpression(undefined, this.root, path, this.notLoaded); } if (!this.value) return new PlatformExpression(undefined, this.root, path); if (!this.value.isLoaded('platform')) { return new PlatformExpression(undefined, this.root, path, new TeaQLNotLoadedError(this.root, path, 'platform')); } const relation = this.value.platform; const target = relation == null ? undefined : relation instanceof Platform ? relation : Platform.fromRecord({ id: relation }); return new PlatformExpression(target, this.root, path); } } export class PlatformListExpression { constructor( private readonly items: readonly Platform[], private readonly root = 'Platform(null)', private readonly path = '', private readonly present = true, private readonly notLoaded?: TeaQLNotLoadedError, ) {} static missing(root?: string, path = ''): PlatformListExpression { return new PlatformListExpression([], root, path, false); } size(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); return this.present ? new ValueExpression(this.items.length) : ValueExpression.missing(); } first(): PlatformExpression { return this.get(0); } get(index: number): PlatformExpression { const itemPath = expressionPath(this.path, `get(${index})`); if (this.notLoaded) { return new PlatformExpression(undefined, this.root, itemPath, this.notLoaded); } return !this.present || index < 0 || index >= this.items.length ? new PlatformExpression(undefined, this.root, itemPath) : new PlatformExpression(this.items[index], this.root, itemPath); } } export class WorkItemListExpression { constructor( private readonly items: readonly WorkItem[], private readonly root = 'WorkItem(null)', private readonly path = '', private readonly present = true, private readonly notLoaded?: TeaQLNotLoadedError, ) {} static missing(root?: string, path = ''): WorkItemListExpression { return new WorkItemListExpression([], root, path, false); } size(): ValueExpression { if (this.notLoaded) return ValueExpression.notLoaded(this.notLoaded); return this.present ? new ValueExpression(this.items.length) : ValueExpression.missing(); } first(): WorkItemExpression { return this.get(0); } get(index: number): WorkItemExpression { const itemPath = expressionPath(this.path, `get(${index})`); if (this.notLoaded) { return new WorkItemExpression(undefined, this.root, itemPath, this.notLoaded); } return !this.present || index < 0 || index >= this.items.length ? new WorkItemExpression(undefined, this.root, itemPath) : new WorkItemExpression(this.items[index], this.root, itemPath); } } export class E { static platform( value: Platform | null | undefined, ): PlatformExpression { return new PlatformExpression( value, `Platform(id=${value?.id ?? 'null'})`, ); } static workItem( value: WorkItem | null | undefined, ): WorkItemExpression { return new WorkItemExpression( value, `WorkItem(id=${value?.id ?? 'null'})`, ); } }