v2 backwards compat: specTypeSchema exported - synchronous StandardSchemaV1 (#2047)

This commit is contained in:
Konstantin Konstantinov 2026-05-15 16:51:48 +03:00 • committed by GitHub
commit 22595b9685
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 41 additions and 19 deletions

View file

@ -140,7 +140,7 @@ export { InMemoryTaskMessageQueue, InMemoryTaskStore } from '../../experimental/
// Validator types and classes
export type { SpecTypeName, SpecTypes } from '../../types/specTypeSchema.js';
export { isSpecType, specTypeSchemas } from '../../types/specTypeSchema.js';
export type { StandardSchemaV1, StandardSchemaWithJSON } from '../../util/standardSchema.js';
export type { StandardSchemaV1, StandardSchemaV1Sync, StandardSchemaWithJSON } from '../../util/standardSchema.js';
export { AjvJsonSchemaValidator } from '../../validators/ajvProvider.js';
export type { CfWorkerSchemaDraft } from '../../validators/cfWorkerProvider.js';
// fromJsonSchema is intentionally NOT exported here — the server and client packages

View file

@ -13,9 +13,9 @@ declare const untrusted: unknown;
declare const value: unknown;
declare const mixed: unknown[];
async function specTypeSchemas_basicUsage() {
function specTypeSchemas_basicUsage() {
//#region specTypeSchemas_basicUsage
const result = await specTypeSchemas.CallToolResult['~standard'].validate(untrusted);
const result = specTypeSchemas.CallToolResult['~standard'].validate(untrusted);
if (result.issues === undefined) {
// result.value is CallToolResult
}

View file

@ -13,7 +13,7 @@ import {
OpenIdProviderDiscoveryMetadataSchema,
OpenIdProviderMetadataSchema
} from '../shared/auth.js';
import type { StandardSchemaV1 } from '../util/standardSchema.js';
import type { StandardSchemaV1, StandardSchemaV1Sync } from '../util/standardSchema.js';
import * as schemas from './schemas.js';
/**
@ -235,7 +235,7 @@ type SpecTypeInputs = {
[K in SchemaKey as StripSchemaSuffix<K>]: SchemaFor<K> extends z.ZodType ? z.input<SchemaFor<K>> : never;
};
type SchemaRecord = { readonly [K in SpecTypeName]: StandardSchemaV1<SpecTypeInputs[K], SpecTypes[K]> };
type SchemaRecord = { readonly [K in SpecTypeName]: StandardSchemaV1Sync<SpecTypeInputs[K], SpecTypes[K]> };
type GuardRecord = { readonly [K in SpecTypeName]: (value: unknown) => value is SpecTypeInputs[K] };
const _specTypeSchemas: Record<string, StandardSchemaV1> = {};
@ -265,7 +265,7 @@ for (const [key, schema] of Object.entries(authSchemas)) {
*
* @example
* ```ts source="./specTypeSchema.examples.ts#specTypeSchemas_basicUsage"
* const result = await specTypeSchemas.CallToolResult['~standard'].validate(untrusted);
* const result = specTypeSchemas.CallToolResult['~standard'].validate(untrusted);
* if (result.issues === undefined) {
* // result.value is CallToolResult
* }

View file

@ -114,6 +114,28 @@ export namespace StandardSchemaWithJSON {
export type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
}
/**
* Narrowing of {@linkcode StandardSchemaV1} whose `validate` is guaranteed synchronous.
*
* The Zod schemas backing `specTypeSchemas` contain no async refinements or transforms,
* so every entry satisfies this interface. Consumers can call `validate()` and access
* `.issues` / `.value` on the result without `await`.
*
* `StandardSchemaV1Sync` is assignable to `StandardSchemaV1` — it is a strict subtype.
*/
export interface StandardSchemaV1Sync<Input = unknown, Output = Input> extends StandardSchemaV1<Input, Output> {
readonly '~standard': StandardSchemaV1Sync.Props<Input, Output>;
}
export namespace StandardSchemaV1Sync {
export interface Props<Input = unknown, Output = Input> extends StandardSchemaV1.Props<Input, Output> {
readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => StandardSchemaV1.Result<Output>;
}
export type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
export type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
}
// Type guards
export function isStandardJSONSchema(schema: unknown): schema is StandardJSONSchemaV1 {

View file

@ -16,14 +16,14 @@ import type {
} from '../../src/types/types.js';
describe('specTypeSchemas', () => {
it('returns a StandardSchemaV1 validator that accepts valid values', () => {
it('returns a StandardSchemaV1Sync validator that accepts valid values', () => {
const result = specTypeSchemas.Implementation['~standard'].validate({ name: 'x', version: '1.0.0' });
expect((result as { issues?: unknown }).issues).toBeUndefined();
expect(result.issues).toBeUndefined();
});
it('returns a validator that rejects invalid values with issues', () => {
const result = specTypeSchemas.Implementation['~standard'].validate({ name: 'x' });
expect((result as { issues?: readonly unknown[] }).issues?.length).toBeGreaterThan(0);
expect(result.issues?.length).toBeGreaterThan(0);
});
it('rejects unknown names at compile time and is undefined at runtime', () => {
@ -33,14 +33,14 @@ describe('specTypeSchemas', () => {
it('covers JSON-RPC envelope types', () => {
const ok = specTypeSchemas.JSONRPCRequest['~standard'].validate({ jsonrpc: '2.0', id: 1, method: 'ping' });
expect((ok as { issues?: unknown }).issues).toBeUndefined();
expect(ok.issues).toBeUndefined();
});
it('covers OAuth types from shared/auth.ts', () => {
const ok = specTypeSchemas.OAuthTokens['~standard'].validate({ access_token: 'x', token_type: 'Bearer' });
expect((ok as { issues?: unknown }).issues).toBeUndefined();
expect(ok.issues).toBeUndefined();
const bad = specTypeSchemas.OAuthTokens['~standard'].validate({ token_type: 'Bearer' });
expect((bad as { issues?: readonly unknown[] }).issues?.length).toBeGreaterThan(0);
expect(bad.issues?.length).toBeGreaterThan(0);
});
});