Skip to content

Commit 4318933

Browse files
Follow up: unify v3 and v4 zod types via describe matrix and a test helper (#1141)
1 parent 9438478 commit 4318933

20 files changed

+6993
-17620
lines changed

src/client/index.test.ts

Lines changed: 159 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
/* eslint-disable no-constant-binary-expression */
33
/* eslint-disable @typescript-eslint/no-unused-expressions */
44
import { Client, getSupportedElicitationModes } from './index.js';
5-
import * as z from 'zod/v4';
65
import {
76
RequestSchema,
87
NotificationSchema,
@@ -21,6 +20,165 @@ import {
2120
import { Transport } from '../shared/transport.js';
2221
import { Server } from '../server/index.js';
2322
import { InMemoryTransport } from '../inMemory.js';
23+
import * as z3 from 'zod/v3';
24+
import * as z4 from 'zod/v4';
25+
26+
describe('Zod v4', () => {
27+
/***
28+
* Test: Type Checking
29+
* Test that custom request/notification/result schemas can be used with the Client class.
30+
*/
31+
test('should typecheck', () => {
32+
const GetWeatherRequestSchema = RequestSchema.extend({
33+
method: z4.literal('weather/get'),
34+
params: z4.object({
35+
city: z4.string()
36+
})
37+
});
38+
39+
const GetForecastRequestSchema = RequestSchema.extend({
40+
method: z4.literal('weather/forecast'),
41+
params: z4.object({
42+
city: z4.string(),
43+
days: z4.number()
44+
})
45+
});
46+
47+
const WeatherForecastNotificationSchema = NotificationSchema.extend({
48+
method: z4.literal('weather/alert'),
49+
params: z4.object({
50+
severity: z4.enum(['warning', 'watch']),
51+
message: z4.string()
52+
})
53+
});
54+
55+
const WeatherRequestSchema = GetWeatherRequestSchema.or(GetForecastRequestSchema);
56+
const WeatherNotificationSchema = WeatherForecastNotificationSchema;
57+
const WeatherResultSchema = ResultSchema.extend({
58+
temperature: z4.number(),
59+
conditions: z4.string()
60+
});
61+
62+
type WeatherRequest = z4.infer<typeof WeatherRequestSchema>;
63+
type WeatherNotification = z4.infer<typeof WeatherNotificationSchema>;
64+
type WeatherResult = z4.infer<typeof WeatherResultSchema>;
65+
66+
// Create a typed Client for weather data
67+
const weatherClient = new Client<WeatherRequest, WeatherNotification, WeatherResult>(
68+
{
69+
name: 'WeatherClient',
70+
version: '1.0.0'
71+
},
72+
{
73+
capabilities: {
74+
sampling: {}
75+
}
76+
}
77+
);
78+
79+
// Typecheck that only valid weather requests/notifications/results are allowed
80+
false &&
81+
weatherClient.request(
82+
{
83+
method: 'weather/get',
84+
params: {
85+
city: 'Seattle'
86+
}
87+
},
88+
WeatherResultSchema
89+
);
90+
91+
false &&
92+
weatherClient.notification({
93+
method: 'weather/alert',
94+
params: {
95+
severity: 'warning',
96+
message: 'Storm approaching'
97+
}
98+
});
99+
});
100+
});
101+
102+
describe('Zod v3', () => {
103+
/***
104+
* Test: Type Checking
105+
* Test that custom request/notification/result schemas can be used with the Client class.
106+
*/
107+
test('should typecheck', () => {
108+
const GetWeatherRequestSchema = z3.object({
109+
...RequestSchema.shape,
110+
method: z3.literal('weather/get'),
111+
params: z3.object({
112+
city: z3.string()
113+
})
114+
});
115+
116+
const GetForecastRequestSchema = z3.object({
117+
...RequestSchema.shape,
118+
method: z3.literal('weather/forecast'),
119+
params: z3.object({
120+
city: z3.string(),
121+
days: z3.number()
122+
})
123+
});
124+
125+
const WeatherForecastNotificationSchema = z3.object({
126+
...NotificationSchema.shape,
127+
method: z3.literal('weather/alert'),
128+
params: z3.object({
129+
severity: z3.enum(['warning', 'watch']),
130+
message: z3.string()
131+
})
132+
});
133+
134+
const WeatherRequestSchema = GetWeatherRequestSchema.or(GetForecastRequestSchema);
135+
const WeatherNotificationSchema = WeatherForecastNotificationSchema;
136+
const WeatherResultSchema = z3.object({
137+
...ResultSchema.shape,
138+
_meta: z3.record(z3.string(), z3.unknown()).optional(),
139+
temperature: z3.number(),
140+
conditions: z3.string()
141+
});
142+
143+
type WeatherRequest = z3.infer<typeof WeatherRequestSchema>;
144+
type WeatherNotification = z3.infer<typeof WeatherNotificationSchema>;
145+
type WeatherResult = z3.infer<typeof WeatherResultSchema>;
146+
147+
// Create a typed Client for weather data
148+
const weatherClient = new Client<WeatherRequest, WeatherNotification, WeatherResult>(
149+
{
150+
name: 'WeatherClient',
151+
version: '1.0.0'
152+
},
153+
{
154+
capabilities: {
155+
sampling: {}
156+
}
157+
}
158+
);
159+
160+
// Typecheck that only valid weather requests/notifications/results are allowed
161+
false &&
162+
weatherClient.request(
163+
{
164+
method: 'weather/get',
165+
params: {
166+
city: 'Seattle'
167+
}
168+
},
169+
WeatherResultSchema
170+
);
171+
172+
false &&
173+
weatherClient.notification({
174+
method: 'weather/alert',
175+
params: {
176+
severity: 'warning',
177+
message: 'Storm approaching'
178+
}
179+
});
180+
});
181+
});
24182

25183
/***
26184
* Test: Initialize with Matching Protocol Version
@@ -906,80 +1064,6 @@ test('should apply defaults for form-mode elicitation when applyDefaults is enab
9061064
await client.close();
9071065
});
9081066

909-
/***
910-
* Test: Type Checking
911-
* Test that custom request/notification/result schemas can be used with the Client class.
912-
*/
913-
test('should typecheck', () => {
914-
const GetWeatherRequestSchema = RequestSchema.extend({
915-
method: z.literal('weather/get'),
916-
params: z.object({
917-
city: z.string()
918-
})
919-
});
920-
921-
const GetForecastRequestSchema = RequestSchema.extend({
922-
method: z.literal('weather/forecast'),
923-
params: z.object({
924-
city: z.string(),
925-
days: z.number()
926-
})
927-
});
928-
929-
const WeatherForecastNotificationSchema = NotificationSchema.extend({
930-
method: z.literal('weather/alert'),
931-
params: z.object({
932-
severity: z.enum(['warning', 'watch']),
933-
message: z.string()
934-
})
935-
});
936-
937-
const WeatherRequestSchema = GetWeatherRequestSchema.or(GetForecastRequestSchema);
938-
const WeatherNotificationSchema = WeatherForecastNotificationSchema;
939-
const WeatherResultSchema = ResultSchema.extend({
940-
temperature: z.number(),
941-
conditions: z.string()
942-
});
943-
944-
type WeatherRequest = z.infer<typeof WeatherRequestSchema>;
945-
type WeatherNotification = z.infer<typeof WeatherNotificationSchema>;
946-
type WeatherResult = z.infer<typeof WeatherResultSchema>;
947-
948-
// Create a typed Client for weather data
949-
const weatherClient = new Client<WeatherRequest, WeatherNotification, WeatherResult>(
950-
{
951-
name: 'WeatherClient',
952-
version: '1.0.0'
953-
},
954-
{
955-
capabilities: {
956-
sampling: {}
957-
}
958-
}
959-
);
960-
961-
// Typecheck that only valid weather requests/notifications/results are allowed
962-
false &&
963-
weatherClient.request(
964-
{
965-
method: 'weather/get',
966-
params: {
967-
city: 'Seattle'
968-
}
969-
},
970-
WeatherResultSchema
971-
);
972-
973-
false &&
974-
weatherClient.notification({
975-
method: 'weather/alert',
976-
params: {
977-
severity: 'warning',
978-
message: 'Storm approaching'
979-
}
980-
});
981-
});
982-
9831067
/***
9841068
* Test: Handle Client Cancelling a Request
9851069
*/

0 commit comments

Comments
 (0)