import { ModelInstanceCreator } from './datastore/datastore'; import { exhaustiveCheck } from './util'; //#region Schema types export type Schema = UserSchema & { version: string; }; export type UserSchema = { models: SchemaModels; nonModels?: SchemaNonModels; relationships?: RelationshipType; enums: SchemaEnums; modelTopologicalOrdering?: Map; }; export type InternalSchema = { namespaces: SchemaNamespaces; version: string; }; export type SchemaNamespaces = Record; export type SchemaNamespace = UserSchema & { name: string; }; export type SchemaModels = Record; export type SchemaModel = { name: string; pluralName: string; attributes?: ModelAttributes; fields: ModelFields; syncable?: boolean; }; export function isSchemaModel(obj: any): obj is SchemaModel { return obj && (obj).pluralName !== undefined; } export type SchemaNonModels = Record; export type SchemaNonModel = { name: string; fields: ModelFields; }; type SchemaEnums = Record; type SchemaEnum = { name: string; values: string[]; }; export type ModelAssociation = AssociatedWith | TargetNameAssociation; type AssociatedWith = { connectionType: 'HAS_MANY' | 'HAS_ONE'; associatedWith: 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; } type ModelAttributes = ModelAttribute[]; type ModelAttribute = { type: string; properties?: Record }; export type ModelFields = Record; 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 ): 'string' | 'number' | 'boolean' { switch (scalar) { case 'Boolean': return 'boolean'; case 'ID': case 'String': case 'AWSDate': case 'AWSTime': case 'AWSDateTime': case 'AWSEmail': case 'AWSJSON': case 'AWSURL': case 'AWSPhone': case 'AWSIPAddress': return 'string'; case 'Int': case 'Float': case 'AWSTimestamp': return 'number'; default: exhaustiveCheck(scalar); } } } 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 { 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; } type ModelField = { name: string; type: | keyof Omit | ModelFieldType | NonModelFieldType | EnumFieldType; isArray: boolean; isRequired?: boolean; association?: ModelAssociation; attributes?: ModelAttributes[]; }; //#endregion //#region Model definition export type NonModelTypeConstructor = { new (init: T): T; }; export type PersistentModelConstructor = { new (init: ModelInit): T; copyOf(src: T, mutator: (draft: MutableModel) => void): T; }; export type TypeConstructorMap = Record< string, PersistentModelConstructor | NonModelTypeConstructor >; export type PersistentModel = Readonly<{ id: string } & Record>; export type ModelInit = Omit; type DeepWritable = { -readonly [P in keyof T]: T[P] extends TypeName ? T[P] : DeepWritable; }; export type MutableModel = Omit, 'id'>; 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 = { opType: OpType; element: T; model: PersistentModelConstructor; condition: PredicatesGroup | null; }; //#endregion //#region Predicates export type PredicateExpression = TypeName< FT > extends keyof MapTypeToOperands ? ( operator: keyof MapTypeToOperands[TypeName], operand: MapTypeToOperands[TypeName][keyof MapTypeToOperands< FT >[TypeName]] ) => ModelPredicate : never; type EqualityOperators = { ne: T; eq: T; }; type ScalarNumberOperators = EqualityOperators & { le: T; lt: T; ge: T; gt: T; }; type NumberOperators = ScalarNumberOperators & { between: [T, T]; }; type StringOperators = ScalarNumberOperators & { beginsWith: T; contains: T; notContains: T; }; type BooleanOperators = EqualityOperators; type ArrayOperators = { contains: T; notContains: T; }; export type AllOperators = NumberOperators & StringOperators & ArrayOperators; type MapTypeToOperands = { number: NumberOperators>; string: StringOperators>; boolean: BooleanOperators>; 'number[]': ArrayOperators; 'string[]': ArrayOperators; 'boolean[]': ArrayOperators; }; type TypeName = 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 = { and: ( predicate: (predicate: ModelPredicate) => ModelPredicate ) => ModelPredicate; or: ( predicate: (predicate: ModelPredicate) => ModelPredicate ) => ModelPredicate; not: ( predicate: (predicate: ModelPredicate) => ModelPredicate ) => ModelPredicate; }; export type ModelPredicate = { [K in keyof M]-?: PredicateExpression>; } & PredicateGroups; export type ProducerModelPredicate = ( condition: ModelPredicate ) => ModelPredicate; export type PredicatesGroup = { type: keyof PredicateGroups; predicates: (PredicateObject | PredicatesGroup)[]; }; export function isPredicateObj( obj: any ): obj is PredicateObject { return obj && (>obj).field !== undefined; } export function isPredicateGroup( obj: any ): obj is PredicatesGroup { return obj && (>obj).type !== undefined; } export type PredicateObject = { field: keyof T; operator: keyof AllOperators; operand: any; }; export enum QueryOne { FIRST, LAST, } export type GraphQLCondition = Partial< | { [field: string]: { [operator: string]: string | number | [number, number]; }; } | { and: [GraphQLCondition]; or: [GraphQLCondition]; not: GraphQLCondition; } >; //#endregion //#region Pagination export type PaginationInput = { limit?: number; page?: number; }; //#endregion //#region System Components export type SystemComponent = { setUp( schema: InternalSchema, namespaceResolver: NamespaceResolver, modelInstanceCreator: ModelInstanceCreator, getModelConstructorByModelName: ( namsespaceName: string, modelName: string ) => PersistentModelConstructor ): Promise; }; export type NamespaceResolver = ( modelConstructor: PersistentModelConstructor ) => string; export type ControlMessageType = { 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 DataStore config types export type DataStoreConfig = { DataStore?: { 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; }; 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; }; export type SyncConflict = { modelConstructor: PersistentModelConstructor; 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; export type ErrorHandler = (error: SyncError) => void; //#endregion