Skip to content

Commit

Permalink
Refactor nested mutation handling (#6094)
Browse files Browse the repository at this point in the history
  • Loading branch information
timleslie committed Jul 13, 2021
1 parent 884fd3b commit 279403c
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 123 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-poets-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': patch
---

Refactored internal nested mutation input handler code.
26 changes: 24 additions & 2 deletions packages/keystone/src/lib/core/mutations/create-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,39 @@ import {
getPrismaModelForList,
promiseAllRejectWithAllErrors,
getDBFieldKeyForFieldOnMultiField,
IdType,
} from '../utils';
import {
NestedMutationState,
resolveRelateToManyForCreateInput,
resolveRelateToManyForUpdateInput,
} from './nested-mutation-many-input-resolvers';
import {
resolveRelateToOneForCreateInput,
resolveRelateToOneForUpdateInput,
} from './nested-mutation-input-resolvers';
} from './nested-mutation-one-input-resolvers';
import { applyAccessControlForCreate, getAccessControlledItemForUpdate } from './access-control';
import { runSideEffectOnlyHook, validationHook } from './hooks';

export class NestedMutationState {
#afterChanges: (() => void | Promise<void>)[] = [];
#context: KeystoneContext;
constructor(context: KeystoneContext) {
this.#context = context;
}
async create(
input: Record<string, any>,
list: InitialisedList
): Promise<{ kind: 'connect'; id: IdType } | { kind: 'create'; data: Record<string, any> }> {
const { afterChange, data } = await createOneState({ data: input }, list, this.#context);
const item = await getPrismaModelForList(this.#context.prisma, list.listKey).create({ data });
this.#afterChanges.push(() => afterChange(item));
return { kind: 'connect' as const, id: item.id as any };
}
async afterChange() {
await promiseAllRejectWithAllErrors(this.#afterChanges.map(async x => x()));
}
}

export function createMany(
{ data }: { data: Record<string, any>[] },
list: InitialisedList,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
import { KeystoneContext, TypesForList, schema } from '@keystone-next/types';
import { resolveUniqueWhereInput, UniqueInputFilter, UniquePrismaFilter } from '../where-inputs';
import { InitialisedList } from '../types-for-lists';
import {
isRejected,
isFulfilled,
getPrismaModelForList,
promiseAllRejectWithAllErrors,
IdType,
} from '../utils';
import { createOneState } from './create-update';
import { isRejected, isFulfilled } from '../utils';
import { NestedMutationState } from './create-update';

const isNotNull = <T>(arg: T): arg is Exclude<T, null> => arg !== null;

export class NestedMutationState {
#afterChanges: (() => void | Promise<void>)[] = [];
#context: KeystoneContext;
constructor(context: KeystoneContext) {
this.#context = context;
}
async create(
input: Record<string, any>,
list: InitialisedList
): Promise<{ kind: 'connect'; id: IdType } | { kind: 'create'; data: Record<string, any> }> {
const { afterChange, data } = await createOneState({ data: input }, list, this.#context);
const item = await getPrismaModelForList(this.#context.prisma, list.listKey).create({ data });
this.#afterChanges.push(() => afterChange(item));
return { kind: 'connect' as const, id: item.id as any };
}
async afterChange() {
await promiseAllRejectWithAllErrors(this.#afterChanges.map(async x => x()));
}
}

export function resolveRelateToManyForCreateInput(
nestedMutationState: NestedMutationState,
context: KeystoneContext,
Expand Down Expand Up @@ -171,96 +145,3 @@ export function resolveRelateToManyForUpdateInput(
};
};
}

async function handleCreateAndUpdate(
value: Exclude<
schema.InferValueFromArg<schema.Arg<TypesForList['relateTo']['one']['create']>>,
null | undefined
>,
nestedMutationState: NestedMutationState,
context: KeystoneContext,
foreignList: InitialisedList,
target: string
) {
if (value.connect) {
try {
await context.db.lists[foreignList.listKey].findOne({ where: value.connect as any });
} catch (err) {
throw new Error(`Unable to connect a ${target}`);
}
return {
connect: await resolveUniqueWhereInput(value.connect, foreignList.fields, context),
};
}
if (value.create) {
const createInput = value.create;
let create = await (async () => {
try {
return await nestedMutationState.create(createInput, foreignList);
} catch (err) {
throw new Error(`Unable to create a ${target}`);
}
})();

if (create.kind === 'connect') {
return { connect: { id: create.id } };
}
return { create: create.data };
}
}

export function resolveRelateToOneForCreateInput(
nestedMutationState: NestedMutationState,
context: KeystoneContext,
foreignList: InitialisedList,
target: string
) {
return async (
value: schema.InferValueFromArg<schema.Arg<TypesForList['relateTo']['one']['create']>>
) => {
if (value == null) {
return undefined;
}
const numOfKeys = Object.keys(value).length;
if (numOfKeys !== 1) {
throw new Error(`Nested mutation operation invalid for ${target}`);
}
return handleCreateAndUpdate(value, nestedMutationState, context, foreignList, target);
};
}

export function resolveRelateToOneForUpdateInput(
nestedMutationState: NestedMutationState,
context: KeystoneContext,
foreignList: InitialisedList,
target: string
) {
return async (
value: schema.InferValueFromArg<
schema.Arg<schema.NonNullType<TypesForList['relateTo']['one']['update']>>
>
) => {
if (value == null) {
return undefined;
}
if (value.connect && value.create) {
throw new Error(`Nested mutation operation invalid for ${target}`);
}
if (value.connect || value.create) {
return handleCreateAndUpdate(value, nestedMutationState, context, foreignList, target);
}
if (value.disconnect) {
try {
await context
.sudo()
.db.lists[foreignList.listKey].findOne({ where: value.disconnect as any });
} catch (err) {
return;
}
return { disconnect: true };
}
if (value.disconnectAll) {
return { disconnect: true };
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { KeystoneContext, TypesForList, schema } from '@keystone-next/types';
import { resolveUniqueWhereInput } from '../where-inputs';
import { InitialisedList } from '../types-for-lists';
import { NestedMutationState } from './create-update';

async function handleCreateAndUpdate(
value: Exclude<
schema.InferValueFromArg<schema.Arg<TypesForList['relateTo']['one']['create']>>,
null | undefined
>,
nestedMutationState: NestedMutationState,
context: KeystoneContext,
foreignList: InitialisedList,
target: string
) {
if (value.connect) {
try {
await context.db.lists[foreignList.listKey].findOne({ where: value.connect as any });
} catch (err) {
throw new Error(`Unable to connect a ${target}`);
}
return {
connect: await resolveUniqueWhereInput(value.connect, foreignList.fields, context),
};
}
if (value.create) {
const createInput = value.create;
let create = await (async () => {
try {
return await nestedMutationState.create(createInput, foreignList);
} catch (err) {
throw new Error(`Unable to create a ${target}`);
}
})();

if (create.kind === 'connect') {
return { connect: { id: create.id } };
}
return { create: create.data };
}
}

export function resolveRelateToOneForCreateInput(
nestedMutationState: NestedMutationState,
context: KeystoneContext,
foreignList: InitialisedList,
target: string
) {
return async (
value: schema.InferValueFromArg<schema.Arg<TypesForList['relateTo']['one']['create']>>
) => {
if (value == null) {
return undefined;
}
const numOfKeys = Object.keys(value).length;
if (numOfKeys !== 1) {
throw new Error(`Nested mutation operation invalid for ${target}`);
}
return handleCreateAndUpdate(value, nestedMutationState, context, foreignList, target);
};
}

export function resolveRelateToOneForUpdateInput(
nestedMutationState: NestedMutationState,
context: KeystoneContext,
foreignList: InitialisedList,
target: string
) {
return async (
value: schema.InferValueFromArg<
schema.Arg<schema.NonNullType<TypesForList['relateTo']['one']['update']>>
>
) => {
if (value == null) {
return undefined;
}
if (value.connect && value.create) {
throw new Error(`Nested mutation operation invalid for ${target}`);
}
if (value.connect || value.create) {
return handleCreateAndUpdate(value, nestedMutationState, context, foreignList, target);
}
if (value.disconnect) {
try {
await context
.sudo()
.db.lists[foreignList.listKey].findOne({ where: value.disconnect as any });
} catch (err) {
return;
}
return { disconnect: true };
}
if (value.disconnectAll) {
return { disconnect: true };
}
};
}

1 comment on commit 279403c

@vercel
Copy link

@vercel vercel bot commented on 279403c Jul 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.