amplify-category-api icon indicating copy to clipboard operation
amplify-category-api copied to clipboard

BatchDeleteItem fails when required fields are present on the model definition

Open dan-whitehouse opened this issue 9 months ago • 2 comments

Before opening, please confirm:

JavaScript Framework

Vue

Amplify APIs

GraphQL API

Amplify Version

v6

Amplify Categories

function, api

Backend

Amplify Gen 2

Environment information

System:
    OS: Windows 10 10.0.19045
    CPU: (12) x64 Intel(R) Core(TM) i7-5820K CPU @ 3.30GHz
    Memory: 2.21 GB / 27.91 GB
  Binaries:
    Node: 20.10.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 10.2.3 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (127.0.2651.74)
    Internet Explorer: 11.0.19041.4355
  npmPackages:
    %name%:  0.1.0
    @aws-amplify/backend: ^1.13.0 => 1.14.1
    @aws-amplify/backend-cli: ^1.4.6 => 1.4.11
    @aws-amplify/ui-vue: ^4.2.26 => 4.2.29
    @aws-appsync/utils: ^1.12.0 => 1.12.0
    @aws-sdk/client-cognito-identity-provider: ^3.726.1 => 3.758.0
    @fontsource/dm-sans: 5.0.17 => 5.0.17
    @mdi/font: 7.3.67 => 7.3.67
    @pvermeer/dexie-encrypted-addon: 3.0.0 => 3.0.0
    @rushstack/eslint-patch: ^1.1.0 => 1.10.5
    @stripe/stripe-js: ^5.6.0 => 5.7.0
    @tiptap/extension-character-count: 2.9.1 => 2.9.1
    @tiptap/extension-color: 2.9.1 => 2.9.1
    @tiptap/extension-font-family: 2.9.1 => 2.9.1
    @tiptap/extension-highlight: 2.9.1 => 2.9.1
    @tiptap/extension-image: 2.9.1 => 2.9.1
    @tiptap/extension-link: 2.9.1 => 2.9.1
    @tiptap/extension-list-item: 2.9.1 => 2.9.1
    @tiptap/extension-placeholder: 2.9.1 => 2.9.1
    @tiptap/extension-superscript: 2.9.1 => 2.9.1
    @tiptap/extension-table: 2.9.1 => 2.9.1
    @tiptap/extension-table-cell: 2.9.1 => 2.9.1
    @tiptap/extension-table-header: 2.9.1 => 2.9.1
    @tiptap/extension-table-row: 2.9.1 => 2.9.1
    @tiptap/extension-text-align: 2.9.1 => 2.9.1
    @tiptap/extension-text-style: 2.9.1 => 2.9.1
    @tiptap/extension-typography: 2.9.1 => 2.9.1
    @tiptap/extension-underline: 2.9.1 => 2.9.1
    @tiptap/pm: 2.9.1 => 2.9.1
    @tiptap/starter-kit: 2.9.1 => 2.9.1
    @tiptap/vue-3: 2.9.1 => 2.9.1
    @types/aws-lambda: ^8.10.142 => 8.10.147
    @types/chance: ^1.1.3 => 1.1.6
    @types/diff: 5.0.9 => 5.0.9
    @types/node: ^16.11.25 => 16.18.126
    @types/uuid: ^9.0.7 => 9.0.8
    @types/vextab: ^3.0.3 => 3.0.3
    @vite-pwa/assets-generator: 0.2.6 => 0.2.6
    @vitejs/plugin-vue: 5.0.0 => 5.0.0
    @vue/eslint-config-prettier: ^7.0.0 => 7.1.0
    @vue/eslint-config-typescript: ^10.0.0 => 10.0.0
    @vue/language-server: 2.1.6 => 2.1.6
    @vue/tsconfig: ^0.1.3 => 0.1.3
    @vueuse/core: ^10.6.1 => 10.11.1 (7.5.5)
    apexcharts: 3.36.3 => 3.36.3
    aws-amplify: ^6.12.0 => 6.13.2
    aws-amplify/adapter-core:  undefined ()
    aws-amplify/adapter-core/internals:  undefined ()
    aws-amplify/analytics:  undefined ()
    aws-amplify/analytics/kinesis:  undefined ()
    aws-amplify/analytics/kinesis-firehose:  undefined ()
    aws-amplify/analytics/personalize:  undefined ()
    aws-amplify/analytics/pinpoint:  undefined ()
    aws-amplify/api:  undefined ()
    aws-amplify/api/internals:  undefined ()
    aws-amplify/api/server:  undefined ()
    aws-amplify/auth:  undefined ()
    aws-amplify/auth/cognito:  undefined ()
    aws-amplify/auth/cognito/server:  undefined ()
    aws-amplify/auth/enable-oauth-listener:  undefined ()
    aws-amplify/auth/server:  undefined ()
    aws-amplify/data:  undefined ()
    aws-amplify/data/server:  undefined ()
    aws-amplify/datastore:  undefined ()
    aws-amplify/in-app-messaging:  undefined ()
    aws-amplify/in-app-messaging/pinpoint:  undefined ()
    aws-amplify/push-notifications:  undefined ()
    aws-amplify/push-notifications/pinpoint:  undefined ()
    aws-amplify/storage:  undefined ()
    aws-amplify/storage/s3:  undefined ()
    aws-amplify/storage/s3/server:  undefined ()
    aws-amplify/storage/server:  undefined ()
    aws-amplify/utils:  undefined ()
    aws-cdk: ^2.175.1 => 2.1001.0
    aws-cdk-lib: ^2.175.1 => 2.181.1
    constructs: ^10.3.0 => 10.4.2
    date-fns: 3.0.5 => 3.0.5
    dexie: 3.2.4 => 3.2.4
    diff: 5.2.0 => 5.2.0
    esbuild: ^0.17.19 => 0.17.19 (0.25.0, 0.21.5)
    eslint: ^8.5.0 => 8.57.1
    eslint-plugin-vue: 9.19.2 => 9.19.2
    flat: 6.0.1 => 6.0.1
    fuse.js: ^6.6.2 => 6.6.2
    highlight.js: 11.9.0 => 11.9.0
    jison: ^0.4.18 => 0.4.18
    jison-loader: ^1.0.0 => 1.0.0
    js-base64: 3.7.5 => 3.7.5
    jszip: 3.10.1 => 3.10.1
    lodash: ^4.17.21 => 4.17.21
    maska: 2.1.11 => 2.1.11
    pinia: 2.2.4 => 2.2.4
    pinia-plugin-persistedstate: 4.1.1 => 4.1.1
    prettier: ^2.5.1 => 2.8.8 (2.3.2, 1.19.1)
    prismjs: 1.29.0 => 1.29.0
    sass: ^1.49.9 => 1.85.1
    sass-loader: ^10.0.0 => 10.5.2
    stripe: ^17.6.0 => 17.7.0
    tsx: ^4.16.2 => 4.19.3
    typescript: ^5.5.4 => 5.7.3 (4.4.4, 4.9.5)
    uuid: 9.0.1 => 9.0.1
    vee-validate: ^4.6.7 => 4.15.0
    vexflow: 4.2.5 => 4.2.5 (3.0.9)
    vextab: ^3.0.6 => 3.0.6
    vite: 5.4.9 => 5.4.9
    vite-plugin-mkcert: 1.17.1 => 1.17.1
    vite-plugin-pwa: 0.20.5 => 0.20.5
    vite-plugin-vuetify: 2.0.1 => 2.0.1
    vue: 3.5.3 => 3.5.3
    vue-cli-plugin-vuetify: 2.5.5 => 2.5.5
    vue-draggable-next: 2.2.1 => 2.2.1
    vue-prism-component: 2.0.0 => 2.0.0
    vue-qrcode: ^2.2.2 => 2.2.2
    vue-router: 4.4.3 => 4.4.3
    vue-scrollto: 2.20.0 => 2.20.0
    vue-tsc: ^2.0.6 => 2.2.4
    vue3-apexcharts: 1.4.4 => 1.4.4
    vue3-avataaars: ^1.0.15 => 1.0.15
    vue3-perfect-scrollbar: 2.0.0 => 2.0.0
    vue3-print-nb: ^0.1.4 => 0.1.4
    vuetify: 3.7.13 => 3.7.13
  npmGlobalPackages:
    corepack: 0.22.0
    npm: 10.2.3
    vexflow: 4.2.5

Describe the bug

A BatchDeleteItem operation that returns an array of objects fails when required fields are present on the model definition. It's possible that this occurs because the required field being returned is a custom type. See code snippets below.

Expected behavior

I would expect this be handled internally, as the operation will only ever return the primary key(s). Or, update the documentation to suggest that we return something different. I personally had to update mine to a.json() in order for the operations to complete.

Reproduction steps

  1. Create a table that has a required field
  2. Create a batch delete operation, that returns an object array
  3. Add some data
  4. Execute

Code Snippet

amplify/data/resource.ts

PublisherContact: a
	.model({
		id: a.id().required(), <--- PK
		editionId: a.id().required(), <--- PK
		publisherId: a.id(),
		publisherEditionId: a.id(),
		name: a.ref('Name').required(), <--- Required
		title: a.string(),
		email: a.string(),
		phone: a.ref('Phone').required(), <--- Required
		//relationships && aws data
		publisher: a.belongsTo('Publisher', ['publisherId', 'publisherEditionId']),
		createdAt: a.datetime(),
		updatedAt: a.datetime(),
	})
	.identifier(['id', 'editionId'])
	.secondaryIndexes((index) => [
		index("editionId").queryField("listPublisherContactsByEditionId"),
		index("publisherId").sortKeys(['publisherEditionId']).queryField("listPublisherContactsByPublisherId"),
	])
	.authorization(allow => [
		allow.groups(['dev', 'admin']),
		allow.groups(['staff']).to(['create', 'read', 'update']),
	]),



Name: a.customType({
    first: a.string(),
    last: a.string(),
}),

Phone: a.customType({
    number: a.string(),
    extension: a.string(),
}),
batchDeletePublisherContacts: a
	.mutation()
	.arguments({
		editionId: a.id().required(),
		ids: a.string().array().required(),
	})
	.authorization(allow => [
		allow.groups(['dev', 'admin']),
	])
	.handler(
		a.handler.custom({
			entry: './batch-delete/PublisherContact.js',
			dataSource: a.ref('PublisherContact'),
		}),
	)
	.returns(a.ref('PublisherContact').array()), <--- Return Objects Array

amplify/data/batch-delete/PublisherContact.js

import { util } from '@aws-appsync/utils';

export function request(ctx) {
	const { editionId, ids } = ctx.args;
	const mappedItems = ids.map((id) => {
		return util.dynamodb.toMapValues({
			id: id,
			editionId: editionId,
		});
	}) ?? [];

	return {
		operation: 'BatchDeleteItem',
		tables: {
			[`PublisherContact-${ctx.stash.awsAppsyncApiId}-${ctx.stash.amplifyApiEnvironmentName}`]: mappedItems,
		},
	};
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type);
	}
	return ctx.result.data[`PublisherContact-${ctx.stash.awsAppsyncApiId}-${ctx.stash.amplifyApiEnvironmentName}`];
}

Log output

``` path":["batchDeletePublisherContacts",0,"name"],"locations":null,"message":"Cannot return null for non-nullable type: 'Name' within parent 'PublisherContact' (/batchDeletePublisherContacts[0]/name) ```

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

dan-whitehouse avatar Mar 05 '25 15:03 dan-whitehouse

Hello, @dan-whitehouse and thanks for opening this issue. I'm going to transfer this to the amplify-data repo for better assistance.

cwomack avatar Mar 10 '25 15:03 cwomack

You've got this working using custom resolvers. In order to automatically offer batch deletion for models, we would need to have this behavior generated in the backend. Once it is supported by the backend, then we would add the corresponding model operations in the client. I'm transferring this for consideration as a feature request to the team that own the model implementation.

stocaaro avatar Mar 13 '25 18:03 stocaaro