From 210f9e0fcc31239c32e5ef16ad745b6e099b457b Mon Sep 17 00:00:00 2001 From: Albert Winberg Date: Sun, 13 Aug 2023 21:57:47 +0000 Subject: [PATCH] fix: use custom primary key name when fetching record for update form --- ...studio-ui-codegen-react-forms.test.ts.snap | 646 +++++++++++++++++- .../studio-ui-codegen-react-forms.test.ts | 17 + .../forms/form-renderer-helper/cta-props.ts | 16 +- .../lib/forms/react-form-renderer.ts | 12 +- .../forms/CustomKeyModelUpdateForm.json | 12 + .../models/custom-key-model/schema.graphql | 10 + .../models/custom-key-model/schema.json | 166 +++++ 7 files changed, 863 insertions(+), 16 deletions(-) create mode 100644 packages/codegen-ui/example-schemas/models/custom-key-model/forms/CustomKeyModelUpdateForm.json create mode 100644 packages/codegen-ui/example-schemas/models/custom-key-model/schema.graphql create mode 100644 packages/codegen-ui/example-schemas/models/custom-key-model/schema.json diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap index 888467c5..c3004726 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap @@ -12532,7 +12532,7 @@ export default function UpdateCPKTeacherForm(props) { ? ( await API.graphql({ query: getCPKTeacher, - variables: { id: specialTeacherIdProp }, + variables: { specialTeacherId: specialTeacherIdProp }, }) )?.data?.getCPKTeacher : cPKTeacherModelProp; @@ -18819,6 +18819,650 @@ export default function CommentUpdateForm(props: CommentUpdateFormProps): React. " `; +exports[`amplify form renderer tests GraphQL form tests should use custom primary key on fetch query for update form 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Autocomplete, + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { API } from \\"aws-amplify\\"; +import { getCustomKeyModel, listChildItems } from \\"../graphql/queries\\"; +import { updateChildItem, updateCustomKeyModel } from \\"../graphql/mutations\\"; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, + errorMessage, +}) { + const labelElement = {label}; + const { + tokens: { + components: { + fieldmessages: { error: errorStyles }, + }, + }, + } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + if ( + currentFieldValue !== undefined && + currentFieldValue !== null && + currentFieldValue !== \\"\\" && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return ( + + {labelElement} + {arraySection} + + ); + } + return ( + + {labelElement} + {isEditing && children} + {!isEditing ? ( + <> + + {errorMessage && hasError && ( + + {errorMessage} + + )} + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + {arraySection} + + ); +} +export default function UpdateForm(props) { + const { + mycustomkey: mycustomkeyProp, + customKeyModel: customKeyModelModelProp, + onSuccess, + onError, + onSubmit, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + mycustomkey: \\"\\", + content: \\"\\", + children: [], + }; + const [mycustomkey, setMycustomkey] = React.useState( + initialValues.mycustomkey + ); + const [content, setContent] = React.useState(initialValues.content); + const [children, setChildren] = React.useState(initialValues.children); + const [childrenLoading, setChildrenLoading] = React.useState(false); + const [childrenRecords, setChildrenRecords] = React.useState([]); + const autocompleteLength = 10; + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + const cleanValues = customKeyModelRecord + ? { ...initialValues, ...customKeyModelRecord, children: linkedChildren } + : initialValues; + setMycustomkey(cleanValues.mycustomkey); + setContent(cleanValues.content); + setChildren(cleanValues.children ?? []); + setCurrentChildrenValue(undefined); + setCurrentChildrenDisplayValue(\\"\\"); + setErrors({}); + }; + const [customKeyModelRecord, setCustomKeyModelRecord] = React.useState( + customKeyModelModelProp + ); + const [linkedChildren, setLinkedChildren] = React.useState([]); + const canUnlinkChildren = true; + React.useEffect(() => { + const queryData = async () => { + const record = mycustomkeyProp + ? ( + await API.graphql({ + query: getCustomKeyModel, + variables: { mycustomkey: mycustomkeyProp }, + }) + )?.data?.getCustomKeyModel + : customKeyModelModelProp; + const linkedChildren = record?.children?.items ?? []; + setLinkedChildren(linkedChildren); + setCustomKeyModelRecord(record); + }; + queryData(); + }, [mycustomkeyProp, customKeyModelModelProp]); + React.useEffect(resetStateValues, [customKeyModelRecord, linkedChildren]); + const [currentChildrenDisplayValue, setCurrentChildrenDisplayValue] = + React.useState(\\"\\"); + const [currentChildrenValue, setCurrentChildrenValue] = + React.useState(undefined); + const childrenRef = React.createRef(); + const getIDValue = { + children: (r) => JSON.stringify({ id: r?.id }), + }; + const childrenIdSet = new Set( + Array.isArray(children) + ? children.map((r) => getIDValue.children?.(r)) + : getIDValue.children?.(children) + ); + const getDisplayValue = { + children: (r) => \`\${r?.content ? r?.content + \\" - \\" : \\"\\"}\${r?.id}\`, + }; + const validations = { + mycustomkey: [{ type: \\"Required\\" }], + content: [{ type: \\"Required\\" }], + children: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + const fetchChildrenRecords = async (value) => { + setChildrenLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ content: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listChildItems, + variables, + }) + )?.data?.listChildItems?.items; + var loaded = result.filter( + (item) => !childrenIdSet.has(getIDValue.children?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setChildrenRecords(newOptions.slice(0, autocompleteLength)); + setChildrenLoading(false); + }; + React.useEffect(() => { + fetchChildrenRecords(\\"\\"); + }, []); + return ( + { + event.preventDefault(); + let modelFields = { + mycustomkey, + content, + children, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) + ) + ); + return promises; + } + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value === \\"\\") { + modelFields[key] = null; + } + }); + const promises = []; + const childrenToLink = []; + const childrenToUnLink = []; + const childrenSet = new Set(); + const linkedChildrenSet = new Set(); + children.forEach((r) => childrenSet.add(getIDValue.children?.(r))); + linkedChildren.forEach((r) => + linkedChildrenSet.add(getIDValue.children?.(r)) + ); + linkedChildren.forEach((r) => { + if (!childrenSet.has(getIDValue.children?.(r))) { + childrenToUnLink.push(r); + } + }); + children.forEach((r) => { + if (!linkedChildrenSet.has(getIDValue.children?.(r))) { + childrenToLink.push(r); + } + }); + childrenToUnLink.forEach((original) => { + if (!canUnlinkChildren) { + throw Error( + \`ChildItem \${original.id} cannot be unlinked from CustomKeyModel because customKeyModelChildrenMycustomkey is a required field.\` + ); + } + promises.push( + API.graphql({ + query: updateChildItem, + variables: { + input: { + id: original.id, + customKeyModelChildrenMycustomkey: null, + }, + }, + }) + ); + }); + childrenToLink.forEach((original) => { + promises.push( + API.graphql({ + query: updateChildItem, + variables: { + input: { + id: original.id, + customKeyModelChildrenMycustomkey: + customKeyModelRecord.mycustomkey, + }, + }, + }) + ); + }); + const modelFieldsToSave = { + mycustomkey: modelFields.mycustomkey, + content: modelFields.content, + }; + promises.push( + API.graphql({ + query: updateCustomKeyModel, + variables: { + input: { + mycustomkey: customKeyModelRecord.mycustomkey, + ...modelFieldsToSave, + }, + }, + }) + ); + await Promise.all(promises); + if (onSuccess) { + onSuccess(modelFields); + } + } catch (err) { + if (onError) { + const messages = err.errors.map((e) => e.message).join(\\"\\\\n\\"); + onError(modelFields, messages); + } + } + }} + {...getOverrideProps(overrides, \\"UpdateForm\\")} + {...rest} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + mycustomkey: value, + content, + children, + }; + const result = onChange(modelFields); + value = result?.mycustomkey ?? value; + } + if (errors.mycustomkey?.hasError) { + runValidationTasks(\\"mycustomkey\\", value); + } + setMycustomkey(value); + }} + onBlur={() => runValidationTasks(\\"mycustomkey\\", mycustomkey)} + errorMessage={errors.mycustomkey?.errorMessage} + hasError={errors.mycustomkey?.hasError} + {...getOverrideProps(overrides, \\"mycustomkey\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + mycustomkey, + content: value, + children, + }; + const result = onChange(modelFields); + value = result?.content ?? value; + } + if (errors.content?.hasError) { + runValidationTasks(\\"content\\", value); + } + setContent(value); + }} + onBlur={() => runValidationTasks(\\"content\\", content)} + errorMessage={errors.content?.errorMessage} + hasError={errors.content?.hasError} + {...getOverrideProps(overrides, \\"content\\")} + > + { + let values = items; + if (onChange) { + const modelFields = { + mycustomkey, + content, + children: values, + }; + const result = onChange(modelFields); + values = result?.children ?? values; + } + setChildren(values); + setCurrentChildrenValue(undefined); + setCurrentChildrenDisplayValue(\\"\\"); + }} + currentFieldValue={currentChildrenValue} + label={\\"Children\\"} + items={children} + hasError={errors?.children?.hasError} + errorMessage={errors?.children?.errorMessage} + getBadgeText={getDisplayValue.children} + setFieldValue={(model) => { + setCurrentChildrenDisplayValue( + model ? getDisplayValue.children(model) : \\"\\" + ); + setCurrentChildrenValue(model); + }} + inputFieldRef={childrenRef} + defaultFieldValue={\\"\\"} + > + !childrenIdSet.has(getIDValue.children?.(r))) + .map((r) => ({ + id: getIDValue.children?.(r), + label: getDisplayValue.children?.(r), + }))} + isLoading={childrenLoading} + onSelect={({ id, label }) => { + setCurrentChildrenValue( + childrenRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentChildrenDisplayValue(label); + runValidationTasks(\\"children\\", label); + }} + onClear={() => { + setCurrentChildrenDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchChildrenRecords(value); + if (errors.children?.hasError) { + runValidationTasks(\\"children\\", value); + } + setCurrentChildrenDisplayValue(value); + setCurrentChildrenValue(undefined); + }} + onBlur={() => + runValidationTasks(\\"children\\", currentChildrenDisplayValue) + } + errorMessage={errors.children?.errorMessage} + hasError={errors.children?.hasError} + ref={childrenRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"children\\")} + > + + + + + + + + + ); +} +" +`; + +exports[`amplify form renderer tests GraphQL form tests should use custom primary key on fetch query for update form 2`] = ` +"import * as React from \\"react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { ChildItem, CustomKeyModel } from \\"../API\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type UpdateFormInputValues = { + mycustomkey?: string; + content?: string; + children?: ChildItem[]; +}; +export declare type UpdateFormValidationValues = { + mycustomkey?: ValidationFunction; + content?: ValidationFunction; + children?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type UpdateFormOverridesProps = { + UpdateFormGrid?: PrimitiveOverrideProps; + mycustomkey?: PrimitiveOverrideProps; + content?: PrimitiveOverrideProps; + children?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type UpdateFormProps = React.PropsWithChildren<{ + overrides?: UpdateFormOverridesProps | undefined | null; +} & { + mycustomkey?: string; + customKeyModel?: CustomKeyModel; + onSubmit?: (fields: UpdateFormInputValues) => UpdateFormInputValues; + onSuccess?: (fields: UpdateFormInputValues) => void; + onError?: (fields: UpdateFormInputValues, errorMessage: string) => void; + onChange?: (fields: UpdateFormInputValues) => UpdateFormInputValues; + onValidate?: UpdateFormValidationValues; +} & React.CSSProperties>; +export default function UpdateForm(props: UpdateFormProps): React.ReactElement; +" +`; + exports[`amplify form renderer tests NoApi form tests should render custom data form successfully with no configured API 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts index 207f420f..bdb768d4 100644 --- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts @@ -962,6 +962,23 @@ describe('amplify form renderer tests', () => { expect(declaration).toMatchSnapshot(); }); + it('should use custom primary key on fetch query for update form', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'models/custom-key-model/forms/CustomKeyModelUpdateForm', + 'models/custom-key-model/schema', + { ...defaultCLIRenderConfig, ...rendererConfigWithGraphQL }, + { isNonModelSupported: true, isRelationshipSupported: true }, + ); + + // check for import statement for graphql operation + expect(componentText).not.toContain('DataStore'); + expect(componentText).toContain('variables: { mycustomkey: mycustomkeyProp },'); + expect(componentText).not.toContain('variables: { id: mycustomkeyProp },'); + + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + it('should generate a create form with manyToMany relationship', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/tag-datastore-create', diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts index 6960793e..bb52c561 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts @@ -683,22 +683,22 @@ export const buildUpdateDatastoreQuery = ( importedModelName: string, lowerCaseDataTypeName: string, relatedModelStatements: Statement[], - primaryKey: string, + primaryKeyPropName: string, importCollection: ImportCollection, - isCompositeKey: boolean, + primaryKeys: string[], dataApi?: DataApiKind, ) => { // if there are multiple primaryKeys, it's a composite key and we're using 'id' for a composite key prop - const pkQueryIdentifier = factory.createIdentifier(primaryKey); + const pkPropIdentifier = factory.createIdentifier(primaryKeyPropName); const queryCall = dataApi === 'GraphQL' ? wrapInParenthesizedExpression( getGraphqlCallExpression(ActionType.GET, importedModelName, importCollection, { inputs: [ - isCompositeKey - ? factory.createSpreadAssignment(pkQueryIdentifier) - : factory.createPropertyAssignment(factory.createIdentifier('id'), pkQueryIdentifier), + primaryKeys.length > 1 + ? factory.createSpreadAssignment(pkPropIdentifier) + : factory.createPropertyAssignment(factory.createIdentifier(primaryKeys[0]), pkPropIdentifier), ], }), ['data', getGraphqlQueryForModel(ActionType.GET, importedModelName)], @@ -710,7 +710,7 @@ export const buildUpdateDatastoreQuery = ( factory.createIdentifier('query'), ), undefined, - [factory.createIdentifier(importedModelName), pkQueryIdentifier], + [factory.createIdentifier(importedModelName), pkPropIdentifier], ), ); @@ -755,7 +755,7 @@ export const buildUpdateDatastoreQuery = ( undefined, undefined, factory.createConditionalExpression( - pkQueryIdentifier, + pkPropIdentifier, factory.createToken(SyntaxKind.QuestionToken), queryCall, factory.createToken(SyntaxKind.ColonToken), diff --git a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts index 0b144c22..3de73d45 100644 --- a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts +++ b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts @@ -540,22 +540,20 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< // primaryKey should exist if DataStore update form. This condition is just for ts if (this.primaryKeys) { // if there are multiple primaryKeys, it's a composite key and we're using 'id' for a composite key prop - const isCompositeKey = this.primaryKeys.length > 1; - const destructuredPrimaryKey = isCompositeKey - ? getPropName(COMPOSITE_PRIMARY_KEY_PROP_NAME) - : getPropName(this.primaryKeys[0]); + const primaryKeyPropName = + this.primaryKeys.length > 1 ? getPropName(COMPOSITE_PRIMARY_KEY_PROP_NAME) : getPropName(this.primaryKeys[0]); statements.push( addUseEffectWrapper( buildUpdateDatastoreQuery( modelName, lowerCaseDataTypeName, relatedModelStatements, - destructuredPrimaryKey, + primaryKeyPropName, this.importCollection, - isCompositeKey, + this.primaryKeys, dataApi, ), - [destructuredPrimaryKey, getModelNameProp(lowerCaseDataTypeName)], + [primaryKeyPropName, getModelNameProp(lowerCaseDataTypeName)], ), ); } diff --git a/packages/codegen-ui/example-schemas/models/custom-key-model/forms/CustomKeyModelUpdateForm.json b/packages/codegen-ui/example-schemas/models/custom-key-model/forms/CustomKeyModelUpdateForm.json new file mode 100644 index 00000000..888d39dc --- /dev/null +++ b/packages/codegen-ui/example-schemas/models/custom-key-model/forms/CustomKeyModelUpdateForm.json @@ -0,0 +1,12 @@ +{ + "name": "UpdateForm", + "dataType": { + "dataSourceType": "DataStore", + "dataTypeName": "CustomKeyModel" + }, + "formActionType": "update", + "fields": {}, + "sectionalElements": {}, + "style": {}, + "cta": {} +} \ No newline at end of file diff --git a/packages/codegen-ui/example-schemas/models/custom-key-model/schema.graphql b/packages/codegen-ui/example-schemas/models/custom-key-model/schema.graphql new file mode 100644 index 00000000..32590558 --- /dev/null +++ b/packages/codegen-ui/example-schemas/models/custom-key-model/schema.graphql @@ -0,0 +1,10 @@ +type CustomKeyModel @model @auth(rules: [{allow: public}]) { + mycustomkey: String! @primaryKey + content: String! + children: [ChildItem] @hasMany +} + +type ChildItem @model @auth(rules: [{allow: public}]) { + id: ID! + content: String! +} \ No newline at end of file diff --git a/packages/codegen-ui/example-schemas/models/custom-key-model/schema.json b/packages/codegen-ui/example-schemas/models/custom-key-model/schema.json new file mode 100644 index 00000000..704f1db5 --- /dev/null +++ b/packages/codegen-ui/example-schemas/models/custom-key-model/schema.json @@ -0,0 +1,166 @@ +{ + "models": { + "CustomKeyModel": { + "name": "CustomKeyModel", + "fields": { + "mycustomkey": { + "name": "mycustomkey", + "isArray": false, + "type": "String", + "isRequired": true, + "attributes": [] + }, + "content": { + "name": "content", + "isArray": false, + "type": "String", + "isRequired": true, + "attributes": [] + }, + "children": { + "name": "children", + "isArray": true, + "type": { + "model": "ChildItem" + }, + "isRequired": false, + "attributes": [], + "isArrayNullable": true, + "association": { + "connectionType": "HAS_MANY", + "associatedWith": [ + "customKeyModelChildrenMycustomkey" + ] + } + }, + "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": "CustomKeyModels", + "attributes": [ + { + "type": "model", + "properties": {} + }, + { + "type": "key", + "properties": { + "fields": [ + "mycustomkey" + ] + } + }, + { + "type": "auth", + "properties": { + "rules": [ + { + "allow": "public", + "operations": [ + "create", + "update", + "delete", + "read" + ] + } + ] + } + } + ] + }, + "ChildItem": { + "name": "ChildItem", + "fields": { + "id": { + "name": "id", + "isArray": false, + "type": "ID", + "isRequired": true, + "attributes": [] + }, + "content": { + "name": "content", + "isArray": false, + "type": "String", + "isRequired": true, + "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 + }, + "customKeyModelChildrenMycustomkey": { + "name": "customKeyModelChildrenMycustomkey", + "isArray": false, + "type": "String", + "isRequired": false, + "attributes": [] + } + }, + "syncable": true, + "pluralName": "ChildItems", + "attributes": [ + { + "type": "model", + "properties": {} + }, + { + "type": "key", + "properties": { + "name": "gsi-CustomKeyModel.children", + "fields": [ + "customKeyModelChildrenMycustomkey" + ] + } + }, + { + "type": "auth", + "properties": { + "rules": [ + { + "allow": "public", + "operations": [ + "create", + "update", + "delete", + "read" + ] + } + ] + } + } + ] + } + }, + "enums": {}, + "nonModels": {}, + "codegenVersion": "3.4.3", + "version": "d5f6d906b43f31ad19da125a428925b6" +} \ No newline at end of file