UNPKG

@aws-amplify/datastore

Version:

AppSyncLocal support for aws-amplify

804 lines (704 loc) • 18 kB
import { ModelInstanceCreator } from './datastore/datastore'; import { exhaustiveCheck, isAWSDate, isAWSTime, isAWSDateTime, isAWSTimestamp, isAWSEmail, isAWSJSON, isAWSURL, isAWSPhone, isAWSIPAddress, } from './util'; import { PredicateAll } from './predicates'; import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api-graphql'; import { Adapter } from './storage/adapter'; //#region Schema types export type Schema = UserSchema & { version: string; }; export type UserSchema = { models: SchemaModels; nonModels?: SchemaNonModels; relationships?: RelationshipType; keys?: ModelKeys; enums: SchemaEnums; modelTopologicalOrdering?: Map<string, string[]>; }; export type InternalSchema = { namespaces: SchemaNamespaces; version: string; }; export type SchemaNamespaces = Record<string, SchemaNamespace>; export type SchemaNamespace = UserSchema & { name: string; }; export type SchemaModels = Record<string, SchemaModel>; export type SchemaModel = { name: string; pluralName: string; attributes?: ModelAttributes; fields: ModelFields; syncable?: boolean; }; export function isSchemaModel(obj: any): obj is SchemaModel { return obj && (<SchemaModel>obj).pluralName !== undefined; } export type SchemaNonModels = Record<string, SchemaNonModel>; export type SchemaNonModel = { name: string; fields: ModelFields; }; type SchemaEnums = Record<string, SchemaEnum>; type SchemaEnum = { name: string; values: string[]; }; export type ModelAssociation = AssociatedWith | TargetNameAssociation; type AssociatedWith = { connectionType: 'HAS_MANY' | 'HAS_ONE'; associatedWith: string; targetName?: string; }; export function isAssociatedWith(obj: any): obj is AssociatedWith { return obj && obj.associatedWith; } type TargetNameAssociation = { connectionType: 'BELONGS_TO'; targetName: string; }; export function isTargetNameAssociation( obj: any ): obj is TargetNameAssociation { return obj && obj.targetName; } export type ModelAttributes = ModelAttribute[]; type ModelAttribute = { type: string; properties?: Record<string, any> }; export type ModelAuthRule = { allow: string; provider?: string; operations?: string[]; ownerField?: string; identityClaim?: string; groups?: string[]; groupClaim?: string; groupsField?: string; }; export type ModelAttributeAuth = { type: 'auth'; properties: { rules: ModelAuthRule[]; }; }; export function isModelAttributeAuth( attr: ModelAttribute ): attr is ModelAttributeAuth { return ( attr.type === 'auth' && attr.properties && attr.properties.rules && attr.properties.rules.length > 0 ); } type ModelAttributeKey = { type: 'key'; properties: { name?: string; fields: string[]; }; }; type ModelAttributePrimaryKey = { type: 'key'; properties: { fields: string[]; }; }; type ModelAttributeCompositeKey = { type: 'key'; properties: { name: string; fields: [string, string, string, string?, string?]; }; }; export function isModelAttributeKey( attr: ModelAttribute ): attr is ModelAttributeKey { return ( attr.type === 'key' && attr.properties && attr.properties.fields && attr.properties.fields.length > 0 ); } export function isModelAttributePrimaryKey( attr: ModelAttribute ): attr is ModelAttributePrimaryKey { return isModelAttributeKey(attr) && attr.properties.name === undefined; } export function isModelAttributeCompositeKey( attr: ModelAttribute ): attr is ModelAttributeCompositeKey { return ( isModelAttributeKey(attr) && attr.properties.name !== undefined && attr.properties.fields.length > 2 ); } export type ModelAttributeAuthProperty = { allow: ModelAttributeAuthAllow; identityClaim?: string; groupClaim?: string; groups?: string[]; operations?: string[]; ownerField?: string; provider?: ModelAttributeAuthProvider; }; export enum ModelAttributeAuthAllow { CUSTOM = 'custom', OWNER = 'owner', GROUPS = 'groups', PRIVATE = 'private', PUBLIC = 'public', } export enum ModelAttributeAuthProvider { FUNCTION = 'function', USER_POOLS = 'userPools', OIDC = 'oidc', IAM = 'iam', API_KEY = 'apiKey', } export type ModelFields = Record<string, ModelField>; export enum GraphQLScalarType { ID, String, Int, Float, Boolean, AWSDate, AWSTime, AWSDateTime, AWSTimestamp, AWSEmail, AWSJSON, AWSURL, AWSPhone, AWSIPAddress, } export namespace GraphQLScalarType { export function getJSType( scalar: keyof Omit< typeof GraphQLScalarType, 'getJSType' | 'getValidationFunction' > ): 'string' | 'number' | 'boolean' | 'object' { switch (scalar) { case 'Boolean': return 'boolean'; case 'ID': case 'String': case 'AWSDate': case 'AWSTime': case 'AWSDateTime': case 'AWSEmail': case 'AWSURL': case 'AWSPhone': case 'AWSIPAddress': return 'string'; case 'Int': case 'Float': case 'AWSTimestamp': return 'number'; case 'AWSJSON': return 'object'; default: exhaustiveCheck(scalar as never); } } export function getValidationFunction( scalar: keyof Omit< typeof GraphQLScalarType, 'getJSType' | 'getValidationFunction' > ): ((val: string | number) => boolean) | undefined { switch (scalar) { case 'AWSDate': return isAWSDate; case 'AWSTime': return isAWSTime; case 'AWSDateTime': return isAWSDateTime; case 'AWSTimestamp': return isAWSTimestamp; case 'AWSEmail': return isAWSEmail; case 'AWSJSON': return isAWSJSON; case 'AWSURL': return isAWSURL; case 'AWSPhone': return isAWSPhone; case 'AWSIPAddress': return isAWSIPAddress; default: return undefined; } } } export type AuthorizationRule = { identityClaim: string; ownerField: string; provider: 'userPools' | 'oidc' | 'iam' | 'apiKey'; groupClaim: string; groups: [string]; authStrategy: 'owner' | 'groups' | 'private' | 'public'; areSubscriptionsPublic: boolean; }; export function isGraphQLScalarType( obj: any ): obj is keyof Omit< typeof GraphQLScalarType, 'getJSType' | 'getValidationFunction' > { return obj && GraphQLScalarType[obj] !== undefined; } export type ModelFieldType = { model: string }; export function isModelFieldType(obj: any): obj is ModelFieldType { const modelField: keyof ModelFieldType = 'model'; if (obj && obj[modelField]) return true; return false; } export type NonModelFieldType = { nonModel: string }; export function isNonModelFieldType(obj: any): obj is NonModelFieldType { const typeField: keyof NonModelFieldType = 'nonModel'; if (obj && obj[typeField]) return true; return false; } type EnumFieldType = { enum: string }; export function isEnumFieldType(obj: any): obj is EnumFieldType { const modelField: keyof EnumFieldType = 'enum'; if (obj && obj[modelField]) return true; return false; } export type ModelField = { name: string; type: | keyof Omit< typeof GraphQLScalarType, 'getJSType' | 'getValidationFunction' > | ModelFieldType | NonModelFieldType | EnumFieldType; isArray: boolean; isRequired?: boolean; isReadOnly?: boolean; isArrayNullable?: boolean; association?: ModelAssociation; attributes?: ModelAttributes[]; }; //#endregion //#region Model definition export type NonModelTypeConstructor<T> = { new (init: T): T; }; // Class for model export type PersistentModelConstructor< T extends PersistentModel, K extends PersistentModelMetaData = { readOnlyFields: 'createdAt' | 'updatedAt'; } > = { new (init: ModelInit<T, K>): T; copyOf(src: T, mutator: (draft: MutableModel<T, K>) => void): T; }; export type TypeConstructorMap = Record< string, PersistentModelConstructor<any> | NonModelTypeConstructor<any> >; // Instance of model export type PersistentModelMetaData = { readOnlyFields: string; }; export type PersistentModel = Readonly<{ id: string } & Record<string, any>>; export type ModelInit< T, K extends PersistentModelMetaData = { readOnlyFields: 'createdAt' | 'updatedAt'; } > = Omit<T, 'id' | K['readOnlyFields']>; type DeepWritable<T> = { -readonly [P in keyof T]: T[P] extends TypeName<T[P]> ? T[P] : DeepWritable<T[P]>; }; export type MutableModel< T extends Record<string, any>, K extends PersistentModelMetaData = { readOnlyFields: 'createdAt' | 'updatedAt'; } // This provides Intellisense with ALL of the properties, regardless of read-only // but will throw a linting error if trying to overwrite a read-only property > = DeepWritable<Omit<T, 'id' | K['readOnlyFields']>> & Readonly<Pick<T, 'id' | K['readOnlyFields']>>; export type ModelInstanceMetadata = { id: string; _version: number; _lastChangedAt: number; _deleted: boolean; }; //#endregion //#region Subscription messages export enum OpType { INSERT = 'INSERT', UPDATE = 'UPDATE', DELETE = 'DELETE', } export type SubscriptionMessage<T extends PersistentModel> = { opType: OpType; element: T; model: PersistentModelConstructor<T>; condition: PredicatesGroup<T> | null; }; export type DataStoreSnapshot<T extends PersistentModel> = { items: T[]; isSynced: boolean; }; //#endregion //#region Predicates export type PredicateExpression<M extends PersistentModel, FT> = TypeName< FT > extends keyof MapTypeToOperands<FT> ? ( operator: keyof MapTypeToOperands<FT>[TypeName<FT>], // make the operand type match the type they're trying to filter on operand: MapTypeToOperands<FT>[TypeName<FT>][keyof MapTypeToOperands< FT >[TypeName<FT>]] ) => ModelPredicate<M> : never; type EqualityOperators<T> = { ne: T; eq: T; }; type ScalarNumberOperators<T> = EqualityOperators<T> & { le: T; lt: T; ge: T; gt: T; }; type NumberOperators<T> = ScalarNumberOperators<T> & { between: [T, T]; }; type StringOperators<T> = ScalarNumberOperators<T> & { beginsWith: T; contains: T; notContains: T; }; type BooleanOperators<T> = EqualityOperators<T>; type ArrayOperators<T> = { contains: T; notContains: T; }; export type AllOperators = NumberOperators<any> & StringOperators<any> & ArrayOperators<any>; type MapTypeToOperands<T> = { number: NumberOperators<NonNullable<T>>; string: StringOperators<NonNullable<T>>; boolean: BooleanOperators<NonNullable<T>>; 'number[]': ArrayOperators<number>; 'string[]': ArrayOperators<string>; 'boolean[]': ArrayOperators<boolean>; }; type TypeName<T> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends string[] ? 'string[]' : T extends number[] ? 'number[]' : T extends boolean[] ? 'boolean[]' : never; export type PredicateGroups<T extends PersistentModel> = { and: ( predicate: (predicate: ModelPredicate<T>) => ModelPredicate<T> ) => ModelPredicate<T>; or: ( predicate: (predicate: ModelPredicate<T>) => ModelPredicate<T> ) => ModelPredicate<T>; not: ( predicate: (predicate: ModelPredicate<T>) => ModelPredicate<T> ) => ModelPredicate<T>; }; export type ModelPredicate<M extends PersistentModel> = { [K in keyof M]-?: PredicateExpression<M, NonNullable<M[K]>>; } & PredicateGroups<M>; export type ProducerModelPredicate<M extends PersistentModel> = ( condition: ModelPredicate<M> ) => ModelPredicate<M>; export type PredicatesGroup<T extends PersistentModel> = { type: keyof PredicateGroups<T>; predicates: (PredicateObject<T> | PredicatesGroup<T>)[]; }; export function isPredicateObj<T extends PersistentModel>( obj: any ): obj is PredicateObject<T> { return obj && (<PredicateObject<T>>obj).field !== undefined; } export function isPredicateGroup<T extends PersistentModel>( obj: any ): obj is PredicatesGroup<T> { return obj && (<PredicatesGroup<T>>obj).type !== undefined; } export type PredicateObject<T extends PersistentModel> = { field: keyof T; operator: keyof AllOperators; operand: any; }; export enum QueryOne { FIRST, LAST, } export type GraphQLField = { [field: string]: { [operator: string]: string | number | [number, number]; }; }; export type GraphQLCondition = Partial< | GraphQLField | { and: [GraphQLCondition]; or: [GraphQLCondition]; not: GraphQLCondition; } >; export type GraphQLFilter = Partial< | GraphQLField | { and: GraphQLFilter[]; } | { or: GraphQLFilter[]; } | { not: GraphQLFilter; } >; //#endregion //#region Pagination export type ProducerPaginationInput<T extends PersistentModel> = { sort?: ProducerSortPredicate<T>; limit?: number; page?: number; }; export type ObserveQueryOptions<T extends PersistentModel> = Pick< ProducerPaginationInput<T>, 'sort' >; export type PaginationInput<T extends PersistentModel> = { sort?: SortPredicate<T>; limit?: number; page?: number; }; export type ProducerSortPredicate<M extends PersistentModel> = ( condition: SortPredicate<M> ) => SortPredicate<M>; export type SortPredicate<T extends PersistentModel> = { [K in keyof T]-?: SortPredicateExpression<T, NonNullable<T[K]>>; }; export type SortPredicateExpression<M extends PersistentModel, FT> = TypeName< FT > extends keyof MapTypeToOperands<FT> ? (sortDirection: keyof typeof SortDirection) => SortPredicate<M> : never; export enum SortDirection { ASCENDING = 'ASCENDING', DESCENDING = 'DESCENDING', } export type SortPredicatesGroup< T extends PersistentModel > = SortPredicateObject<T>[]; export type SortPredicateObject<T extends PersistentModel> = { field: keyof T; sortDirection: keyof typeof SortDirection; }; //#endregion //#region System Components export type SystemComponent = { setUp( schema: InternalSchema, namespaceResolver: NamespaceResolver, modelInstanceCreator: ModelInstanceCreator, getModelConstructorByModelName: ( namsespaceName: string, modelName: string ) => PersistentModelConstructor<any>, appId: string ): Promise<void>; }; export type NamespaceResolver = ( modelConstructor: PersistentModelConstructor<any> ) => string; export type ControlMessageType<T> = { type: T; data?: any; }; //#endregion //#region Relationship types export type RelationType = { fieldName: string; modelName: string; relationType: 'HAS_ONE' | 'HAS_MANY' | 'BELONGS_TO'; targetName?: string; associatedWith?: string; }; export type RelationshipType = { [modelName: string]: { indexes: string[]; relationTypes: RelationType[] }; }; //#endregion //#region Key type export type KeyType = { primaryKey?: string[]; compositeKeys?: Set<string>[]; }; export type ModelKeys = { [modelName: string]: KeyType; }; //#endregion //#region DataStore config types export type DataStoreConfig = { DataStore?: { authModeStrategyType?: AuthModeStrategyType; conflictHandler?: ConflictHandler; // default : retry until client wins up to x times errorHandler?: (error: SyncError) => void; // default : logger.warn maxRecordsToSync?: number; // merge syncPageSize?: number; fullSyncInterval?: number; syncExpressions?: SyncExpression[]; authProviders?: AuthProviders; storageAdapter?: Adapter; }; authModeStrategyType?: AuthModeStrategyType; conflictHandler?: ConflictHandler; // default : retry until client wins up to x times errorHandler?: (error: SyncError) => void; // default : logger.warn maxRecordsToSync?: number; // merge syncPageSize?: number; fullSyncInterval?: number; syncExpressions?: SyncExpression[]; authProviders?: AuthProviders; storageAdapter?: Adapter; }; export type AuthProviders = { functionAuthProvider: () => { token: string } | Promise<{ token: string }>; }; export enum AuthModeStrategyType { DEFAULT = 'DEFAULT', MULTI_AUTH = 'MULTI_AUTH', } export type AuthModeStrategyReturn = | GRAPHQL_AUTH_MODE | GRAPHQL_AUTH_MODE[] | undefined | null; export type AuthModeStrategyParams = { schema: InternalSchema; modelName: string; operation: ModelOperation; }; export type AuthModeStrategy = ( authModeStrategyParams: AuthModeStrategyParams ) => AuthModeStrategyReturn | Promise<AuthModeStrategyReturn>; export enum ModelOperation { CREATE = 'CREATE', READ = 'READ', UPDATE = 'UPDATE', DELETE = 'DELETE', } export type ModelAuthModes = Record< string, { [Property in ModelOperation]: GRAPHQL_AUTH_MODE[]; } >; export type SyncExpression = Promise<{ modelConstructor: any; conditionProducer: (c?: any) => any; }>; /* Adds Intellisense when passing a function | promise that returns a predicate Or just a predicate. E.g., syncExpressions: [ syncExpression(Post, c => c.rating('gt', 5)), OR syncExpression(Post, async () => { return c => c.rating('gt', 5) }), ] */ type Option0 = []; type Option1<T extends PersistentModel> = [ModelPredicate<T> | undefined]; type Option<T extends PersistentModel> = Option0 | Option1<T>; type Lookup<T extends PersistentModel> = { 0: | ProducerModelPredicate<T> | Promise<ProducerModelPredicate<T>> | typeof PredicateAll; 1: ModelPredicate<T> | undefined; }; type ConditionProducer<T extends PersistentModel, A extends Option<T>> = ( ...args: A ) => A['length'] extends keyof Lookup<T> ? Lookup<T>[A['length']] : never; export async function syncExpression< T extends PersistentModel, A extends Option<T> >( modelConstructor: PersistentModelConstructor<T>, conditionProducer: ConditionProducer<T, A> ): Promise<{ modelConstructor: PersistentModelConstructor<T>; conditionProducer: ConditionProducer<T, A>; }> { return { modelConstructor, conditionProducer, }; } export type SyncConflict = { modelConstructor: PersistentModelConstructor<any>; localModel: PersistentModel; remoteModel: PersistentModel; operation: OpType; attempts: number; }; export type SyncError = { message: string; errorType: string; errorInfo: string; localModel: PersistentModel; remoteModel: PersistentModel; operation: string; }; export const DISCARD = Symbol('DISCARD'); export type ConflictHandler = ( conflict: SyncConflict ) => | Promise<PersistentModel | typeof DISCARD> | PersistentModel | typeof DISCARD; export type ErrorHandler = (error: SyncError) => void; export type DeferredCallbackResolverOptions = { callback: () => void; maxInterval?: number; errorHandler?: (error: string) => void; }; export enum LimitTimerRaceResolvedValues { LIMIT = 'LIMIT', TIMER = 'TIMER', } //#endregion