Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/cli/test/ts-schema-gen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,4 +736,25 @@ model Post {
plugins: {},
});
});

it('supports @@strict for type defs', async () => {
const { schema } = await generateTsSchema(`
model User {
id String @id @default(uuid())
profile Profile? @json
}

type Profile {
bio String

@@strict
}
`);

expect(schema.typeDefs).toMatchObject({
Profile: {
strict: true,
},
});
});
});
4 changes: 3 additions & 1 deletion packages/clients/fetch-client/test/fetch-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,9 @@ describe('createClient', () => {
mockFetch.mockResolvedValue({ ok: true, text: async () => makeResponseText(true) });

const client = createClient(schema, { endpoint: ENDPOINT });
const result = await (client as any).$procs.sendNotification.mutate({ args: { message: 'hello' } });
const result = await (client as any).$procs.sendNotification.mutate({
args: { notification: { message: 'hello' } },
});

const [url, init] = mockFetch.mock.calls[0] ?? [];
expect(url).toBe(`${ENDPOINT}/$procs/sendNotification`);
Expand Down
19 changes: 17 additions & 2 deletions packages/clients/fetch-client/test/schemas/basic/schema-lite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

/* eslint-disable */

import { type SchemaDef, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema";
import { type SchemaDef, type AttributeApplication, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema";
export class SchemaType implements SchemaDef {
provider = {
type: "sqlite"
Expand Down Expand Up @@ -77,6 +77,21 @@ export class SchemaType implements SchemaDef {
}
}
} as const;
typeDefs = {
Notification: {
name: "Notification",
fields: {
message: {
name: "message",
type: "String"
}
},
attributes: [
{ name: "@@strict" }
] as readonly AttributeApplication[],
strict: true
}
} as const;
authType = "User" as const;
procedures = {
getStats: {
Expand All @@ -85,7 +100,7 @@ export class SchemaType implements SchemaDef {
},
sendNotification: {
params: {
message: { name: "message", type: "String" }
notification: { name: "notification", type: "Notification" }
},
returnType: "Boolean",
mutation: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ model Post {
authorId String?
}

type Notification {
message String

@@strict
}

procedure getStats(): Int

mutation procedure sendNotification(message: String): Boolean
mutation procedure sendNotification(notification: Notification): Boolean
33 changes: 33 additions & 0 deletions packages/clients/fetch-client/test/schemas/no-procs/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//////////////////////////////////////////////////////////////////////////////////////////////
// DO NOT MODIFY THIS FILE //
// This file is automatically generated by ZenStack CLI and should not be manually updated. //
//////////////////////////////////////////////////////////////////////////////////////////////

/* eslint-disable */

import { type SchemaDef, type AttributeApplication, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema";
export class SchemaType implements SchemaDef {
provider = {
type: "sqlite"
} as const;
models = {
Item: {
name: "Item",
fields: {
id: {
name: "id",
type: "String",
id: true,
attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }] as readonly AttributeApplication[],
default: ExpressionUtils.call("cuid") as FieldDefault
}
},
idFields: ["id"],
uniqueFields: {
id: { type: "String" }
}
}
} as const;
plugins = {};
}
export const schema = new SchemaType();
16 changes: 16 additions & 0 deletions packages/clients/fetch-client/test/typing.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,19 @@ describe('Extended result fields (ExtResult)', () => {
};
});
});

describe('Custom types', () => {
it('supports @@strict', () => {
const client = createClient(schema, { endpoint: ENDPOINT });

client.$procs.sendNotification.mutate({
args: {
notification: {
message: 'test',
// @ts-expect-error known properties
unknown: true,
},
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ describe('React client typing test', () => {

client.foo.useUpdate();
client.bar.useCreate();

client.user.useCreate().mutate({ data: { email: 'test@example.com', profile: { bio: 'Programmer' } } });

client.user
.useCreate()
// @ts-expect-error known properties
.mutate({ data: { email: 'test@example.com', profile: { bio: 'Programmer', unknown: true } } });
});

it('reflects ExtQueryArgs and ExtResult inferred from a ClientContract type', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

/* eslint-disable */

import { type SchemaDef, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema";
import { type SchemaDef, type AttributeApplication, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema";
export class SchemaType implements SchemaDef {
provider = {
type: "sqlite"
Expand Down Expand Up @@ -35,6 +35,11 @@ export class SchemaType implements SchemaDef {
type: "Post",
array: true,
relation: { opposite: "owner" }
},
profile: {
name: "profile",
type: "Profile",
optional: true
}
},
idFields: ["id"],
Expand Down Expand Up @@ -166,6 +171,21 @@ export class SchemaType implements SchemaDef {
}
}
} as const;
typeDefs = {
Profile: {
name: "Profile",
fields: {
bio: {
name: "bio",
type: "String"
}
},
attributes: [
{ name: "@@strict" }
] as readonly AttributeApplication[],
strict: true
}
} as const;
authType = "User" as const;
plugins = {};
}
Expand Down
30 changes: 18 additions & 12 deletions packages/clients/tanstack-query/test/schemas/basic/schema.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,39 @@ datasource db {
}

model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
profile Profile? @json
}

model Post {
id String @id @default(cuid())
title String
owner User? @relation(fields: [ownerId], references: [id])
ownerId String?
category Category? @relation(fields: [categoryId], references: [id])
id String @id @default(cuid())
title String
owner User? @relation(fields: [ownerId], references: [id])
ownerId String?
category Category? @relation(fields: [categoryId], references: [id])
categoryId String?
}

model Category {
id String @id @default(cuid())
name String @unique
id String @id @default(cuid())
name String @unique
posts Post[]
}

model Foo {
id String @id @default(cuid())
id String @id @default(cuid())
type String
@@delegate(type)
}

model Bar extends Foo {
title String
}

type Profile {
bio String
@@strict
}
Comment thread
sanny-io marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ client.user
data: { email: 'test@example.com' },
});

client.user.useCreate().mutate({ data: { email: 'test@example.com', profile: { bio: 'Programmer' } } });

client.user
.useCreate()
// @ts-expect-error known properties
.mutate({ data: { email: 'test@example.com', profile: { bio: 'Programmer', unknown: true } } });

client.user
.useCreate()
.mutateAsync({ data: { email: 'test@example.com' }, include: { posts: true } })
Expand Down
7 changes: 7 additions & 0 deletions packages/clients/tanstack-query/test/vue/vue-typing-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ client.user
.mutateAsync({ data: { email: 'test@example.com' }, include: { posts: true } })
.then((d) => check(d.posts[0]?.title));

client.user.useCreate().mutate({ data: { email: 'test@example.com', profile: { bio: 'Programmer' } } });

client.user
.useCreate()
// @ts-expect-error known properties
.mutate({ data: { email: 'test@example.com', profile: { bio: 'Programmer', unknown: true } } });

client.user
.useCreateMany()
.mutateAsync({
Expand Down
5 changes: 5 additions & 0 deletions packages/language/res/stdlib.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -729,3 +729,8 @@ attribute @meta(_ name: String, _ value: Any)
* Marks an attribute as deprecated.
*/
attribute @@@deprecated(_ message: String)

/**
* Specifies a type def should not allow unknown fields.
*/
attribute @@strict() @@@once
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,15 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
}
}

@check('@@strict')
private _checkStrict(attr: AttributeApplication, accept: ValidationAcceptor) {
if (!isTypeDef(attr.$container)) {
accept('error', `attribute "@@strict" can only be used on type definitions`, {
node: attr,
});
}
}

private validatePolicyKinds(
kind: string,
candidates: string[],
Expand Down
56 changes: 56 additions & 0 deletions packages/language/test/attribute-application.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,4 +816,60 @@ describe('Attribute application validation tests', () => {
/relation "bar" is not optional/,
);
});

describe('@@strict attribute', () => {
it('accepts type defs', async () => {
await loadSchema(`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

type Profile {
name String

@@strict
}
`);
});

it('rejects non-type defs', async () => {
await loadSchemaWithError(
`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model User {
id String @id
name String

@@strict
}
`,
/attribute "@@strict" can only be used on type definitions/,
);

await loadSchemaWithError(
`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model User {
id String @id
}

enum Enum {
TEST

@@strict
}
`,
/attribute "@@strict" can only be used on type definitions/,
);
});
});
});
12 changes: 10 additions & 2 deletions packages/orm/src/client/crud-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,14 @@ export type TypeDefResult<
>,
Partial
> &
Record<string, unknown>;
(IsTypeDefStrict<Schema, TypeDef> extends true ? {} : Record<string, unknown>);

export type IsTypeDefStrict<Schema extends SchemaDef, TypeDef extends GetTypeDefs<Schema>> =
Schema['typeDefs'] extends Record<string, unknown>
? Schema['typeDefs'][TypeDef]['strict'] extends true
? true
: false
: never;

export type BatchResult = { count: number };

Expand Down Expand Up @@ -1480,7 +1487,8 @@ type MapFieldDefType<
T['type'] extends GetEnums<Schema>
? keyof GetEnum<Schema, T['type']>
: T['type'] extends GetTypeDefs<Schema>
? TypeDefResult<Schema, T['type'], Partial> & Record<string, unknown>
? TypeDefResult<Schema, T['type'], Partial> &
(IsTypeDefStrict<Schema, T['type']> extends true ? {} : Record<string, unknown>)
: MapBaseType<T['type']>,
T['optional'],
T['array']
Expand Down
Loading
Loading