import { AllOperators, ModelPredicate, PersistentModel, PredicateExpression, PredicateGroups, PredicatesGroup, ProducerModelPredicate, SchemaModel, } from '../types'; import { exhaustiveCheck } from '../util'; export { ModelSortPredicateCreator } from './sort'; const predicatesAllSet = new WeakSet>(); export function isPredicatesAll( predicate: any ): predicate is typeof PredicateAll { return predicatesAllSet.has(predicate); } // This symbol is not used at runtime, only its type (unique symbol) export const PredicateAll = Symbol('A predicate that matches all records'); export class Predicates { public static get ALL(): typeof PredicateAll { const predicate = >(c => c); predicatesAllSet.add(predicate); return (predicate); } } export class ModelPredicateCreator { private static predicateGroupsMap = new WeakMap< ModelPredicate, PredicatesGroup >(); private static createPredicateBuilder( modelDefinition: SchemaModel ) { const { name: modelName } = modelDefinition; const fieldNames = new Set(Object.keys(modelDefinition.fields)); let handler: ProxyHandler>; const predicate = new Proxy( {} as ModelPredicate, (handler = { get( _target, propertyKey, receiver: ModelPredicate ): PredicateExpression { const groupType = propertyKey as keyof PredicateGroups; switch (groupType) { case 'and': case 'or': case 'not': const result: PredicateExpression = ( newPredicate: (criteria: ModelPredicate) => ModelPredicate ) => { const group: PredicatesGroup = { type: groupType, predicates: [], }; // Create a new recorder const tmpPredicateRecorder = new Proxy( {} as ModelPredicate, handler ); // Set the recorder group ModelPredicateCreator.predicateGroupsMap.set( tmpPredicateRecorder, group ); // Apply the predicates to the recorder (this is the step that records the changes) newPredicate(tmpPredicateRecorder); // Push the group to the top-level recorder ModelPredicateCreator.predicateGroupsMap .get(receiver) .predicates.push(group); return receiver; }; return result; default: exhaustiveCheck(groupType, false); } const field = propertyKey as keyof T; if (!fieldNames.has(field)) { throw new Error( `Invalid field for model. field: ${field}, model: ${modelName}` ); } const result: PredicateExpression = ( operator: keyof AllOperators, operand: any ) => { ModelPredicateCreator.predicateGroupsMap .get(receiver) .predicates.push({ field, operator, operand }); return receiver; }; return result; }, }) ); const group: PredicatesGroup = { type: 'and', predicates: [], }; ModelPredicateCreator.predicateGroupsMap.set(predicate, group); return predicate; } static isValidPredicate( predicate: any ): predicate is ModelPredicate { return ModelPredicateCreator.predicateGroupsMap.has(predicate); } static getPredicates( predicate: ModelPredicate, throwOnInvalid: boolean = true ) { if (throwOnInvalid && !ModelPredicateCreator.isValidPredicate(predicate)) { throw new Error('The predicate is not valid'); } return ModelPredicateCreator.predicateGroupsMap.get(predicate); } // transforms cb-style predicate into Proxy static createFromExisting( modelDefinition: SchemaModel, existing: ProducerModelPredicate ) { if (!existing || !modelDefinition) { return undefined; } return existing( ModelPredicateCreator.createPredicateBuilder(modelDefinition) ); } static createForId( modelDefinition: SchemaModel, id: string ) { return ModelPredicateCreator.createPredicateBuilder(modelDefinition).id( 'eq', id ); } }