Skip to content

Commit ff26c3d

Browse files
committed
implement omit
1 parent 3699629 commit ff26c3d

File tree

8 files changed

+79
-11
lines changed

8 files changed

+79
-11
lines changed

TODO.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
- [x] info
99
- [ ] init
1010
- [ ] ORM
11-
- [ ] Create
11+
- [x] Create
1212
- [x] Input validation
1313
- [x] Simple create
1414
- [x] Nested create
@@ -18,7 +18,7 @@
1818
- [ ] Find
1919
- [x] Input validation
2020
- [ ] Field selection
21-
- [ ] Omit
21+
- [x] Omit
2222
- [x] Counting relation
2323
- [x] Pagination
2424
- [x] Skip and limit
@@ -50,6 +50,7 @@
5050
- [ ] Extensions
5151
- [x] Query builder API
5252
- [x] Computed fields
53+
- [ ] Prisma client extension
5354
- [ ] Misc
5455
- [ ] Compound ID
5556
- [ ] Cross field comparison

packages/runtime/src/client/client-types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@ export type SelectInclude<
309309
> = {
310310
select?: Select<Schema, Model, AllowCount>;
311311
include?: Include<Schema, Model>;
312+
omit?: {
313+
[Key in ScalarFields<Schema, Model>]?: true;
314+
};
312315
};
313316

314317
type Select<
@@ -367,6 +370,8 @@ export type SelectSubset<T, U> = {
367370
[key in keyof T]: key extends keyof U ? T[key] : never;
368371
} & (T extends { select: any; include: any }
369372
? 'Please either choose `select` or `include`.'
373+
: T extends { select: any; omit: any }
374+
? 'Please either choose `select` or `omit`.'
370375
: {});
371376

372377
type ToManyRelationFilter<

packages/runtime/src/client/crud/dialects/postgresql.ts

+7
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ export class PostgresCrudDialect<
210210
objArgs.push(
211211
...Object.entries(relationModelDef.fields)
212212
.filter(([, value]) => !value.relation)
213+
.filter(
214+
([name]) =>
215+
!(
216+
typeof payload === 'object' &&
217+
(payload.omit as any)?.[name] === true
218+
)
219+
)
213220
.map(([field]) => [
214221
sql.lit(field),
215222
buildFieldRef(

packages/runtime/src/client/crud/dialects/sqlite.ts

+7
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ export class SqliteCrudDialect<
128128
objArgs.push(
129129
...Object.entries(relationModelDef.fields)
130130
.filter(([, value]) => !value.relation)
131+
.filter(
132+
([name]) =>
133+
!(
134+
typeof payload === 'object' &&
135+
(payload.omit as any)?.[name] === true
136+
)
137+
)
131138
.map(([field]) => [
132139
sql.lit(field),
133140
buildFieldRef(

packages/runtime/src/client/crud/operations/base.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
138138
modelDef.dbTable
139139
);
140140
} else {
141-
query = this.buildSelectAllScalarFields(model, query);
141+
query = this.buildSelectAllScalarFields(model, query, args?.omit);
142142
}
143143

144144
// include
@@ -308,12 +308,14 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
308308

309309
private buildSelectAllScalarFields(
310310
model: string,
311-
query: SelectQueryBuilder<any, any, {}>
311+
query: SelectQueryBuilder<any, any, {}>,
312+
omit?: Record<string, boolean | undefined>
312313
) {
313314
let result = query;
314315
const modelDef = this.requireModel(model);
315316
return Object.keys(modelDef.fields)
316317
.filter((f) => !isRelationField(this.schema, model, f))
318+
.filter((f) => omit?.[f] !== true)
317319
.reduce(
318320
(acc, f) => this.selectField(acc, model, modelDef.dbTable, f),
319321
result

packages/runtime/src/client/crud/operations/find.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ export class FindOperationHandler<
3333
operation === 'findUnique',
3434
args
3535
)
36-
: (args as FindArgs<Schema, GetModels<Schema>, true>);
36+
: args;
3737

3838
// run query
39-
const result = await this.runQuery(this.model, parsedArgs);
39+
const result = await this.runQuery(
40+
this.model,
41+
parsedArgs as FindArgs<Schema, GetModels<Schema>, true>
42+
);
4043

4144
const finalResult =
4245
operation === 'findMany' ? result : result[0] ?? null;

packages/runtime/src/client/crud/operations/validator.ts

+26-5
Original file line numberDiff line numberDiff line change
@@ -28,37 +28,37 @@ export class InputValidator<Schema extends SchemaDef> {
2828

2929
constructor(private readonly schema: Schema) {}
3030

31-
validateFindArgs(model: string, unique: boolean, args: unknown) {
31+
validateFindArgs(model: GetModels<Schema>, unique: boolean, args: unknown) {
3232
return this.validate<FindArgs<Schema, GetModels<Schema>, true>>(
3333
this.makeFindSchema(model, unique, true),
3434
'find',
3535
args
3636
);
3737
}
3838

39-
validateCreateArgs(model: string, args: unknown) {
39+
validateCreateArgs(model: GetModels<Schema>, args: unknown) {
4040
return this.validate<CreateArgs<Schema, GetModels<Schema>>>(
4141
this.makeCreateSchema(model),
4242
'create',
4343
args
4444
);
4545
}
4646

47-
validateCreateManyArgs(model: string, args: unknown) {
47+
validateCreateManyArgs(model: GetModels<Schema>, args: unknown) {
4848
return this.validate<
4949
CreateManyArgs<Schema, GetModels<Schema>> | undefined
5050
>(this.makeCreateManySchema(model), 'createMany', args);
5151
}
5252

53-
validateUpdateArgs(model: string, args: unknown) {
53+
validateUpdateArgs(model: GetModels<Schema>, args: unknown) {
5454
return this.validate<UpdateArgs<Schema, GetModels<Schema>>>(
5555
this.makeUpdateSchema(model),
5656
'update',
5757
args
5858
);
5959
}
6060

61-
validateUpdateManyArgs(model: string, args: unknown) {
61+
validateUpdateManyArgs(model: GetModels<Schema>, args: unknown) {
6262
return this.validate<UpdateManyArgs<Schema, GetModels<Schema>>>(
6363
this.makeUpdateManySchema(model),
6464
'updateMany',
@@ -121,6 +121,7 @@ export class InputValidator<Schema extends SchemaDef> {
121121

122122
fields['select'] = this.makeSelectSchema(model).optional();
123123
fields['include'] = this.makeIncludeSchema(model).optional();
124+
fields['omit'] = this.makeOmitSchema(model).optional();
124125

125126
if (collection) {
126127
fields['skip'] = z.number().int().nonnegative().optional();
@@ -133,6 +134,7 @@ export class InputValidator<Schema extends SchemaDef> {
133134

134135
let result: ZodSchema = z.object(fields).strict();
135136
result = this.refineForSelectIncludeMutuallyExclusive(result);
137+
result = this.refineForSelectOmitMutuallyExclusive(result);
136138

137139
if (!unique) {
138140
result = result.optional();
@@ -446,6 +448,18 @@ export class InputValidator<Schema extends SchemaDef> {
446448
return z.object(fields).strict();
447449
}
448450

451+
protected makeOmitSchema(model: string) {
452+
const modelDef = requireModel(this.schema, model);
453+
const fields: Record<string, ZodSchema> = {};
454+
for (const field of Object.keys(modelDef.fields)) {
455+
const fieldDef = requireField(this.schema, model, field);
456+
if (!fieldDef.relation) {
457+
fields[field] = z.boolean().optional();
458+
}
459+
}
460+
return z.object(fields).strict();
461+
}
462+
449463
protected makeIncludeSchema(model: string) {
450464
const modelDef = requireModel(this.schema, model);
451465
const fields: Record<string, ZodSchema> = {};
@@ -1029,6 +1043,13 @@ export class InputValidator<Schema extends SchemaDef> {
10291043
);
10301044
}
10311045

1046+
private refineForSelectOmitMutuallyExclusive(schema: ZodSchema) {
1047+
return schema.refine(
1048+
(value) => !(value['select'] && value['omit']),
1049+
'"select" and "omit" cannot be used together'
1050+
);
1051+
}
1052+
10321053
private nullableIf(schema: ZodSchema, nullable: boolean) {
10331054
return nullable ? schema.nullable() : schema;
10341055
}

packages/runtime/test/client-api/find.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,28 @@ describe.each(createClientSpecs(PG_DB_NAME))(
489489
});
490490
});
491491

492+
it('allows field omission', async () => {
493+
const user = await createUser(client);
494+
await createPosts(client, user.id);
495+
496+
const r = await client.user.findFirstOrThrow({
497+
omit: { name: true },
498+
});
499+
expect('name' in r).toBeFalsy();
500+
expect(r.email).toBeTruthy();
501+
502+
// @ts-expect-error omit and select cannot be used together
503+
client.user.findFirstOrThrow({
504+
omit: { name: true },
505+
select: { email: true },
506+
});
507+
508+
const r1 = await client.user.findFirstOrThrow({
509+
include: { posts: { omit: { published: true } } },
510+
});
511+
expect('published' in r1.posts[0]!).toBeFalsy();
512+
});
513+
492514
it('allows including relation', async () => {
493515
const user = await createUser(client);
494516
const [post1, post2] = await createPosts(client, user.id);

0 commit comments

Comments
 (0)