From 7fdd7a0c9d9e4dd46ee4fb6a9c36966237a364c9 Mon Sep 17 00:00:00 2001 From: Hein Jeong <73264629+hein-j@users.noreply.github.com> Date: Thu, 7 Jul 2022 09:13:51 -0700 Subject: [PATCH] chore: add util to map datastore schema to generic (#498) Co-authored-by: Hein Jeong --- packages/codegen-ui/index.ts | 1 + .../lib/__tests__/__utils__/mock-schemas.ts | 671 ++++++++++++++++++ .../__tests__/generic-from-datastore.test.ts | 117 +++ .../form-to-component.ts | 4 +- .../helpers/field-type-map.ts | 4 +- .../codegen-ui/lib/generic-from-datastore.ts | 145 ++++ packages/codegen-ui/lib/types/data.ts | 52 +- 7 files changed, 987 insertions(+), 7 deletions(-) create mode 100644 packages/codegen-ui/lib/__tests__/generic-from-datastore.test.ts create mode 100644 packages/codegen-ui/lib/generic-from-datastore.ts diff --git a/packages/codegen-ui/index.ts b/packages/codegen-ui/index.ts index d18736e50..0f1f5a350 100644 --- a/packages/codegen-ui/index.ts +++ b/packages/codegen-ui/index.ts @@ -21,6 +21,7 @@ export * from './lib/render-component-response'; export * from './lib/framework-output-manager'; export * from './lib/template-renderer-factory'; export * from './lib/generate-form-definition'; +export * from './lib/generic-from-datastore'; export * from './lib/renderer-helper'; export * from './lib/validation-helper'; diff --git a/packages/codegen-ui/lib/__tests__/__utils__/mock-schemas.ts b/packages/codegen-ui/lib/__tests__/__utils__/mock-schemas.ts index 2e78c2995..d8d2c643f 100644 --- a/packages/codegen-ui/lib/__tests__/__utils__/mock-schemas.ts +++ b/packages/codegen-ui/lib/__tests__/__utils__/mock-schemas.ts @@ -112,3 +112,674 @@ export const postSchema: Schema = { nonModels: {}, version: '000000', }; + +export const schemaWithRelationships: Schema = { + models: { + PrimaryCareGiver: { + name: 'PrimaryCareGiver', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + Child: { + name: 'Child', + isArray: false, + type: { + model: 'Child', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: 'id', + targetName: 'primaryCareGiverChildId', + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + primaryCareGiverChildId: { + name: 'primaryCareGiverChildId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'PrimaryCareGivers', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Child: { + name: 'Child', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Children', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Student: { + name: 'Student', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + Teachers: { + name: 'Teachers', + isArray: true, + type: { + model: 'StudentTeacher', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: 'student', + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Students', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Teacher: { + name: 'Teacher', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + students: { + name: 'students', + isArray: true, + type: { + model: 'StudentTeacher', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: 'teacher', + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Teachers', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Lock: { + name: 'Lock', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + Key: { + name: 'Key', + isArray: false, + type: { + model: 'Key', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: 'Lock', + targetName: 'lockKeyId', + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + lockKeyId: { + name: 'lockKeyId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'Locks', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Key: { + name: 'Key', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + Lock: { + name: 'Lock', + isArray: false, + type: { + model: 'Lock', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetName: 'keyLockId', + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Keys', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Owner: { + name: 'Owner', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + Dog: { + name: 'Dog', + isArray: true, + type: { + model: 'Dog', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: 'ownerID', + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Owners', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Dog: { + name: 'Dog', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + ownerID: { + name: 'ownerID', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Dogs', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + name: 'byOwner', + fields: ['ownerID'], + }, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + StudentTeacher: { + name: 'StudentTeacher', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + student: { + name: 'student', + isArray: false, + type: { + model: 'Student', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetName: 'studentID', + }, + }, + teacher: { + name: 'teacher', + isArray: false, + type: { + model: 'Teacher', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetName: 'teacherID', + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'StudentTeachers', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + name: 'byStudent', + fields: ['studentID'], + }, + }, + { + type: 'key', + properties: { + name: 'byTeacher', + fields: ['teacherID'], + }, + }, + ], + }, + }, + enums: {}, + nonModels: {}, + version: '3ea7de9ef8e765b48c0a53e3e45735a3', +}; + +export const schemaWithEnums: Schema = { + models: {}, + enums: { + City: { + name: 'City', + values: ['SAN_FRANCISCO', 'NEW_YORK'], + }, + }, + nonModels: {}, + version: '3ea7de9ef8e765b48c0a53e3e45735a3', +}; + +export const schemaWithNonModels: Schema = { + models: {}, + enums: {}, + nonModels: { + Reactions: { + name: 'Reactions', + fields: { + ball: { + name: 'ball', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + fireworks: { + name: 'fireworks', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + }, + }, + Misc: { + name: 'Misc', + fields: { + quotes: { + name: 'quotes', + isArray: true, + type: 'String', + isRequired: false, + attributes: [], + }, + }, + }, + }, + version: '38a1a46479c6cd75d21439d7f3122c1d', +}; diff --git a/packages/codegen-ui/lib/__tests__/generic-from-datastore.test.ts b/packages/codegen-ui/lib/__tests__/generic-from-datastore.test.ts new file mode 100644 index 000000000..235189265 --- /dev/null +++ b/packages/codegen-ui/lib/__tests__/generic-from-datastore.test.ts @@ -0,0 +1,117 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { getGenericFromDataStore } from '../generic-from-datastore'; +import { schemaWithEnums, schemaWithNonModels, schemaWithRelationships } from './__utils__/mock-schemas'; + +describe('getGenericFromDataStore', () => { + it('should map fields', () => { + const genericSchema = getGenericFromDataStore(schemaWithRelationships); + expect(genericSchema.models.Child.fields).toStrictEqual({ + id: { + dataType: 'ID', + required: true, + readOnly: false, + isArray: false, + }, + name: { + dataType: 'String', + required: false, + readOnly: false, + isArray: false, + }, + createdAt: { + dataType: 'AWSDateTime', + required: false, + readOnly: true, + isArray: false, + }, + updatedAt: { + dataType: 'AWSDateTime', + required: false, + readOnly: true, + isArray: false, + }, + }); + }); + + it('should map relationships', () => { + const genericSchema = getGenericFromDataStore(schemaWithRelationships); + + expect(genericSchema.models.PrimaryCareGiver.fields.Child.relationship).toStrictEqual({ + type: 'HAS_ONE', + relatedModelName: 'Child', + }); + + expect(genericSchema.models.PrimaryCareGiver.fields.primaryCareGiverChildId.relationship).toStrictEqual({ + type: 'HAS_ONE', + relatedModelName: 'Child', + }); + + expect(genericSchema.models.Student.fields.Teachers.relationship).toStrictEqual({ + type: 'HAS_MANY', + relatedModelName: 'Teacher', + }); + + expect(genericSchema.models.Teacher.fields.students.relationship).toStrictEqual({ + type: 'HAS_MANY', + relatedModelName: 'Student', + }); + + expect(genericSchema.models.Lock.fields.Key.relationship).toStrictEqual({ + type: 'HAS_ONE', + relatedModelName: 'Key', + }); + + expect(genericSchema.models.Lock.fields.lockKeyId.relationship).toStrictEqual({ + type: 'HAS_ONE', + relatedModelName: 'Key', + }); + + expect(genericSchema.models.Key.fields.Lock.relationship).toStrictEqual({ + type: 'BELONGS_TO', + relatedModelName: 'Lock', + }); + + expect(genericSchema.models.Owner.fields.Dog.relationship).toStrictEqual({ + type: 'HAS_MANY', + relatedModelName: 'Dog', + }); + + expect(genericSchema.models.Dog.fields.ownerID.relationship).toStrictEqual({ + type: 'HAS_ONE', + relatedModelName: 'Owner', + }); + }); + + it('should map enums', () => { + const genericSchema = getGenericFromDataStore(schemaWithEnums); + + expect(genericSchema.enums).toStrictEqual(schemaWithEnums.enums); + }); + + it('should map nonModels', () => { + const genericSchema = getGenericFromDataStore(schemaWithNonModels); + expect(genericSchema.nonModels).toStrictEqual({ + Reactions: { + fields: { + ball: { dataType: 'String', required: false, readOnly: false, isArray: false }, + fireworks: { dataType: 'String', required: false, readOnly: false, isArray: false }, + }, + }, + Misc: { fields: { quotes: { dataType: 'String', required: false, readOnly: false, isArray: true } } }, + }); + }); +}); diff --git a/packages/codegen-ui/lib/generate-form-definition/form-to-component.ts b/packages/codegen-ui/lib/generate-form-definition/form-to-component.ts index e033a8f7e..2e3e0cf7b 100644 --- a/packages/codegen-ui/lib/generate-form-definition/form-to-component.ts +++ b/packages/codegen-ui/lib/generate-form-definition/form-to-component.ts @@ -13,10 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { SchemaModel, ModelFields, isGraphQLScalarType } from '@aws-amplify/datastore'; import { - SchemaModel, - ModelFields, - isGraphQLScalarType, StudioComponent, StudioForm, StudioComponentChild, diff --git a/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts b/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts index 7b9dcfba1..147318fef 100644 --- a/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts +++ b/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts @@ -81,11 +81,11 @@ export const FIELD_TYPE_MAP: { defaultComponent: 'PhoneNumberField', supportedComponents: new Set(['PhoneNumberField']), }, - enum: { + Enum: { defaultComponent: 'SelectField', supportedComponents: new Set(['RadioGroupField', 'SelectField']), }, - nonModel: { + Relationship: { defaultComponent: 'SelectField', supportedComponents: new Set(['SelectField']), }, diff --git a/packages/codegen-ui/lib/generic-from-datastore.ts b/packages/codegen-ui/lib/generic-from-datastore.ts new file mode 100644 index 000000000..1fbe15360 --- /dev/null +++ b/packages/codegen-ui/lib/generic-from-datastore.ts @@ -0,0 +1,145 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Schema as DataStoreSchema, ModelField } from '@aws-amplify/datastore'; +import { InvalidInputError } from './errors'; +import { GenericDataField, GenericDataSchema } from './types'; + +function getGenericDataField(field: ModelField): GenericDataField { + return { + dataType: field.type, + required: !!field.isRequired, + readOnly: !!field.isReadOnly, + isArray: field.isArray, + }; +} + +function addRelationship( + fields: { + [modelName: string]: { [fieldName: string]: GenericDataField['relationship'] }; + }, + modelName: string, + fieldName: string, + relationship: GenericDataField['relationship'], +) { + // handle prototype-pollution vulnerability + if (modelName === '__proto__') { + throw new InvalidInputError('Invalid model name "__proto__"'); + } + if (!fields[modelName]) { + // eslint-disable-next-line no-param-reassign + fields[modelName] = {}; + } + + // eslint-disable-next-line no-param-reassign + fields[modelName][fieldName] = relationship; +} + +export function getGenericFromDataStore(dataStoreSchema: DataStoreSchema): GenericDataSchema { + const genericSchema: GenericDataSchema = { + dataSourceType: 'DataStore', + models: {}, + enums: {}, + nonModels: {}, + }; + + const fieldsWithImplicitRelationships: { + [modelName: string]: { [fieldName: string]: GenericDataField['relationship'] }; + } = {}; + + Object.values(dataStoreSchema.models).forEach((model) => { + const genericFields: { [fieldName: string]: GenericDataField } = {}; + + Object.values(model.fields).forEach((field) => { + const genericField = getGenericDataField(field); + + // handle relationships + if (typeof field.type === 'object' && 'model' in field.type) { + if (field.association) { + const relationshipType = field.association.connectionType; + + let relatedModelName = field.type.model; + + if (relationshipType === 'HAS_MANY' && 'associatedWith' in field.association) { + const associatedModel = dataStoreSchema.models[field.type.model]; + const associatedFieldName = field.association.associatedWith; + const associatedField = associatedModel.fields[associatedFieldName]; + + // if the associated model is a join table, update relatedModelName to the actual related model + if ( + typeof associatedField.type === 'object' && + 'model' in associatedField.type && + associatedField.type.model === model.name + ) { + const relatedJoinField = Object.values(associatedModel.fields).find( + (joinField) => + joinField.name !== associatedFieldName && + typeof joinField.type === 'object' && + 'model' in joinField.type, + ); + if (relatedJoinField && typeof relatedJoinField.type === 'object' && 'model' in relatedJoinField.type) { + relatedModelName = relatedJoinField.type.model; + } + // if the associated model is not a join table, note implicit relationship for associated field + } else { + addRelationship(fieldsWithImplicitRelationships, associatedModel.name, associatedFieldName, { + type: 'HAS_ONE', + relatedModelName: model.name, + }); + } + } + + // note implicit relationship for associated field within same model + if (relationshipType === 'HAS_ONE' && 'targetName' in field.association && field.association.targetName) { + addRelationship(fieldsWithImplicitRelationships, model.name, field.association.targetName, { + type: relationshipType, + relatedModelName, + }); + } + + genericField.relationship = { type: relationshipType, relatedModelName }; + } + } + + genericFields[field.name] = genericField; + }); + + genericSchema.models[model.name] = { fields: genericFields }; + }); + + Object.entries(fieldsWithImplicitRelationships).forEach(([modelName, fields]) => { + Object.entries(fields).forEach(([fieldName, relationship]) => { + const field = genericSchema.models[modelName]?.fields[fieldName]; + if (field) { + field.relationship = relationship; + } + }); + }); + + genericSchema.enums = dataStoreSchema.enums; + + if (dataStoreSchema.nonModels) { + Object.values(dataStoreSchema.nonModels).forEach((nonModel) => { + const genericFields: { [fieldName: string]: GenericDataField } = {}; + Object.values(nonModel.fields).forEach((field) => { + const genericField = getGenericDataField(field); + genericFields[field.name] = genericField; + }); + genericSchema.nonModels[nonModel.name] = { fields: genericFields }; + }); + } + + return genericSchema; +} diff --git a/packages/codegen-ui/lib/types/data.ts b/packages/codegen-ui/lib/types/data.ts index 9d169712a..7b1011110 100644 --- a/packages/codegen-ui/lib/types/data.ts +++ b/packages/codegen-ui/lib/types/data.ts @@ -16,8 +16,7 @@ // exporting types and scalar functions from aws-amplify // as these will be used when loading in dataschema for form generation -export type { SchemaModel, ModelFields } from '@aws-amplify/datastore'; -export { isGraphQLScalarType } from '@aws-amplify/datastore'; +export type { SchemaModel } from '@aws-amplify/datastore'; type FieldType = string | { model: string } | { nonModel: string } | { enum: string }; @@ -28,3 +27,52 @@ export type DataStoreModelField = { isRequired: boolean; isArray: boolean; }; + +export type DataFieldDataType = + | 'ID' + | 'String' + | 'Int' + | 'Float' + | 'AWSDate' + | 'AWSTime' + | 'AWSDateTime' + | 'AWSTimestamp' + | 'AWSEmail' + | 'AWSURL' + | 'AWSIPAddress' + | 'Boolean' + | 'AWSJSON' + | 'AWSPhone' + | { enum: string } + | { model: string } + | { nonModel: string }; + +export type GenericDataField = { + dataType: DataFieldDataType; + + required: boolean; + + readOnly: boolean; + + isArray: boolean; + + relationship?: { + type: 'HAS_ONE' | 'HAS_MANY' | 'BELONGS_TO'; + + relatedModelName: string; + }; +}; + +export type GenericDataModel = { + fields: { [fieldName: string]: GenericDataField }; +}; + +export type GenericDataSchema = { + dataSourceType: 'DataStore'; + + models: { [modelName: string]: GenericDataModel }; + + enums: { [enumName: string]: { values: string[] } }; + + nonModels: { [nonModelName: string]: GenericDataModel }; +};