Skip to content

Commit 52d9576

Browse files
authored
fix: make helpers package browser-compatible, fix update args validation (#90)
* fix: make helpers package browser-compatible, fix update args validation * update
1 parent 07feac7 commit 52d9576

File tree

15 files changed

+254
-61
lines changed

15 files changed

+254
-61
lines changed

TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
- [x] Nested to-one
4545
- [x] Incremental update for numeric fields
4646
- [x] Array update
47+
- [ ] Strict typing for checked/unchecked input
4748
- [x] Upsert
4849
- [ ] Implement with "on conflict"
4950
- [x] Delete

packages/cli/src/actions/action-utils.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { findUp } from '@zenstackhq/common-helpers';
21
import { loadDocument } from '@zenstackhq/language';
32
import { PrismaSchemaGenerator } from '@zenstackhq/sdk';
43
import colors from 'colors';
@@ -86,3 +85,28 @@ export function getPkgJsonConfig(startPath: string) {
8685

8786
return result;
8887
}
88+
89+
type FindUpResult<Multiple extends boolean> = Multiple extends true ? string[] | undefined : string | undefined;
90+
91+
function findUp<Multiple extends boolean = false>(
92+
names: string[],
93+
cwd: string = process.cwd(),
94+
multiple: Multiple = false as Multiple,
95+
result: string[] = [],
96+
): FindUpResult<Multiple> {
97+
if (!names.some((name) => !!name)) {
98+
return undefined;
99+
}
100+
const target = names.find((name) => fs.existsSync(path.join(cwd, name)));
101+
if (multiple === false && target) {
102+
return path.join(cwd, target) as FindUpResult<Multiple>;
103+
}
104+
if (target) {
105+
result.push(path.join(cwd, target));
106+
}
107+
const up = path.resolve(cwd, '..');
108+
if (up === cwd) {
109+
return (multiple && result.length > 0 ? result : undefined) as FindUpResult<Multiple>;
110+
}
111+
return findUp(names, up, multiple, result);
112+
}

packages/cli/src/actions/generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function run(options: Options) {
2424

2525
// generate TS schema
2626
const tsSchemaFile = path.join(outputPath, 'schema.ts');
27-
await new TsSchemaGenerator().generate(schemaFile, [], tsSchemaFile);
27+
await new TsSchemaGenerator().generate(schemaFile, [], outputPath);
2828

2929
await runPlugins(model, outputPath, tsSchemaFile);
3030

packages/common-helpers/src/find-up.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

packages/common-helpers/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export * from './find-up';
21
export * from './is-plain-object';
32
export * from './lower-case-first';
43
export * from './param-case';

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { BaseOperationHandler, type CrudOperation } from './base';
55
export class FindOperationHandler<Schema extends SchemaDef> extends BaseOperationHandler<Schema> {
66
async handle(operation: CrudOperation, args: unknown, validateArgs = true): Promise<unknown> {
77
// normalize args to strip `undefined` fields
8-
const normalizeArgs = this.normalizeArgs(args);
8+
const normalizedArgs = this.normalizeArgs(args);
99

1010
// parse args
1111
const parsedArgs = validateArgs
12-
? this.inputValidator.validateFindArgs(this.model, operation === 'findUnique', normalizeArgs)
13-
: normalizeArgs;
12+
? this.inputValidator.validateFindArgs(this.model, operation === 'findUnique', normalizedArgs)
13+
: normalizedArgs;
1414

1515
// run query
1616
const result = await this.read(

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -924,8 +924,8 @@ export class InputValidator<Schema extends SchemaDef> {
924924
}
925925

926926
private makeUpdateDataSchema(model: string, withoutFields: string[] = [], withoutRelationFields = false) {
927-
const regularAndFkFields: any = {};
928-
const regularAndRelationFields: any = {};
927+
const uncheckedVariantFields: Record<string, ZodType> = {};
928+
const checkedVariantFields: Record<string, ZodType> = {};
929929
const modelDef = requireModel(this.schema, model);
930930
const hasRelation = Object.entries(modelDef.fields).some(
931931
([key, value]) => value.relation && !withoutFields.includes(key),
@@ -957,7 +957,11 @@ export class InputValidator<Schema extends SchemaDef> {
957957
if (fieldDef.optional && !fieldDef.array) {
958958
fieldSchema = fieldSchema.nullable();
959959
}
960-
regularAndRelationFields[field] = fieldSchema;
960+
checkedVariantFields[field] = fieldSchema;
961+
if (fieldDef.array || !fieldDef.relation.references) {
962+
// non-owned relation
963+
uncheckedVariantFields[field] = fieldSchema;
964+
}
961965
} else {
962966
let fieldSchema: ZodType = this.makePrimitiveSchema(fieldDef.type).optional();
963967

@@ -1000,17 +1004,18 @@ export class InputValidator<Schema extends SchemaDef> {
10001004
fieldSchema = fieldSchema.nullable();
10011005
}
10021006

1003-
regularAndFkFields[field] = fieldSchema;
1007+
uncheckedVariantFields[field] = fieldSchema;
10041008
if (!fieldDef.foreignKeyFor) {
1005-
regularAndRelationFields[field] = fieldSchema;
1009+
// non-fk field
1010+
checkedVariantFields[field] = fieldSchema;
10061011
}
10071012
}
10081013
});
10091014

10101015
if (!hasRelation) {
1011-
return z.object(regularAndFkFields).strict();
1016+
return z.object(uncheckedVariantFields).strict();
10121017
} else {
1013-
return z.union([z.object(regularAndFkFields).strict(), z.object(regularAndRelationFields).strict()]);
1018+
return z.union([z.object(uncheckedVariantFields).strict(), z.object(checkedVariantFields).strict()]);
10141019
}
10151020
}
10161021

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,50 @@ describe.each(createClientSpecs(PG_DB_NAME))('Client update tests', ({ createCli
170170
}),
171171
).resolves.toMatchObject({ age: null });
172172
});
173+
174+
it('compiles with Prisma checked/unchecked typing', async () => {
175+
const user = await client.user.create({
176+
data: {
177+
178+
posts: {
179+
create: {
180+
id: '1',
181+
title: 'title',
182+
},
183+
},
184+
},
185+
});
186+
187+
// fk and owned-relation are mutually exclusive
188+
// TODO: @ts-expect-error
189+
client.post.update({
190+
where: { id: '1' },
191+
data: {
192+
authorId: user.id,
193+
title: 'title',
194+
author: { connect: { id: user.id } },
195+
},
196+
});
197+
198+
// fk can work with non-owned relation
199+
const comment = await client.comment.create({
200+
data: {
201+
content: 'comment',
202+
},
203+
});
204+
await expect(
205+
client.post.update({
206+
where: { id: '1' },
207+
data: {
208+
authorId: user.id,
209+
title: 'title',
210+
comments: {
211+
connect: { id: comment.id },
212+
},
213+
},
214+
}),
215+
).toResolveTruthy();
216+
});
173217
});
174218

175219
describe('nested to-many', () => {

packages/runtime/test/typing/generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ async function main() {
88
const dir = path.dirname(fileURLToPath(import.meta.url));
99
const zmodelPath = path.join(dir, 'typing-test.zmodel');
1010
const tsPath = path.join(dir, 'schema.ts');
11-
await generator.generate(zmodelPath, [], tsPath);
11+
await generator.generate(zmodelPath, [], dir);
1212

1313
const content = fs.readFileSync(tsPath, 'utf-8');
1414
fs.writeFileSync(tsPath, content.replace(/@zenstackhq\/runtime/g, '../../dist'));
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//////////////////////////////////////////////////////////////////////////////////////////////
2+
// DO NOT MODIFY THIS FILE //
3+
// This file is automatically generated by ZenStack CLI and should not be manually updated. //
4+
//////////////////////////////////////////////////////////////////////////////////////////////
5+
6+
/* eslint-disable */
7+
8+
import { type ModelResult } from "@zenstackhq/runtime";
9+
import { schema } from "./schema";
10+
export type Schema = typeof schema;
11+
export type User = ModelResult<Schema, "User">;
12+
export type Post = ModelResult<Schema, "Post">;
13+
export type Profile = ModelResult<Schema, "Profile">;
14+
export type Tag = ModelResult<Schema, "Tag">;
15+
export type Region = ModelResult<Schema, "Region">;
16+
export type Meta = ModelResult<Schema, "Meta">;

0 commit comments

Comments
 (0)