Skip to content

Commit 72a3dab

Browse files
committed
feat(compatibility): New configuration option unsafeCompatibleWhereUniqueInput
close: #177
1 parent 30c8f59 commit 72a3dab

File tree

9 files changed

+117
-24
lines changed

9 files changed

+117
-24
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ Type: `boolean`
145145
Default: `false`
146146
**Note**: It will break compatiblity between Prisma types and generated classes.
147147

148+
#### `unsafeCompatibleWhereUniqueInput`
149+
150+
This trick TypeScript and set property as non optional for all fields in `*WhereUniqueInput` classes.
151+
See [#177](https://github.com/unlight/prisma-nestjs-graphql/issues/177) for more details.
152+
Type: `boolean`
153+
Default: `false`
154+
148155
#### `useInputType`
149156

150157
Since GraphQL does not support input union type, this setting map

src/event-names.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const BeforeGenerateField = 'BeforeGenerateField';

src/handlers/combine-scalar-filters.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import AwaitEventEmitter from 'await-event-emitter';
22
import { cloneDeep, keyBy, remove } from 'lodash';
33

4+
import { BeforeGenerateField } from '../event-names';
45
import { DMMF, EventArguments, InputType } from '../types';
56

67
/**
78
* Subscribes on 'BeforeInputType'
89
*/
910
export function combineScalarFilters(eventEmitter: AwaitEventEmitter) {
1011
eventEmitter.on('BeforeInputType', beforeInputType);
11-
eventEmitter.on('BeforeGenerateField', beforeGenerateField);
12+
eventEmitter.on(BeforeGenerateField, beforeGenerateField);
1213
eventEmitter.on('PostBegin', postBegin);
1314
}
1415

src/handlers/input-type.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { castArray, last } from 'lodash';
44
import pupa from 'pupa';
55
import { ClassDeclarationStructure, StructureKind } from 'ts-morph';
66

7+
import { BeforeGenerateField } from '../event-names';
78
import { getGraphqlImport } from '../helpers/get-graphql-import';
89
import { getGraphqlInputType } from '../helpers/get-graphql-input-type';
910
import { getPropertyType } from '../helpers/get-property-type';
@@ -72,11 +73,12 @@ export function inputType(
7273
const useInputType = config.useInputType.find(x =>
7374
inputType.name.includes(x.typeName),
7475
);
76+
const isWhereUnique = isWhereUniqueInputType(inputType.name);
7577

7678
for (const field of inputType.fields) {
7779
field.inputTypes = field.inputTypes.filter(t => !removeTypes.has(String(t.type)));
7880

79-
eventEmitter.emitSync('BeforeGenerateField', field, args);
81+
eventEmitter.emitSync(BeforeGenerateField, field, args);
8082

8183
const { inputTypes, isRequired, name } = field;
8284

@@ -96,10 +98,14 @@ export function inputType(
9698
});
9799
const modelField = model?.fields.find(f => f.name === name);
98100
const isCustomsApplicable = typeName === modelField?.type;
101+
const atLeastKeys = model && getWhereUniqueAtLeastKeys(model);
99102
const whereUniqueInputType =
100103
isWhereUniqueInputType(typeName) &&
101-
model &&
102-
`Prisma.AtLeast<${typeName}, ${getWhereUniqueAtLeastKeys(model)}>`;
104+
atLeastKeys &&
105+
`Prisma.AtLeast<${typeName}, ${atLeastKeys
106+
.map(name => `'${name}'`)
107+
.join(' | ')}>`;
108+
103109
const propertyType = castArray(
104110
propertySettings?.name ||
105111
whereUniqueInputType ||
@@ -108,12 +114,21 @@ export function inputType(
108114
type: typeName,
109115
}),
110116
);
117+
118+
const hasExclamationToken = Boolean(
119+
isWhereUnique &&
120+
config.unsafeCompatibleWhereUniqueInput &&
121+
atLeastKeys?.includes(name),
122+
);
111123
const property = propertyStructure({
112124
name,
113125
isNullable: !isRequired,
126+
hasExclamationToken: hasExclamationToken || undefined,
127+
hasQuestionToken: hasExclamationToken ? false : undefined,
114128
propertyType,
115129
isList,
116130
});
131+
117132
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
118133
classStructure.properties!.push(property);
119134

@@ -136,7 +151,7 @@ export function inputType(
136151
config.decorate.some(
137152
d =>
138153
d.name === 'HideField' &&
139-
d.from === '@nestjs/graphql' &&
154+
d.from === moduleSpecifier &&
140155
d.isMatchField(name) &&
141156
d.isMatchType(inputType.name),
142157
);
@@ -182,7 +197,7 @@ export function inputType(
182197
ok(property.decorators, 'property.decorators is undefined');
183198

184199
if (shouldHideField) {
185-
importDeclarations.add('HideField', '@nestjs/graphql');
200+
importDeclarations.add('HideField', moduleSpecifier);
186201
property.decorators.push({ name: 'HideField', arguments: [] });
187202
} else {
188203
// Generate `@Field()` decorator
@@ -197,22 +212,6 @@ export function inputType(
197212
],
198213
});
199214

200-
// Debug
201-
// if (classStructure.name === 'XInput') {
202-
// console.log('------------');
203-
// console.log({
204-
// field,
205-
// property,
206-
// modelField,
207-
// graphqlInputType,
208-
// 'args.inputType': args.inputType,
209-
// 'classStructure.name': classStructure.name,
210-
// classTransformerTypeModels,
211-
// modelName,
212-
// graphqlType,
213-
// });
214-
// }
215-
216215
if (graphqlType === 'GraphQLDecimal') {
217216
importDeclarations.add('transformToDecimal', 'prisma-graphql-type-decimal');
218217
importDeclarations.add('Transform', 'class-transformer');

src/helpers/create-config.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,12 @@ describe('createConfig', () => {
138138
expect(result.emitBlocks.prismaEnums).toEqual(true);
139139
expect(result.emitBlocks.schemaEnums).toEqual(true);
140140
});
141+
142+
it('unsafeCompatibleWhereUniqueInput', () => {
143+
const result = createConfig({
144+
unsafeCompatibleWhereUniqueInput: 'true',
145+
});
146+
147+
expect(result.unsafeCompatibleWhereUniqueInput).toEqual(true);
148+
});
141149
});

src/helpers/create-config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ export function createConfig(data: Record<string, unknown>) {
114114
requireSingleFieldsInWhereUniqueInput: toBoolean(
115115
config.requireSingleFieldsInWhereUniqueInput,
116116
),
117+
unsafeCompatibleWhereUniqueInput: toBoolean(
118+
config.unsafeCompatibleWhereUniqueInput,
119+
),
117120
graphqlScalars: (config.graphqlScalars || {}) as Record<
118121
string,
119122
ImportNameSpec | undefined

src/helpers/get-where-unique-at-least-keys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function getWhereUniqueAtLeastKeys(model: DMMF.Model) {
1313
names.push(createFieldName(uniqueIndex));
1414
}
1515

16-
return names.map(name => `'${name}'`).join(' | ');
16+
return names;
1717
}
1818

1919
function createFieldName(args: { name?: string | null; fields: string[] }) {

src/test/compatibility.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { UserListRelationFilter } from '../../@generated/user/user-list-relation
2020
import { UserMaxOrderByAggregateInput } from '../../@generated/user/user-max-order-by-aggregate.input';
2121
import { UserScalarFieldEnum } from '../../@generated/user/user-scalar-field.enum';
2222
import { UserWhereInput } from '../../@generated/user/user-where.input';
23+
import { UserWhereUniqueInput } from '../../@generated/user/user-where-unique.input';
24+
import { Field } from '@nestjs/graphql';
2325

2426
let $prisma = new PrismaClient();
2527

@@ -199,3 +201,15 @@ let $prisma = new PrismaClient();
199201
console.log('result', result);
200202
});
201203
}
204+
205+
{
206+
class UserWhereUniqueInput1 extends UserWhereUniqueInput {
207+
@Field(() => String, { nullable: true })
208+
id!: string;
209+
}
210+
const userWhereUniqueInput1: UserWhereUniqueInput1 = { id: '1' };
211+
212+
$prisma.user.findUnique({
213+
where: userWhereUniqueInput1,
214+
});
215+
}

src/test/generate.spec.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ describe('model with one id int', () => {
163163
it('aggregate user output count', () => {
164164
const s = testSourceFile({
165165
project,
166-
file: 'aggregate-user.output.ts',
166+
class: 'AggregateUser',
167167
property: '_count',
168168
});
169169
expect(s.property?.type).toEqual('UserCountAggregate');
@@ -2411,3 +2411,63 @@ describe('deprecation reason', () => {
24112411
);
24122412
});
24132413
});
2414+
2415+
describe('unsafeCompatibleWhereUniqueInput', () => {
2416+
it('user id', async () => {
2417+
({ project, sourceFiles } = await testGenerate({
2418+
schema: `
2419+
model User {
2420+
id String @id
2421+
}`,
2422+
options: [
2423+
`outputFilePattern = "{name}.{type}.ts"`,
2424+
`unsafeCompatibleWhereUniqueInput = true`,
2425+
],
2426+
}));
2427+
const s = testSourceFile({
2428+
project,
2429+
class: 'UserWhereUniqueInput',
2430+
property: 'id',
2431+
});
2432+
expect(s.property?.hasExclamationToken).toBe(true);
2433+
});
2434+
2435+
it('user id email', async () => {
2436+
({ project, sourceFiles } = await testGenerate({
2437+
schema: `
2438+
model User {
2439+
id String @id @default(cuid())
2440+
name String
2441+
email String @unique @db.VarChar(255)
2442+
password String
2443+
2444+
@@unique([email, name])
2445+
}`,
2446+
options: [
2447+
`outputFilePattern = "{name}.{type}.ts"`,
2448+
`unsafeCompatibleWhereUniqueInput = true`,
2449+
],
2450+
}));
2451+
2452+
const id = testSourceFile({
2453+
project,
2454+
class: 'UserWhereUniqueInput',
2455+
property: 'id',
2456+
});
2457+
expect(id.property?.hasExclamationToken).toBe(true);
2458+
2459+
const email = testSourceFile({
2460+
project,
2461+
class: 'UserWhereUniqueInput',
2462+
property: 'email',
2463+
});
2464+
expect(email.property?.hasExclamationToken).toBe(true);
2465+
2466+
const emailName = testSourceFile({
2467+
project,
2468+
class: 'UserWhereUniqueInput',
2469+
property: 'email_name',
2470+
});
2471+
expect(emailName.property?.hasExclamationToken).toBe(true);
2472+
});
2473+
});

0 commit comments

Comments
 (0)