perf(rpc): restore compiled Zod request schemas without override (#17374)

* perf(rpc): compile Zod request schemas lazily

* chore(deps): pin zod 4.5.4 and except it from the release-age gate

4.5.4 is the first release fixing isRecursiveSchema (upstream 84e416f, #6500),
which compile() calls on every schema — on 4.5.0 it fired .default() factories
at compile time. Verified: compile-time factory calls 0 on 4.5.4, 1 on 4.5.0.
This commit is contained in:
Neil
2026-08-31 00:47:22 -07:00
committed by GitHub
parent 0293ebe3eb
commit c09810b641
5 changed files with 84 additions and 15 deletions
+1 -1
View File
@@ -169,7 +169,7 @@
"tweetnacl": "^1.0.3",
"ws": "^8.21.3",
"yaml": "^2.8.4",
"zod": "~4.4.3"
"zod": "~4.5.4"
},
"devDependencies": {
"@dnd-kit/core": "^6.3.1",
+13 -13
View File
@@ -191,8 +191,8 @@ importers:
specifier: ^2.8.4
version: 2.8.4
zod:
specifier: ~4.4.3
version: 4.4.3
specifier: ~4.5.4
version: 4.5.4
devDependencies:
'@dnd-kit/core':
specifier: ^6.3.1
@@ -217,7 +217,7 @@ importers:
version: 3.2.0
'@stablyai/playwright-test':
specifier: ^2.1.14
version: 2.1.14(@playwright/test@1.59.1)(zod@4.4.3)
version: 2.1.14(@playwright/test@1.59.1)(zod@4.5.4)
'@tailwindcss/vite':
specifier: ^4.2.4
version: 4.2.4(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))
@@ -6982,8 +6982,8 @@ packages:
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
zod@4.4.3:
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
zod@4.5.4:
resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==}
zustand@5.0.14:
resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==}
@@ -8916,27 +8916,27 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {}
'@stablyai/playwright-base@2.1.14(@playwright/test@1.59.1)(zod@4.4.3)':
'@stablyai/playwright-base@2.1.14(@playwright/test@1.59.1)(zod@4.5.4)':
dependencies:
'@playwright/test': 1.59.1
jpeg-js: 0.4.4
p-retry: 4.6.2
pngjs: 7.0.0
optionalDependencies:
zod: 4.4.3
zod: 4.5.4
'@stablyai/playwright-test@2.1.14(@playwright/test@1.59.1)(zod@4.4.3)':
'@stablyai/playwright-test@2.1.14(@playwright/test@1.59.1)(zod@4.5.4)':
dependencies:
'@playwright/test': 1.59.1
'@stablyai/playwright': 2.1.14(@playwright/test@1.59.1)(zod@4.4.3)
'@stablyai/playwright-base': 2.1.14(@playwright/test@1.59.1)(zod@4.4.3)
'@stablyai/playwright': 2.1.14(@playwright/test@1.59.1)(zod@4.5.4)
'@stablyai/playwright-base': 2.1.14(@playwright/test@1.59.1)(zod@4.5.4)
transitivePeerDependencies:
- zod
'@stablyai/playwright@2.1.14(@playwright/test@1.59.1)(zod@4.4.3)':
'@stablyai/playwright@2.1.14(@playwright/test@1.59.1)(zod@4.5.4)':
dependencies:
'@playwright/test': 1.59.1
'@stablyai/playwright-base': 2.1.14(@playwright/test@1.59.1)(zod@4.4.3)
'@stablyai/playwright-base': 2.1.14(@playwright/test@1.59.1)(zod@4.5.4)
transitivePeerDependencies:
- zod
@@ -13787,7 +13787,7 @@ snapshots:
zod@3.25.76: {}
zod@4.4.3: {}
zod@4.5.4: {}
zustand@5.0.14(@types/react@19.2.17)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)):
optionalDependencies:
+2
View File
@@ -6,8 +6,10 @@ packages: []
minimumReleaseAge: 4320
# 6.3.289 was published 2026-08-29T12:48Z; it clears the 3-day gate on 2026-09-01T12:48Z.
# zod 4.5.4 was published 2026-08-29T17:55Z; it clears the 3-day gate on 2026-09-01T17:55Z.
minimumReleaseAgeExclude:
- pdfjs-dist@6.3.289
- zod@4.5.4
shamefullyHoist: true
supportedArchitectures:
@@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest'
import { z } from 'zod'
import { defineMethod, type RpcEnvelopeMeta, type RpcRequest } from './core'
import { parseRpcRequestParams } from './dispatcher-request-parsing'
const META: RpcEnvelopeMeta = { runtimeId: 'runtime-1' }
function request(params: unknown): RpcRequest {
return { id: 'req-1', authToken: 'token', method: 'test.parse', params }
}
describe('parseRpcRequestParams', () => {
it('parses repeated valid requests through the compiled schema', () => {
const method = defineMethod({
name: 'test.parse',
params: z.object({ requestId: z.string().min(1), count: z.number().int().nonnegative() }),
handler: () => null
})
expect(parseRpcRequestParams(request({ requestId: 'a', count: 1 }), method, META)).toEqual({
value: { requestId: 'a', count: 1 }
})
expect(parseRpcRequestParams(request({ requestId: 'b', count: 2 }), method, META)).toEqual({
value: { requestId: 'b', count: 2 }
})
})
it('preserves validation errors from the runtime fallback', () => {
const method = defineMethod({
name: 'test.parse',
params: z.object({ count: z.number().int('Count must be an integer') }),
handler: () => null
})
expect(parseRpcRequestParams(request({ count: 1.5 }), method, META)).toMatchObject({
error: {
ok: false,
error: { code: 'invalid_argument', message: 'Count must be an integer' }
}
})
})
it('keeps transform output when compilation falls back', () => {
const method = defineMethod({
name: 'test.parse',
params: z.object({ value: z.string().transform((value) => value.toUpperCase()) }),
handler: () => null
})
expect(parseRpcRequestParams(request({ value: 'ok' }), method, META)).toEqual({
value: { value: 'OK' }
})
})
})
@@ -1,3 +1,4 @@
import { compile, type ZodType } from 'zod'
import {
formatZodError,
type RpcAnyMethod,
@@ -7,6 +8,8 @@ import {
} from './core'
import { invalidArgumentResponse } from './dispatcher-error-response'
const compiledParams = new WeakMap<ZodType, ZodType>()
export function parseRpcRequestParams(
request: RpcRequest,
method: RpcAnyMethod,
@@ -15,7 +18,7 @@ export function parseRpcRequestParams(
if (method.params === null) {
return { value: undefined }
}
const result = method.params.safeParse(request.params ?? {})
const result = getCompiledParams(method.params).safeParse(request.params ?? {})
if (!result.success) {
return {
error: invalidArgumentResponse(request, meta, formatZodError(result.error))
@@ -23,3 +26,13 @@ export function parseRpcRequestParams(
}
return { value: result.data }
}
function getCompiledParams(schema: ZodType): ZodType {
const cached = compiledParams.get(schema)
if (cached) {
return cached
}
const compiled = compile(schema)
compiledParams.set(schema, compiled)
return compiled
}