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
8686 export const OPENAPI_OAUTH_CALLBACK_PATH = "/api/oauth/callback";
8787
8888 const ErrorMessage = Schema.Struct({ message: Schema.String });
89+const decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
8990
9091 const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>
91− Option.match(
92− Option.flatMap(Exit.findErrorOption(exit), Schema.decodeUnknownOption(ErrorMessage)),
93− {
94− onNone: () => fallback,
95− onSome: ({ message }) => message,
96− },
97− );
92+ Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
93+ onNone: () => fallback,
94+ onSome: ({ message }) => message,
95+ });
9896
9997 const substituteUrlVariables = (url: string, values: Record<string, string>): string => {
10098 let out = url;
packages/plugins/openapi/src/react/EditOpenApiSource.tsx+8−9
@@ -51,15 +51,14 @@ import { · 50 unmodified lines
5151 } from "../sdk/types";
5252
5353 const ErrorMessage = Schema.Struct({ message: Schema.String });
54+const decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
55+const isOpenApiSourceBindingValue = Schema.is(OpenApiSourceBindingValue);
5456
5557 const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>
56− Option.match(
57− Option.flatMap(Exit.findErrorOption(exit), Schema.decodeUnknownOption(ErrorMessage)),
58− {
59− onNone: () => fallback,
60− onSome: ({ message }) => message,
61− },
62− );
58+ Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
59+ onNone: () => fallback,
60+ onSome: ({ message }) => message,
61+ });
6362
6463 type SlotDef =
6564 | {
@@ -137,12 +136,12 @@ const effectiveBindingForScope = ( · 71 unmodified lines
137136 const isSecretBindingValue = (
138137 value: unknown,
139138 ): value is Extract<OpenApiSourceBindingValueType, { readonly kind: "secret" }> =>
140− Schema.is(OpenApiSourceBindingValue)(value) && value.kind === "secret";
139+ isOpenApiSourceBindingValue(value) && value.kind === "secret";
141140
142141 const isConnectionBindingValue = (
143142 value: unknown,
144143 ): value is Extract<OpenApiSourceBindingValueType, { readonly kind: "connection" }> =>
145− Schema.is(OpenApiSourceBindingValue)(value) && value.kind === "connection";
144+ isOpenApiSourceBindingValue(value) && value.kind === "connection";
146145
147146 export default function EditOpenApiSource(props: {
148147 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
3333 name: Schema.String,
3434 }),
3535 );
36+const decodeJsonNameBody = Schema.decodeUnknownSync(JsonNameBody);
3637
3738 const memoryProvider: SecretProvider = (() => {
3839 const store = new Map<string, string>();
@@ -216,7 +217,7 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 177 unmodified lines
216217 expect(captured.contentType).toBe("text/xml");
217218 const body = captured.body.toString("utf8");
218219 expect(body).not.toBe("[object Object]");
219− expect(Schema.decodeUnknownSync(JsonNameBody)(body)).toEqual({ name: "Acme" });
220+ expect(decodeJsonNameBody(body)).toEqual({ name: "Acme" });
220221 }),
221222 );
222223
@@ -364,7 +365,7 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 141 unmodified lines
364365 );
365366
366367 expect(captured.contentType).toBe("application/json");
367− expect(Schema.decodeUnknownSync(JsonNameBody)(captured.body.toString("utf8"))).toEqual({
368+ expect(decodeJsonNameBody(captured.body.toString("utf8"))).toEqual({
368369 name: "Acme",
369370 });
370371 }),
packages/plugins/openapi/src/sdk/store.ts+3−1
@@ -287,8 +287,10 @@ const SourceNameStorageRow = Schema.Struct({ · 286 unmodified lines
287287 });
288288 const decodeSourceNameStorageRow = Schema.decodeUnknownSync(SourceNameStorageRow);
289289
290+const decodeStorageString = Schema.decodeUnknownSync(Schema.String);
291+
290292 const 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));
292294
293295 interface ChildRow {
294296 readonly id: string;