Skip to content

Commit

Permalink
Convert Relationship Implementation to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
timleslie committed Apr 9, 2021
1 parent 760334a commit f01dee5
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 133 deletions.
41 changes: 34 additions & 7 deletions packages-next/fields/src/Implementation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import { humanize } from '@keystone-next/utils-legacy';
import { parseFieldAccess } from '@keystone-next/access-control-legacy';
import { KeystoneContext } from '@keystone-next/types';
import { PrismaListAdapter } from '@keystone-next/adapter-prisma-legacy';

type List = {};
export type List = {
key: string;
adapter: PrismaListAdapter;
gqlNames: {
relateToOneInputName: string;
whereUniqueInputName: string;
createInputName: string;
relateToManyInputName: string;
whereInputName: string;
outputTypeName: string;
listQueryName: string;
itemQueryName: string;
};
access: Record<string, any>;
fieldsByPath: Record<string, Field<any>>;
getGraphqlFilterFragment: () => string[];
listQuery: any;
listQueryMeta: any;
itemQuery: any;
createMutation: any;
};

export type FieldConfigArgs = {
hooks?: any;
Expand Down Expand Up @@ -109,10 +130,12 @@ class Field<P extends string> {
}

// Field types should replace this if they want to any fields to the output type
gqlOutputFields(): string[] {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
gqlOutputFields({ schemaName }: { schemaName: string }): string[] {
return [];
}
gqlOutputFieldResolvers() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
gqlOutputFieldResolvers({ schemaName }: { schemaName: string }) {
return {};
}

Expand All @@ -126,7 +149,8 @@ class Field<P extends string> {
* NOTE: When a naming conflict occurs, a list's types/queries/mutations will
* overwrite any auxiliary types defined by an individual type.
*/
getGqlAuxTypes(): string[] {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getGqlAuxTypes({ schemaName }: { schemaName: string }): string[] {
return [];
}
gqlAuxFieldResolvers() {
Expand Down Expand Up @@ -167,7 +191,8 @@ class Field<P extends string> {

async afterDelete() {}

gqlQueryInputFields(): string[] {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
gqlQueryInputFields({ schemaName }: { schemaName: string }): string[] {
return [];
}
equalityInputFields(type: string) {
Expand Down Expand Up @@ -209,10 +234,12 @@ class Field<P extends string> {
`${this.path}_not_ends_with_i: ${type}`,
];
}
gqlCreateInputFields(): string[] {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
gqlCreateInputFields({ schemaName }: { schemaName: string }): string[] {
return [];
}
gqlUpdateInputFields(): string[] {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
gqlUpdateInputFields({ schemaName }: { schemaName: string }): string[] {
return [];
}
getDefaultValue({ context, originalInput }: { context: KeystoneContext; originalInput: any }) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import { PrismaFieldAdapter } from '@keystone-next/adapter-prisma-legacy';
import { Implementation } from '../../Implementation.ts';
import { resolveNested } from './nested-mutations';

export class Relationship extends Implementation {
constructor(path, { ref, many, withMeta }) {
super(...arguments);
import { PrismaFieldAdapter, PrismaListAdapter } from '@keystone-next/adapter-prisma-legacy';
import { KeystoneContext } from '@keystone-next/types';
import { FieldConfigArgs, FieldExtraArgs, Implementation } from '../../Implementation';
import { resolveNestedMany, resolveNestedSingle, cleanAndValidateInput } from './nested-mutations';

type List = { adapter: PrismaListAdapter };

export type RelationshipSingleOperation = {
connect?: any;
create?: any;
disconnect?: any;
disconnectAll?: boolean;
};

export type RelationshipManyOperation = {
connect?: any[];
create?: any[];
disconnect?: any[];
disconnectAll?: boolean;
};

export type RelationshipOperation = RelationshipSingleOperation | RelationshipManyOperation;

export class Relationship<P extends string> extends Implementation<P> {
many: boolean;
refFieldPath?: string;
withMeta: boolean;
constructor(
path: P,
{
ref,
many,
withMeta,
...configArgs
}: FieldConfigArgs & { ref: string; many?: boolean; withMeta?: boolean },
extraArgs: FieldExtraArgs
) {
super(path, { ref, many, withMeta, ...configArgs }, extraArgs);

// JM: It bugs me this is duplicated in the field adapters but initialisation order makes it hard to avoid
const [refListKey, refFieldPath] = ref.split('.');
Expand All @@ -13,7 +44,7 @@ export class Relationship extends Implementation {
this.isOrderable = true;

this.isRelationship = true;
this.many = many;
this.many = !!many;
this.withMeta = typeof withMeta !== 'undefined' ? withMeta : true;
}

Expand Down Expand Up @@ -44,7 +75,7 @@ export class Relationship extends Implementation {
return { refList, refField };
}

gqlOutputFields({ schemaName }) {
gqlOutputFields({ schemaName }: { schemaName: string }) {
const { refList } = this.tryResolveRefList();

if (!refList.access[schemaName].read) {
Expand All @@ -63,7 +94,7 @@ export class Relationship extends Implementation {
}
}

gqlQueryInputFields({ schemaName }) {
gqlQueryInputFields({ schemaName }: { schemaName: string }) {
const { refList } = this.tryResolveRefList();

if (!refList.access[schemaName].read) {
Expand All @@ -85,7 +116,7 @@ export class Relationship extends Implementation {
}
}

gqlOutputFieldResolvers({ schemaName }) {
gqlOutputFieldResolvers({ schemaName }: { schemaName: string }) {
const { refList } = this.tryResolveRefList();

if (!refList.access[schemaName].read) {
Expand All @@ -95,7 +126,7 @@ export class Relationship extends Implementation {

if (this.many) {
return {
[this.path]: (item, args, context, info) => {
[this.path]: (item: any, args: any, context: KeystoneContext, info: any) => {
return refList.listQuery(args, context, info.fieldName, info, {
fromList: this.getListByKey(this.listKey),
fromId: item.id,
Expand All @@ -104,7 +135,7 @@ export class Relationship extends Implementation {
},

...(this.withMeta && {
[`_${this.path}Meta`]: (item, args, context, info) => {
[`_${this.path}Meta`]: (item: any, args: any, context: KeystoneContext, info: any) => {
return refList.listQueryMeta(args, context, info.fieldName, info, {
fromList: this.getListByKey(this.listKey),
fromId: item.id,
Expand All @@ -115,7 +146,7 @@ export class Relationship extends Implementation {
};
} else {
return {
[this.path]: (item, _, context, info) => {
[this.path]: (item: any, _: any, context: KeystoneContext, info: any) => {
// No ID set, so we return null for the value
const id = item && (item[this.adapter.idPath] || (item[this.path] && item[this.path].id));
if (!id) {
Expand All @@ -125,7 +156,7 @@ export class Relationship extends Implementation {
// We do a full query to ensure things like access control are applied
return refList
.listQuery(filteredQueryArgs, context, refList.gqlNames.listQueryName, info)
.then(items => (items && items.length ? items[0] : null));
.then((items?: any[]) => (items && items.length ? items[0] : null));
},
};
}
Expand Down Expand Up @@ -157,10 +188,16 @@ export class Relationship extends Implementation {
* previous stored values, which means indecies may not match those passed in
* `operations`.
*/
async resolveNestedOperations(operations, item, context, getItem, mutationState) {
async resolveNestedOperations(
operations: RelationshipOperation,
item: any,
context: KeystoneContext,
getItem: any,
mutationState: { afterChangeStack: any[]; transaction: {} }
) {
const { refList, refField } = this.tryResolveRefList();
const listInfo = {
local: { list: this.getListByKey(this.listKey), field: this },
local: { list: this.getListByKey(this.listKey)!, field: this },
foreign: { list: refList, field: refField },
};

Expand All @@ -186,10 +223,10 @@ export class Relationship extends Implementation {
});
}

let currentValue;
let currentValue: string | string[];
if (this.many) {
const info = { fieldName: this.path };
currentValue = item
const _currentValue: { id: any }[] = item
? await refList.listQuery(
{},
{ ...context, getListAccessControlForUser: () => true },
Expand All @@ -198,27 +235,44 @@ export class Relationship extends Implementation {
{ fromList: this.getListByKey(this.listKey), fromId: item.id, fromField: this.path }
)
: [];
currentValue = currentValue.map(({ id }) => id.toString());
currentValue = _currentValue.map(({ id }) => id.toString());
} else {
currentValue = item && (item[this.adapter.idPath] || (item[this.path] && item[this.path].id));
currentValue = currentValue && currentValue.toString();
}

// Collect the IDs to be connected and disconnected. This step may trigger
// createMutation calls in order to obtain these IDs if required.
const { create = [], connect = [], disconnect = [] } = await resolveNested({
input: operations,
currentValue,
listInfo,
many: this.many,
context,
mutationState,
});

const localList = listInfo.local.list;
const localField = listInfo.local.field;
const target = `${localList.key}.${localField.path}<${refList.key}>`;
let _bar: { create: string[]; connect: string[]; disconnect: string[] };
if (this.many) {
_bar = await resolveNestedMany({
currentValue: currentValue as string[],
refList: listInfo.foreign.list,
input: cleanAndValidateInput({ input: operations, many: this.many, localField, target }),
context,
localField,
target,
mutationState,
});
} else {
_bar = await resolveNestedSingle({
currentValue: currentValue as string | undefined,
refList: listInfo.foreign.list,
input: cleanAndValidateInput({ input: operations, many: this.many, localField, target }),
context,
localField,
target,
mutationState,
});
}
const { create, connect, disconnect } = _bar;
return { create, connect, disconnect, currentValue };
}

getGqlAuxTypes({ schemaName }) {
getGqlAuxTypes({ schemaName }: { schemaName: string }) {
const { refList } = this.tryResolveRefList();
const schemaAccess = refList.access[schemaName];
// We need an input type that is specific to creating nested items when
Expand Down Expand Up @@ -291,7 +345,8 @@ export class Relationship extends Implementation {
return [];
}
}
gqlUpdateInputFields({ schemaName }) {

gqlUpdateInputFields({ schemaName }: { schemaName: string }) {
const { refList } = this.tryResolveRefList();
const schemaAccess = refList.access[schemaName];
if (
Expand All @@ -310,17 +365,30 @@ export class Relationship extends Implementation {
return [];
}
}
gqlCreateInputFields({ schemaName }) {

gqlCreateInputFields({ schemaName }: { schemaName: string }) {
return this.gqlUpdateInputFields({ schemaName });
}

getBackingTypes() {
return { [this.path]: { optional: true, type: 'string | null' } };
}
}

export class PrismaRelationshipInterface extends PrismaFieldAdapter {
constructor() {
super(...arguments);
export class PrismaRelationshipInterface<P extends string> extends PrismaFieldAdapter<P> {
idPath: string;
isUnique: boolean;
isIndexed: boolean;
refFieldPath?: string;
constructor(
fieldName: string,
path: P,
field: Relationship<P>,
listAdapter: PrismaListAdapter,
getListByKey: (arg: string) => List | undefined,
config = {}
) {
super(fieldName, path, field, listAdapter, getListByKey, config);
this.idPath = `${this.dbPath}Id`;
this.isRelationship = true;

Expand All @@ -338,9 +406,10 @@ export class PrismaRelationshipInterface extends PrismaFieldAdapter {
this.refFieldPath = refFieldPath;
}

getQueryConditions(dbPath) {
getQueryConditions<T>(dbPath: string) {
return {
[`${this.path}_is_null`]: value => (value ? { [dbPath]: null } : { NOT: { [dbPath]: null } }),
[`${this.path}_is_null`]: (value: T | null) =>
value ? { [dbPath]: null } : { NOT: { [dbPath]: null } },
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,5 @@ import { createError } from 'apollo-errors';

export const ParameterError = createError('ParameterError', {
message: 'Incorrect parameters supplied',
options: {
showPath: true,
},
options: { showPath: true },
});
Loading

0 comments on commit f01dee5

Please sign in to comment.