From 6328eb522b10f92d50a41bf28a1bc6d0192b5c41 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 04:27:03 +0000 Subject: [PATCH 1/9] feat: date strings --- packages/language/res/stdlib.zmodel | 13 ++++++++++++- packages/zod/src/utils.ts | 10 ++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/language/res/stdlib.zmodel b/packages/language/res/stdlib.zmodel index 4561da14a..7517d9a45 100644 --- a/packages/language/res/stdlib.zmodel +++ b/packages/language/res/stdlib.zmodel @@ -479,7 +479,7 @@ attribute @db.Double() @@@targetField([FloatField]) @@@prisma attribute @db.Timestamp(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma attribute @db.Timestamptz(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma -attribute @db.Date() @@@targetField([DateTimeField]) @@@prisma +attribute @db.Date() @@@targetField([DateTimeField, StringField]) @@@prisma attribute @db.Time(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma attribute @db.Timetz(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma attribute @db.DateTime(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma @@ -546,6 +546,11 @@ attribute @email(_ message: String?) @@@targetField([StringField]) @@@validation */ attribute @datetime(_ message: String?) @@@targetField([StringField]) @@@validation +/** + * Validates a string field value is a valid ISO date. + */ +attribute @date(_ message: String?) @@@targetField([StringField]) @@@validation + /** * Validates a string field value is a valid url. */ @@ -621,6 +626,12 @@ function isEmail(field: String): Boolean { function isDateTime(field: String): Boolean { } @@@expressionContext([ValidationRule]) +/** + * Validates a string field value is a valid ISO date. + */ +function isDate(field: String): Boolean { +} @@@expressionContext([ValidationRule]) + /** * Validates a string field value is a valid url. */ diff --git a/packages/zod/src/utils.ts b/packages/zod/src/utils.ts index f21d9196f..427f477d9 100644 --- a/packages/zod/src/utils.ts +++ b/packages/zod/src/utils.ts @@ -78,6 +78,9 @@ export function addStringValidation( case '@datetime': result = result.datetime(); break; + case '@date': + result = result.date(); + break; case '@url': result = result.url(); break; @@ -537,7 +540,8 @@ function evalCall(data: any, expr: CallExpression) { case 'isEmail': case 'isUrl': case 'isPhone': - case 'isDateTime': { + case 'isDateTime': + case 'isDate': { if (fieldArg === undefined || fieldArg === null || fieldArg === ABSENT) { return false; } @@ -548,7 +552,9 @@ function evalCall(data: any, expr: CallExpression) { ? ('url' as const) : f === 'isPhone' ? ('e164' as const) - : ('datetime' as const); + : f === 'isDateTime' + ? ('datetime' as const) + : ('date' as const); return z.string()[fn]().safeParse(fieldArg).success; } // list functions From 0cc39acdc1c375020687a59f1ab134490eb8ea94 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 04:27:57 +0000 Subject: [PATCH 2/9] chore: update test schema --- packages/zod/test/schema/schema.ts | 6 ++++++ packages/zod/test/schema/schema.zmodel | 25 +++++++++++++------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/zod/test/schema/schema.ts b/packages/zod/test/schema/schema.ts index 3b8dd6eb2..532590a64 100644 --- a/packages/zod/test/schema/schema.ts +++ b/packages/zod/test/schema/schema.ts @@ -73,6 +73,12 @@ export class SchemaType implements SchemaDef { }, birthdate: { name: "birthdate", + type: "String", + optional: true, + attributes: [{ name: "@date" }] as readonly AttributeApplication[] + }, + createdAt: { + name: "createdAt", type: "DateTime", optional: true }, diff --git a/packages/zod/test/schema/schema.zmodel b/packages/zod/test/schema/schema.zmodel index 0fc3aec88..0f86eece7 100644 --- a/packages/zod/test/schema/schema.zmodel +++ b/packages/zod/test/schema/schema.zmodel @@ -21,22 +21,23 @@ type Address { } model User { - id String @id @default(cuid()) - email String @email @meta("description", "The user's email address") - phone String @phone - username String @length(3, 50) - website String? @url - code String @startsWith("USR") - age Int @gt(0) @lte(150) - score Float @gte(0.0) @lt(100.0) - bigNum BigInt @gte(0) - balance Decimal @gt(0) + id String @id @default(cuid()) + email String @email @meta("description", "The user's email address") + phone String @phone + username String @length(3, 50) + website String? @url + code String @startsWith("USR") + age Int @gt(0) @lte(150) + score Float @gte(0.0) @lt(100.0) + bigNum BigInt @gte(0) + balance Decimal @gt(0) active Boolean - birthdate DateTime? + birthdate String? @date + createdAt DateTime? avatar Bytes? metadata Json? status Status - address Address? @json + address Address? @json posts Post[] @@validate(age >= 18, "Must be adult", ["age"]) From 989b632b032e8c53dafc763e8829bc3acf32ba14 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 04:29:18 +0000 Subject: [PATCH 3/9] chore: update existing tests to match new schema --- packages/zod/test/factory.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 8e81b69ea..2cf1a3c8d 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -21,6 +21,7 @@ const validUser = { balance: 10.0, active: true, birthdate: null, + createdAt: null, avatar: null, metadata: null, status: 'ACTIVE', @@ -65,7 +66,7 @@ describe('SchemaFactory - makeModelSchema', () => { expectTypeOf().toEqualTypeOf(); // DateTime - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); // optional Bytes expectTypeOf().toEqualTypeOf(); @@ -144,7 +145,7 @@ describe('SchemaFactory - makeModelSchema', () => { it('accepts DateTime as a Date object', () => { const userSchema = factory.makeModelSchema('User'); - const result = userSchema.safeParse({ ...validUser, birthdate: new Date() }); + const result = userSchema.safeParse({ ...validUser, createdAt: new Date() }); expect(result.success).toBe(true); }); @@ -152,7 +153,7 @@ describe('SchemaFactory - makeModelSchema', () => { const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - birthdate: '2024-01-15T10:30:00.000Z', + createdAt: '2024-01-15T10:30:00.000Z', }); expect(result.success).toBe(true); }); @@ -214,7 +215,7 @@ describe('SchemaFactory - makeModelSchema', () => { it('infers correct input types for fields', () => { const _userSchema = factory.makeModelSchema('User'); type UserInput = z.input; - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); }); @@ -608,6 +609,7 @@ describe('SchemaFactory - makeTypeSchema', () => { balance: 1, active: true, birthdate: null, + createdAt: null, avatar: null, metadata: null, status: 'ACTIVE', From fde57fecccba4891f17cc89a317fa58e9772abdf Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 04:29:48 +0000 Subject: [PATCH 4/9] chore: add tests --- packages/zod/test/factory.test.ts | 12 ++++++++++++ tests/e2e/orm/validation/custom-validation.test.ts | 7 +++++++ tests/e2e/orm/validation/toplevel.test.ts | 7 +++++++ 3 files changed, 26 insertions(+) diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 2cf1a3c8d..2b8dbf5c6 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -282,6 +282,18 @@ describe('SchemaFactory - makeModelSchema', () => { expect(result.success).toBe(true); }); + it('rejects invalid date for @date field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, birthdate: 'not-a-date' }); + expect(result.success).toBe(false); + }); + + it('accepts valid date for @date field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, birthdate: '2000-01-01' }); + expect(result.success).toBe(true); + }); + it('rejects code that does not start with "USR" for @startsWith', () => { const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, code: 'ABC001' }); diff --git a/tests/e2e/orm/validation/custom-validation.test.ts b/tests/e2e/orm/validation/custom-validation.test.ts index 905c99c92..202efac37 100644 --- a/tests/e2e/orm/validation/custom-validation.test.ts +++ b/tests/e2e/orm/validation/custom-validation.test.ts @@ -13,6 +13,7 @@ describe('Custom validation tests', () => { str4 String? str5 String? str6 String? + str7 String? int1 Int? list1 Int[] list2 Int[] @@ -35,6 +36,8 @@ describe('Custom validation tests', () => { @@validate(str6 == null || isPhone(str6), 'invalid str6') + @@validate(str7 == null || isDate(str7), 'invalid str7') + @@validate(list1 == null || (has(list1, 1) && hasSome(list1, [2, 3]) && hasEvery(list1, [4, 5])), 'invalid list1') @@validate(list2 == null || isEmpty(list2), 'invalid list2', ['x', 'y']) @@ -83,6 +86,9 @@ describe('Custom validation tests', () => { // violates phone await expect(_t({ str6: 'not-a-phone' })).toBeRejectedByValidation(['invalid str6']); + // violates date + await expect(_t({ str7: 'not-a-date' })).toBeRejectedByValidation(['invalid str7']); + // violates has await expect(_t({ list1: [2, 3, 4, 5] })).toBeRejectedByValidation(['invalid list1']); @@ -114,6 +120,7 @@ describe('Custom validation tests', () => { str4: 'http://a.b.c', str5: new Date().toISOString(), str6: '+15555555555', + str7: '2000-01-01', int1: 2, list1: [1, 2, 4, 5], list2: [], diff --git a/tests/e2e/orm/validation/toplevel.test.ts b/tests/e2e/orm/validation/toplevel.test.ts index 65927e192..677726157 100644 --- a/tests/e2e/orm/validation/toplevel.test.ts +++ b/tests/e2e/orm/validation/toplevel.test.ts @@ -15,6 +15,7 @@ describe('Toplevel field validation tests', () => { str5 String? @trim @lower str6 String? @upper str7 String? @phone + str8 String? @date } `, ); @@ -90,6 +91,12 @@ describe('Toplevel field validation tests', () => { // satisfies @phone await expect(_t({ str7: '+15555555555' })).toResolveTruthy(); + + // violates @date + await expect(_t({ str8: 'not-a-date' })).toBeRejectedByValidation(['Invalid ISO']); + + // satisfies @date + await expect(_t({ str8: '2000-01-01' })).toResolveTruthy(); } }); From 514e1a5c30e6f2d2665987dedce0468ccc51d355 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 05:29:39 +0000 Subject: [PATCH 5/9] revert: `@db.Date` allowing `String` fields --- packages/language/res/stdlib.zmodel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language/res/stdlib.zmodel b/packages/language/res/stdlib.zmodel index 7517d9a45..dd57e4c29 100644 --- a/packages/language/res/stdlib.zmodel +++ b/packages/language/res/stdlib.zmodel @@ -479,7 +479,7 @@ attribute @db.Double() @@@targetField([FloatField]) @@@prisma attribute @db.Timestamp(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma attribute @db.Timestamptz(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma -attribute @db.Date() @@@targetField([DateTimeField, StringField]) @@@prisma +attribute @db.Date() @@@targetField([DateTimeField]) @@@prisma attribute @db.Time(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma attribute @db.Timetz(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma attribute @db.DateTime(_ x: Int?) @@@targetField([DateTimeField]) @@@prisma From 9dc1d1b878a6bbd52bacaa105e9f60d759131286 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 07:41:03 +0000 Subject: [PATCH 6/9] chore: add more test cases --- packages/cli/test/db/pull.test.ts | 21 +++++++++++---------- packages/zod/test/factory.test.ts | 8 ++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/cli/test/db/pull.test.ts b/packages/cli/test/db/pull.test.ts index 58649e3da..53d2d7dca 100644 --- a/packages/cli/test/db/pull.test.ts +++ b/packages/cli/test/db/pull.test.ts @@ -619,16 +619,17 @@ enum Status { it('should preserve field-level validation attributes after db pull', async () => { const { workDir, schema } = await createProject( `model User { - id Int @id @default(autoincrement()) - email String @unique @email - phone String @phone - name String @length(min: 2, max: 100) - website String? @url - code String? @regex('^[A-Z]+$') - age Int @gt(0) - score Float @gte(0.0) - rating Decimal @lt(10) - rank BigInt @lte(999) + id Int @id @default(autoincrement()) + email String @unique @email + phone String @phone + birthdate String @date + name String @length(min: 2, max: 100) + website String? @url + code String? @regex('^[A-Z]+$') + age Int @gt(0) + score Float @gte(0.0) + rating Decimal @lt(10) + rank BigInt @lte(999) }`, ); runCli('db push', workDir); diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 2b8dbf5c6..ed9b1c602 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -51,6 +51,7 @@ describe('SchemaFactory - makeModelSchema', () => { expectTypeOf().toEqualTypeOf(); // optional string field (nullable + optional) expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); // number fields (Int and Float both map to ZodNumber) expectTypeOf().toEqualTypeOf(); @@ -294,6 +295,12 @@ describe('SchemaFactory - makeModelSchema', () => { expect(result.success).toBe(true); }); + it('accepts null for optional @date field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, birthdate: null }); + expect(result.success).toBe(true); + }); + it('rejects code that does not start with "USR" for @startsWith', () => { const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, code: 'ABC001' }); @@ -1392,6 +1399,7 @@ describe('SchemaFactory - makeModelSchema with options', () => { expectTypeOf().toEqualTypeOf(); // already-optional nullable field expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); }); it('infers omitted field absent even with optionality all', () => { From 15a96873a46b1492c4d1fccac4102df403c963d5 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 09:40:39 +0000 Subject: [PATCH 7/9] feat: time validation --- packages/language/res/stdlib.zmodel | 11 +++++++++ packages/zod/src/utils.ts | 36 ++++++++++++++++++----------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/language/res/stdlib.zmodel b/packages/language/res/stdlib.zmodel index dd57e4c29..0529c4f31 100644 --- a/packages/language/res/stdlib.zmodel +++ b/packages/language/res/stdlib.zmodel @@ -551,6 +551,11 @@ attribute @datetime(_ message: String?) @@@targetField([StringField]) @@@validat */ attribute @date(_ message: String?) @@@targetField([StringField]) @@@validation +/** + * Validates a string field value is a valid ISO time. + */ +attribute @time(_ precision: Int?, _ message: String?) @@@targetField([StringField]) @@@validation + /** * Validates a string field value is a valid url. */ @@ -632,6 +637,12 @@ function isDateTime(field: String): Boolean { function isDate(field: String): Boolean { } @@@expressionContext([ValidationRule]) +/** + * Validates a string field value is a valid ISO time. + */ +function isTime(field: String): Boolean { +} @@@expressionContext([ValidationRule]) + /** * Validates a string field value is a valid url. */ diff --git a/packages/zod/src/utils.ts b/packages/zod/src/utils.ts index 427f477d9..182e06683 100644 --- a/packages/zod/src/utils.ts +++ b/packages/zod/src/utils.ts @@ -12,6 +12,16 @@ import Decimal from 'decimal.js'; import { z } from 'zod'; import { SchemaFactoryError } from './error'; +// z.string()[mapped] +const stringFuncZodMap = { + isEmail: 'email', + isUrl: 'url', + isPhone: 'e164', + isDate: 'date', + isTime: 'time', + isDateTime: 'datetime', +} as const; + function getArgValue(expr: Expression | undefined): T | undefined { if (!expr || !ExpressionUtils.isLiteral(expr)) { return undefined; @@ -75,12 +85,17 @@ export function addStringValidation( case '@phone': result = result.e164(); break; - case '@datetime': - result = result.datetime(); - break; case '@date': result = result.date(); break; + case '@time': { + const precision = getArgValue(attr.args?.[0]?.value); + result = result.time({ precision }); + break; + } + case '@datetime': + result = result.datetime(); + break; case '@url': result = result.url(); break; @@ -540,21 +555,14 @@ function evalCall(data: any, expr: CallExpression) { case 'isEmail': case 'isUrl': case 'isPhone': - case 'isDateTime': - case 'isDate': { + case 'isDate': + case 'isTime': + case 'isDateTime': { if (fieldArg === undefined || fieldArg === null || fieldArg === ABSENT) { return false; } invariant(typeof fieldArg === 'string', `"${f}" first argument must be a string`); - const fn = f === 'isEmail' - ? ('email' as const) - : f === 'isUrl' - ? ('url' as const) - : f === 'isPhone' - ? ('e164' as const) - : f === 'isDateTime' - ? ('datetime' as const) - : ('date' as const); + const fn = stringFuncZodMap[f]; return z.string()[fn]().safeParse(fieldArg).success; } // list functions From b363d2420061c56d12c06c971ea20b31da7c1ef1 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 09:41:21 +0000 Subject: [PATCH 8/9] chore: add time tests --- packages/cli/test/db/pull.test.ts | 1 + packages/zod/test/factory.test.ts | 22 ++++++++++++ .../orm/validation/custom-validation.test.ts | 7 ++++ tests/e2e/orm/validation/toplevel.test.ts | 34 +++++++++++++------ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/packages/cli/test/db/pull.test.ts b/packages/cli/test/db/pull.test.ts index 53d2d7dca..7ad6a5899 100644 --- a/packages/cli/test/db/pull.test.ts +++ b/packages/cli/test/db/pull.test.ts @@ -623,6 +623,7 @@ enum Status { email String @unique @email phone String @phone birthdate String @date + localTime String @time name String @length(min: 2, max: 100) website String? @url code String? @regex('^[A-Z]+$') diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index ed9b1c602..a0bc7592c 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -21,6 +21,7 @@ const validUser = { balance: 10.0, active: true, birthdate: null, + localTime: null, createdAt: null, avatar: null, metadata: null, @@ -52,6 +53,7 @@ describe('SchemaFactory - makeModelSchema', () => { // optional string field (nullable + optional) expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); // number fields (Int and Float both map to ZodNumber) expectTypeOf().toEqualTypeOf(); @@ -301,6 +303,24 @@ describe('SchemaFactory - makeModelSchema', () => { expect(result.success).toBe(true); }); + it('rejects invalid time for @time field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, localTime: 'not-a-time' }); + expect(result.success).toBe(false); + }); + + it('accepts valid time for @time field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, localTime: '03:15:00' }); + expect(result.success).toBe(true); + }); + + it('accepts null for optional @time field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, localTime: null }); + expect(result.success).toBe(true); + }); + it('rejects code that does not start with "USR" for @startsWith', () => { const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, code: 'ABC001' }); @@ -628,6 +648,7 @@ describe('SchemaFactory - makeTypeSchema', () => { balance: 1, active: true, birthdate: null, + localTime: null, createdAt: null, avatar: null, metadata: null, @@ -1400,6 +1421,7 @@ describe('SchemaFactory - makeModelSchema with options', () => { // already-optional nullable field expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); }); it('infers omitted field absent even with optionality all', () => { diff --git a/tests/e2e/orm/validation/custom-validation.test.ts b/tests/e2e/orm/validation/custom-validation.test.ts index 202efac37..47f12049d 100644 --- a/tests/e2e/orm/validation/custom-validation.test.ts +++ b/tests/e2e/orm/validation/custom-validation.test.ts @@ -14,6 +14,7 @@ describe('Custom validation tests', () => { str5 String? str6 String? str7 String? + str8 String? int1 Int? list1 Int[] list2 Int[] @@ -38,6 +39,8 @@ describe('Custom validation tests', () => { @@validate(str7 == null || isDate(str7), 'invalid str7') + @@validate(str8 == null || isTime(str8), 'invalid str8') + @@validate(list1 == null || (has(list1, 1) && hasSome(list1, [2, 3]) && hasEvery(list1, [4, 5])), 'invalid list1') @@validate(list2 == null || isEmpty(list2), 'invalid list2', ['x', 'y']) @@ -89,6 +92,9 @@ describe('Custom validation tests', () => { // violates date await expect(_t({ str7: 'not-a-date' })).toBeRejectedByValidation(['invalid str7']); + // violates time + await expect(_t({ str8: 'not-a-time' })).toBeRejectedByValidation(['invalid str8']); + // violates has await expect(_t({ list1: [2, 3, 4, 5] })).toBeRejectedByValidation(['invalid list1']); @@ -121,6 +127,7 @@ describe('Custom validation tests', () => { str5: new Date().toISOString(), str6: '+15555555555', str7: '2000-01-01', + str8: '03:15:00', int1: 2, list1: [1, 2, 4, 5], list2: [], diff --git a/tests/e2e/orm/validation/toplevel.test.ts b/tests/e2e/orm/validation/toplevel.test.ts index 677726157..fbfdbd92a 100644 --- a/tests/e2e/orm/validation/toplevel.test.ts +++ b/tests/e2e/orm/validation/toplevel.test.ts @@ -7,15 +7,17 @@ describe('Toplevel field validation tests', () => { const db = await createTestClient( ` model Foo { - id Int @id @default(autoincrement()) - str1 String? @length(2, 4) @startsWith('a') @endsWith('b') @contains('m') @regex('b{2}') - str2 String? @email - str3 String? @datetime - str4 String? @url - str5 String? @trim @lower - str6 String? @upper - str7 String? @phone - str8 String? @date + id Int @id @default(autoincrement()) + str1 String? @length(2, 4) @startsWith('a') @endsWith('b') @contains('m') @regex('b{2}') + str2 String? @email + str3 String? @datetime + str4 String? @url + str5 String? @trim @lower + str6 String? @upper + str7 String? @phone + str8 String? @date + str9 String? @time + str10 String? @time(-1) } `, ); @@ -93,10 +95,22 @@ describe('Toplevel field validation tests', () => { await expect(_t({ str7: '+15555555555' })).toResolveTruthy(); // violates @date - await expect(_t({ str8: 'not-a-date' })).toBeRejectedByValidation(['Invalid ISO']); + await expect(_t({ str8: 'not-a-date' })).toBeRejectedByValidation(['Invalid ISO date']); // satisfies @date await expect(_t({ str8: '2000-01-01' })).toResolveTruthy(); + + // violates @time + await expect(_t({ str9: 'not-a-time' })).toBeRejectedByValidation(['Invalid ISO time']); + + // satisfies @time + await expect(_t({ str9: '03:15:00' })).toResolveTruthy(); + + // violates @time(-1) + await expect(_t({ str10: '03:15:00' })).toBeRejectedByValidation(['Invalid ISO time']); + + // satisfies @time(-1) + await expect(_t({ str10: '03:15' })).toResolveTruthy(); } }); From 10125d016cbf71df583cc928f5e230e39c48c92b Mon Sep 17 00:00:00 2001 From: sanny-io Date: Sun, 31 May 2026 09:41:30 +0000 Subject: [PATCH 9/9] chore: update schema --- packages/zod/test/schema/schema.ts | 6 ++++++ packages/zod/test/schema/schema.zmodel | 25 +++++++++++++------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/zod/test/schema/schema.ts b/packages/zod/test/schema/schema.ts index 532590a64..fa7bc045c 100644 --- a/packages/zod/test/schema/schema.ts +++ b/packages/zod/test/schema/schema.ts @@ -77,6 +77,12 @@ export class SchemaType implements SchemaDef { optional: true, attributes: [{ name: "@date" }] as readonly AttributeApplication[] }, + localTime: { + name: "localTime", + type: "String", + optional: true, + attributes: [{ name: "@time" }] as readonly AttributeApplication[] + }, createdAt: { name: "createdAt", type: "DateTime", diff --git a/packages/zod/test/schema/schema.zmodel b/packages/zod/test/schema/schema.zmodel index 0f86eece7..e7deb27aa 100644 --- a/packages/zod/test/schema/schema.zmodel +++ b/packages/zod/test/schema/schema.zmodel @@ -21,23 +21,24 @@ type Address { } model User { - id String @id @default(cuid()) - email String @email @meta("description", "The user's email address") - phone String @phone - username String @length(3, 50) - website String? @url - code String @startsWith("USR") - age Int @gt(0) @lte(150) - score Float @gte(0.0) @lt(100.0) - bigNum BigInt @gte(0) - balance Decimal @gt(0) + id String @id @default(cuid()) + email String @email @meta("description", "The user's email address") + phone String @phone + username String @length(3, 50) + website String? @url + code String @startsWith("USR") + age Int @gt(0) @lte(150) + score Float @gte(0.0) @lt(100.0) + bigNum BigInt @gte(0) + balance Decimal @gt(0) active Boolean - birthdate String? @date + birthdate String? @date + localTime String? @time createdAt DateTime? avatar Bytes? metadata Json? status Status - address Address? @json + address Address? @json posts Post[] @@validate(age >= 18, "Must be adult", ["age"])