History

perf(schema): hoist Schema compilers and add lint rule

Schema.is, Schema.decode*, Schema.encode*, Schema.validate*, and Schema.parse* each compile a parser/guard from a schema. Calling them inside a function body rebuilds that function on every invocation. Adds executor/no-inline-schema-compile (oxlint plugin rule) that flags these calls when they appear inside a function body, with two severities: HIGH when the schema argument is itself an inline Schema.X(...) literal (both AST and compile rebuilt), MEDIUM when only the compiler is re-called. Fixes the 44 existing violations across 22 files by hoisting the compiled function (and where applicable, its schema literal) to module scope. Notable consolidations: - oauth-service.ts: 8 sites (5 decode + 3 encode of OAuthProviderState) collapse into two hoisted helpers - jwks-cache.ts, mcp-auth.ts: per-call guard compilation removed from JWT verify error path - React Add*Source / EditOpenApiSource components: shared decodeErrorMessage / isOpenApiSourceBindingValue hoisted migrate-connections.ts has one targeted oxlint-disable for a higher-order helper whose schema is a function parameter; the compile is hoisted into the outer closure but the rule (correctly) flags any Schema compiler call inside a function body.

Rhys Sullivan committed 6 months ago

Commit 25515e31 parent bfb87a025 files · +224 −99Browse files

Showing the changes to packages/plugins/openapi/src (4 of 25 files). Show all

Unified · Split

4 files
  1. packages/plugins/openapi/src/react/AddOpenApiSource.tsx +5 −7
  2. packages/plugins/openapi/src/react/EditOpenApiSource.tsx +8 −9
  3. packages/plugins/openapi/src/sdk/non-json-body.test.ts +3 −2
  4. packages/plugins/openapi/src/sdk/store.ts +3 −1
packages/plugins/openapi/src/react/AddOpenApiSource.tsx+5−7
@@ -86,15 +86,13 @@ export const OPENAPI_OAUTH_POPUP_NAME = "openapi-oauth"; · 85 unmodified lines
86export const OPENAPI_OAUTH_CALLBACK_PATH = "/api/oauth/callback";86export const OPENAPI_OAUTH_CALLBACK_PATH = "/api/oauth/callback";
87 87
88const ErrorMessage = Schema.Struct({ message: Schema.String });88const ErrorMessage = Schema.Struct({ message: Schema.String });
89const decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
89 90
90const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>91const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>
91 Option.match(92 Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
92 Option.flatMap(Exit.findErrorOption(exit), Schema.decodeUnknownOption(ErrorMessage)),93 onNone: () => fallback,
93 {94 onSome: ({ message }) => message,
94 onNone: () => fallback,95 });
95 onSome: ({ message }) => message,
96 },
97 );
98 96
99const substituteUrlVariables = (url: string, values: Record<string, string>): string => {97const substituteUrlVariables = (url: string, values: Record<string, string>): string => {
100 let out = url;98 let out = url;
packages/plugins/openapi/src/react/EditOpenApiSource.tsx+8−9
@@ -51,15 +51,14 @@ import { · 50 unmodified lines
51} from "../sdk/types";51} from "../sdk/types";
52 52
53const ErrorMessage = Schema.Struct({ message: Schema.String });53const ErrorMessage = Schema.Struct({ message: Schema.String });
54const decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
55const isOpenApiSourceBindingValue = Schema.is(OpenApiSourceBindingValue);
54 56
55const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>57const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>
56 Option.match(58 Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
57 Option.flatMap(Exit.findErrorOption(exit), Schema.decodeUnknownOption(ErrorMessage)),59 onNone: () => fallback,
58 {60 onSome: ({ message }) => message,
59 onNone: () => fallback,61 });
60 onSome: ({ message }) => message,
61 },
62 );
63 62
64type SlotDef =63type SlotDef =
65 | {64 | {
@@ -137,12 +136,12 @@ const effectiveBindingForScope = ( · 71 unmodified lines
137const isSecretBindingValue = (136const isSecretBindingValue = (
138 value: unknown,137 value: unknown,
139): value is Extract<OpenApiSourceBindingValueType, { readonly kind: "secret" }> =>138): value is Extract<OpenApiSourceBindingValueType, { readonly kind: "secret" }> =>
140 Schema.is(OpenApiSourceBindingValue)(value) && value.kind === "secret";139 isOpenApiSourceBindingValue(value) && value.kind === "secret";
141 140
142const isConnectionBindingValue = (141const isConnectionBindingValue = (
143 value: unknown,142 value: unknown,
144): value is Extract<OpenApiSourceBindingValueType, { readonly kind: "connection" }> =>143): value is Extract<OpenApiSourceBindingValueType, { readonly kind: "connection" }> =>
145 Schema.is(OpenApiSourceBindingValue)(value) && value.kind === "connection";144 isOpenApiSourceBindingValue(value) && value.kind === "connection";
146 145
147export default function EditOpenApiSource(props: {146export default function EditOpenApiSource(props: {
148 readonly sourceId: string;147 readonly sourceId: string;
packages/plugins/openapi/src/sdk/non-json-body.test.ts+3−2
@@ -33,6 +33,7 @@ const JsonNameBody = Schema.fromJsonString( · 32 unmodified lines
33 name: Schema.String,33 name: Schema.String,
34 }),34 }),
35);35);
36const decodeJsonNameBody = Schema.decodeUnknownSync(JsonNameBody);
36 37
37const memoryProvider: SecretProvider = (() => {38const memoryProvider: SecretProvider = (() => {
38 const store = new Map<string, string>();39 const store = new Map<string, string>();
@@ -216,7 +217,7 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 177 unmodified lines
216 expect(captured.contentType).toBe("text/xml");217 expect(captured.contentType).toBe("text/xml");
217 const body = captured.body.toString("utf8");218 const body = captured.body.toString("utf8");
218 expect(body).not.toBe("[object Object]");219 expect(body).not.toBe("[object Object]");
219 expect(Schema.decodeUnknownSync(JsonNameBody)(body)).toEqual({ name: "Acme" });220 expect(decodeJsonNameBody(body)).toEqual({ name: "Acme" });
220 }),221 }),
221 );222 );
222 223
@@ -364,7 +365,7 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 141 unmodified lines
364 );365 );
365 366
366 expect(captured.contentType).toBe("application/json");367 expect(captured.contentType).toBe("application/json");
367 expect(Schema.decodeUnknownSync(JsonNameBody)(captured.body.toString("utf8"))).toEqual({368 expect(decodeJsonNameBody(captured.body.toString("utf8"))).toEqual({
368 name: "Acme",369 name: "Acme",
369 });370 });
370 }),371 }),
packages/plugins/openapi/src/sdk/store.ts+3−1
@@ -287,8 +287,10 @@ const SourceNameStorageRow = Schema.Struct({ · 286 unmodified lines
287});287});
288const decodeSourceNameStorageRow = Schema.decodeUnknownSync(SourceNameStorageRow);288const decodeSourceNameStorageRow = Schema.decodeUnknownSync(SourceNameStorageRow);
289 289
290const decodeStorageString = Schema.decodeUnknownSync(Schema.String);
291
290const decodeStorageDate = (value: unknown): Date =>292const decodeStorageDate = (value: unknown): Date =>
291 value instanceof Date ? value : new Date(Schema.decodeUnknownSync(Schema.String)(value));293 value instanceof Date ? value : new Date(decodeStorageString(value));
292 294
293interface ChildRow {295interface ChildRow {
294 readonly id: string;296 readonly id: string;