@aws-amplify/datastore
Version:
AppSyncLocal support for aws-amplify
171 lines (142 loc) • 4.26 kB
text/typescript
import {
AllOperators,
ModelPredicate,
PersistentModel,
PredicateExpression,
PredicateGroups,
PredicatesGroup,
ProducerModelPredicate,
SchemaModel,
} from '../types';
import { exhaustiveCheck } from '../util';
export { ModelSortPredicateCreator } from './sort';
const predicatesAllSet = new WeakSet<ProducerModelPredicate<any>>();
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 = <ProducerModelPredicate<any>>(c => c);
predicatesAllSet.add(predicate);
return <typeof PredicateAll>(<unknown>predicate);
}
}
export class ModelPredicateCreator {
private static predicateGroupsMap = new WeakMap<
ModelPredicate<any>,
PredicatesGroup<any>
>();
private static createPredicateBuilder<T extends PersistentModel>(
modelDefinition: SchemaModel
) {
const { name: modelName } = modelDefinition;
const fieldNames = new Set<keyof T>(Object.keys(modelDefinition.fields));
let handler: ProxyHandler<ModelPredicate<T>>;
const predicate = new Proxy(
{} as ModelPredicate<T>,
(handler = {
get(
_target,
propertyKey,
receiver: ModelPredicate<T>
): PredicateExpression<T, any> {
const groupType = propertyKey as keyof PredicateGroups<T>;
switch (groupType) {
case 'and':
case 'or':
case 'not':
const result: PredicateExpression<T, any> = (
newPredicate: (criteria: ModelPredicate<T>) => ModelPredicate<T>
) => {
const group: PredicatesGroup<T> = {
type: groupType,
predicates: [],
};
// Create a new recorder
const tmpPredicateRecorder = new Proxy(
{} as ModelPredicate<T>,
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<T, any> = (
operator: keyof AllOperators,
operand: any
) => {
ModelPredicateCreator.predicateGroupsMap
.get(receiver)
.predicates.push({ field, operator, operand });
return receiver;
};
return result;
},
})
);
const group: PredicatesGroup<T> = {
type: 'and',
predicates: [],
};
ModelPredicateCreator.predicateGroupsMap.set(predicate, group);
return predicate;
}
static isValidPredicate<T extends PersistentModel>(
predicate: any
): predicate is ModelPredicate<T> {
return ModelPredicateCreator.predicateGroupsMap.has(predicate);
}
static getPredicates<T extends PersistentModel>(
predicate: ModelPredicate<T>,
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<T extends PersistentModel>(
modelDefinition: SchemaModel,
existing: ProducerModelPredicate<T>
) {
if (!existing || !modelDefinition) {
return undefined;
}
return existing(
ModelPredicateCreator.createPredicateBuilder(modelDefinition)
);
}
static createForId<T extends PersistentModel>(
modelDefinition: SchemaModel,
id: string
) {
return ModelPredicateCreator.createPredicateBuilder<T>(modelDefinition).id(
'eq',
<any>id
);
}
}