import { lazy, useCallback } from "react";import { useAtomSet } from "@effect/atom-react";import * as Exit from "effect/Exit";import type { IntegrationPlugin, IntegrationPreset, IntegrationQuickAddInput, IntegrationQuickAddResult,} from "@executor-js/sdk/client";import { AuthTemplateSlug } from "@executor-js/sdk/shared";import { slugifyNamespace } from "@executor-js/react/plugins/integration-identity";import { placementFromHeaderPattern, type Placement } from "@executor-js/react/lib/auth-placements";import { integrationWriteKeys } from "@executor-js/react/api/reactivity-keys";import { openApiPresets } from "../sdk/presets";import { detectedAuthenticationTemplates } from "../sdk/derive-auth";import { addOpenApiSpec, previewOpenApiSpec } from "./atoms";import { openApiWireAuthInput, templateFromPlacements } from "./auth-method-config";import { decodeOpenApiSpecOverrides } from "../sdk/spec-overrides"; const normalizedSpecUrl = (url: string): string => { if (!URL.canParse(url)) return url.trim().replace(/\/$/, ""); const parsed = new URL(url); parsed.hash = ""; parsed.searchParams.sort(); return parsed.toString().replace(/\/$/, "");}; /** The preset table still knows things no spec can say — GitHub's OAuth * endpoints against a spec that declares NO security at all. A registry row * whose URL is a preset's URL gets that knowledge pulled across. Matches the * plugin's COMPLETE preset list, so a deployment's custom presets * contribute too, not just the built-ins. */const presetForSpecUrl = ( presets: readonly IntegrationPreset[], url: string,): IntegrationPreset | undefined => { const target = normalizedSpecUrl(url); return presets.find( (preset) => preset.url !== undefined && normalizedSpecUrl(preset.url) === target, );}; /** One-click add for a registry OpenAPI row. The spec itself is the * configuration: omitting `authenticationTemplate` and `baseUrl` tells the * server to derive both from the document, exactly what the add page's * untouched defaults submit. Registry spec overrides ride along. */ /** The spec inputs a quick add must hold CONSISTENT between preview and add: * deriving auth from a document other than the one being added is how a * preset's scope overrides got silently dropped. Preset overrides win over * the registry's, mirroring the full add page. */export const quickAddSpecPlan = ( preset: IntegrationPreset | undefined, registryOverrides: ReturnType<typeof decodeOpenApiSpecOverrides>,): { readonly specFormat?: string; readonly specOverrides?: NonNullable<ReturnType<typeof decodeOpenApiSpecOverrides>>;} => { const presetOverrides = preset?.specOverrides ? decodeOpenApiSpecOverrides(preset.specOverrides) : undefined; // PRESENCE-based, mirroring the full page's `presetOverrides ?? // registryOverrides`: a preset declaring an explicitly EMPTY override list // is a decision — suppress the registry's patches — not an absence. const effective = presetOverrides !== undefined ? presetOverrides : registryOverrides; return { ...(preset?.specFormat ? { specFormat: preset.specFormat } : {}), ...(effective && effective.length > 0 ? { specOverrides: effective } : {}), };}; /** The preview and add requests built from ONE plan, so they cannot diverge: * deriving auth from a different effective document than the one stored is * the bug this module has now had twice. The add payload takes further * fields (family, health check, auth template) AFTER this base. */export const quickAddRequestPayloads = ( input: { readonly url: string; readonly name: string; readonly domain?: string }, slug: string, specPlan: ReturnType<typeof quickAddSpecPlan>,): { readonly preview: { readonly spec: string } & ReturnType<typeof quickAddSpecPlan>; readonly add: { readonly spec: { readonly kind: "url"; readonly url: string }; readonly slug: string; readonly name: string; readonly displayDomain?: string; } & ReturnType<typeof quickAddSpecPlan>;} => ({ preview: { spec: input.url, ...specPlan }, add: { spec: { kind: "url", url: input.url }, slug, name: input.name, ...(input.domain ? { displayDomain: input.domain } : {}), ...specPlan, },}); /** The full add page's method policy as one pure step: preset OAuth wins * outright; else every preview-detected method is preserved and the * registry's key placement is appended only when the detected set has no * key method (GitHub declares no security at all). `preview` MUST be the * summary of the same effective document the add will store. */export const composeQuickAddAuth = ( presetMethods: readonly ReturnType<typeof openApiWireAuthInput>[], registryPlacement: Placement | null, preview: { readonly headerPresets: Parameters<typeof detectedAuthenticationTemplates>[0]; readonly oauth2Presets: Parameters<typeof detectedAuthenticationTemplates>[1]; readonly servers: readonly { readonly url: string }[]; } | null,): readonly ReturnType<typeof openApiWireAuthInput>[] => { if (presetMethods.length > 0) { return registryPlacement ? [...presetMethods, openApiWireAuthInput(templateFromPlacements([registryPlacement]))] : presetMethods; } if (!registryPlacement || preview === null) return presetMethods; const detected = detectedAuthenticationTemplates( preview.headerPresets, preview.oauth2Presets, preview.servers[0]?.url ?? "", ); const detectedHasApiKey = detected.some((template) => template.kind === "apikey"); return [ ...detected.map(openApiWireAuthInput), ...(detectedHasApiKey ? [] : [openApiWireAuthInput(templateFromPlacements([registryPlacement]))]), ];}; export interface QuickAddDeps { readonly presets: readonly IntegrationPreset[]; /** The two mutations, injected so the OPERATION is testable against its * actual outgoing payloads — helper-level tests kept passing while a call * site quietly stopped using the shared plan. */ readonly preview: ( payload: ReturnType<typeof quickAddRequestPayloads>["preview"], ) => Promise<Exit.Exit<NonNullable<Parameters<typeof composeQuickAddAuth>[2]>, unknown>>; readonly add: ( payload: ReturnType<typeof quickAddRequestPayloads>["add"] & { readonly family?: string; readonly healthCheck?: IntegrationPreset["healthCheck"]; readonly authenticationTemplate?: ReturnType<typeof openApiWireAuthInput>[]; }, ) => Promise<Exit.Exit<{ readonly slug: string }, unknown>>;} /** The complete quick-add operation, pure of React: everything between the * picker's click and the two API calls. */export const performQuickAdd = async ( deps: QuickAddDeps, input: IntegrationQuickAddInput,): Promise<IntegrationQuickAddResult> => { const { presets } = deps; const slug = slugifyNamespace(input.slug ?? input.name); if (!slug) return { ok: false, reason: "no derivable slug" }; // Registry overrides arrive as untyped JSON; a malformed patch goes to // the configuration screen (its editor renders the parse error) rather // than being silently dropped from a "successful" quick add. const specOverrides = input.specOverrides ? decodeOpenApiSpecOverrides(input.specOverrides) : undefined; if (input.specOverrides && input.specOverrides.length > 0 && specOverrides === undefined) { return { ok: false, reason: "unparseable spec overrides" }; } const preset = presetForSpecUrl(presets, input.url); const registryPlacement = input.authHeader ? placementFromHeaderPattern(input.authHeader) : null; const presetMethods = (preset?.authTemplate ?? []).flatMap((template) => template.kind === "oauth2" ? [ openApiWireAuthInput({ ...template, slug: AuthTemplateSlug.make(template.slug), resource: template.resource ?? undefined, }), ] : [], ); // ONE spec plan for both preview and add: deriving auth from a // different effective document than the one stored is how a preset's // scope overrides got silently dropped. const specPlan = quickAddSpecPlan(preset, specOverrides); const requests = quickAddRequestPayloads( { url: input.url, name: input.name, ...(input.domain ? { domain: input.domain } : {}) }, slug, specPlan, ); let preview: Parameters<typeof composeQuickAddAuth>[2] = null; if (presetMethods.length === 0 && registryPlacement) { // Composing with spec knowledge needs the spec: one preview call, // only on this path (a plain registry row with no auth facts still // adds with zero extra round trips). const previewExit = await deps.preview(requests.preview); if (Exit.isFailure(previewExit)) return { ok: false, reason: "preview failed" }; preview = previewExit.value; } const authenticationTemplate = composeQuickAddAuth(presetMethods, registryPlacement, preview); const exit = await deps.add({ ...requests.add, ...(preset?.family ? { family: preset.family } : {}), ...(preset?.healthCheck ? { healthCheck: preset.healthCheck } : {}), ...(authenticationTemplate.length > 0 ? { authenticationTemplate: [...authenticationTemplate] } : {}), }); if (Exit.isFailure(exit)) return { ok: false, reason: "add failed" }; return { ok: true, slug: String(exit.value.slug) };}; function makeUseQuickAdd(presets: readonly IntegrationPreset[]) { return function useOpenApiQuickAdd(): ( input: IntegrationQuickAddInput, ) => Promise<IntegrationQuickAddResult> { const doAdd = useAtomSet(addOpenApiSpec, { mode: "promiseExit" }); const doPreview = useAtomSet(previewOpenApiSpec, { mode: "promiseExit" }); return useCallback( (input) => performQuickAdd( { presets, preview: (payload) => doPreview({ payload }), add: (payload) => doAdd({ payload, reactivityKeys: integrationWriteKeys }), }, input, ), [doAdd, doPreview], ); };} const importAdd = () => import("./AddOpenApiIntegration");const importEditSheet = () => import("./UpdateSpecSection");const importAccounts = () => import("./OpenApiAccountsPanel"); export interface OpenApiClientConfig { readonly presets?: readonly IntegrationPreset[];} export const createOpenApiIntegrationPlugin = (config?: OpenApiClientConfig): IntegrationPlugin => { // Built-ins are registry-listed (the picker shows the registry's card); // a deployment's custom presets are not, and keep their own cards. const presets: readonly IntegrationPreset[] = [ ...openApiPresets.map((preset) => ({ ...preset, registryListed: true })), ...(config?.presets ?? []), ]; return { key: "openapi", label: "OpenAPI", add: lazy(importAdd), editSheet: lazy(importEditSheet), accounts: lazy(importAccounts), presets, preload: () => { void importAdd(); void importEditSheet(); void importAccounts(); }, useQuickAdd: makeUseQuickAdd(presets), };}; export const openApiIntegrationPlugin: IntegrationPlugin = createOpenApiIntegrationPlugin();
import { lazy, useCallback } from "react";
import { useAtomSet } from "@effect/atom-react";
import * as Exit from "effect/Exit";
import type {
IntegrationPlugin,
IntegrationPreset,
IntegrationQuickAddInput,
IntegrationQuickAddResult,
} from "@executor-js/sdk/client";
import { AuthTemplateSlug } from "@executor-js/sdk/shared";
import { slugifyNamespace } from "@executor-js/react/plugins/integration-identity";
import { placementFromHeaderPattern, type Placement } from "@executor-js/react/lib/auth-placements";
import { integrationWriteKeys } from "@executor-js/react/api/reactivity-keys";
import { openApiPresets } from "../sdk/presets";
import { detectedAuthenticationTemplates } from "../sdk/derive-auth";
import { addOpenApiSpec, previewOpenApiSpec } from "./atoms";
import { openApiWireAuthInput, templateFromPlacements } from "./auth-method-config";
import { decodeOpenApiSpecOverrides } from "../sdk/spec-overrides";
const normalizedSpecUrl = (url: string): string => {
if (!URL.canParse(url)) return url.trim().replace(/\/$/, "");
const parsed = new URL(url);
parsed.hash = "";
parsed.searchParams.sort();
return parsed.toString().replace(/\/$/, "");
};
/** The preset table still knows things no spec can say — GitHub's OAuth
* endpoints against a spec that declares NO security at all. A registry row
* whose URL is a preset's URL gets that knowledge pulled across. Matches the
* plugin's COMPLETE preset list, so a deployment's custom presets
* contribute too, not just the built-ins. */
const presetForSpecUrl = (
presets: readonly IntegrationPreset[],
url: string,
): IntegrationPreset | undefined => {
const target = normalizedSpecUrl(url);
return presets.find(
(preset) => preset.url !== undefined && normalizedSpecUrl(preset.url) === target,
);
};
/** One-click add for a registry OpenAPI row. The spec itself is the
* configuration: omitting `authenticationTemplate` and `baseUrl` tells the
* server to derive both from the document, exactly what the add page's
* untouched defaults submit. Registry spec overrides ride along. */
/** The spec inputs a quick add must hold CONSISTENT between preview and add:
* deriving auth from a document other than the one being added is how a
* preset's scope overrides got silently dropped. Preset overrides win over
* the registry's, mirroring the full add page. */
export const quickAddSpecPlan = (
preset: IntegrationPreset | undefined,
registryOverrides: ReturnType<typeof decodeOpenApiSpecOverrides>,
): {
readonly specFormat?: string;
readonly specOverrides?: NonNullable<ReturnType<typeof decodeOpenApiSpecOverrides>>;
} => {
const presetOverrides = preset?.specOverrides
? decodeOpenApiSpecOverrides(preset.specOverrides)
: undefined;
// PRESENCE-based, mirroring the full page's `presetOverrides ??
// registryOverrides`: a preset declaring an explicitly EMPTY override list
// is a decision — suppress the registry's patches — not an absence.
const effective = presetOverrides !== undefined ? presetOverrides : registryOverrides;
return {
...(preset?.specFormat ? { specFormat: preset.specFormat } : {}),
...(effective && effective.length > 0 ? { specOverrides: effective } : {}),
};
};
/** The preview and add requests built from ONE plan, so they cannot diverge:
* deriving auth from a different effective document than the one stored is
* the bug this module has now had twice. The add payload takes further
* fields (family, health check, auth template) AFTER this base. */
export const quickAddRequestPayloads = (
input: { readonly url: string; readonly name: string; readonly domain?: string },
slug: string,
specPlan: ReturnType<typeof quickAddSpecPlan>,
): {
readonly preview: { readonly spec: string } & ReturnType<typeof quickAddSpecPlan>;
readonly add: {
readonly spec: { readonly kind: "url"; readonly url: string };
readonly slug: string;
readonly name: string;
readonly displayDomain?: string;
} & ReturnType<typeof quickAddSpecPlan>;
} => ({
preview: { spec: input.url, ...specPlan },
add: {
spec: { kind: "url", url: input.url },
slug,
name: input.name,
...(input.domain ? { displayDomain: input.domain } : {}),
...specPlan,
},
});
/** The full add page's method policy as one pure step: preset OAuth wins
* outright; else every preview-detected method is preserved and the
* registry's key placement is appended only when the detected set has no
* key method (GitHub declares no security at all). `preview` MUST be the
* summary of the same effective document the add will store. */
export const composeQuickAddAuth = (
presetMethods: readonly ReturnType<typeof openApiWireAuthInput>[],
registryPlacement: Placement | null,
preview: {
readonly headerPresets: Parameters<typeof detectedAuthenticationTemplates>[0];
readonly oauth2Presets: Parameters<typeof detectedAuthenticationTemplates>[1];
readonly servers: readonly { readonly url: string }[];
} | null,
): readonly ReturnType<typeof openApiWireAuthInput>[] => {
if (presetMethods.length > 0) {
return registryPlacement
? [...presetMethods, openApiWireAuthInput(templateFromPlacements([registryPlacement]))]
: presetMethods;
}
if (!registryPlacement || preview === null) return presetMethods;
const detected = detectedAuthenticationTemplates(
preview.headerPresets,
preview.oauth2Presets,
preview.servers[0]?.url ?? "",
);
const detectedHasApiKey = detected.some((template) => template.kind === "apikey");
return [
...detected.map(openApiWireAuthInput),
...(detectedHasApiKey
? []
: [openApiWireAuthInput(templateFromPlacements([registryPlacement]))]),
];
};
export interface QuickAddDeps {
readonly presets: readonly IntegrationPreset[];
/** The two mutations, injected so the OPERATION is testable against its
* actual outgoing payloads — helper-level tests kept passing while a call
* site quietly stopped using the shared plan. */
readonly preview: (
payload: ReturnType<typeof quickAddRequestPayloads>["preview"],
) => Promise<Exit.Exit<NonNullable<Parameters<typeof composeQuickAddAuth>[2]>, unknown>>;
readonly add: (
payload: ReturnType<typeof quickAddRequestPayloads>["add"] & {
readonly family?: string;
readonly healthCheck?: IntegrationPreset["healthCheck"];
readonly authenticationTemplate?: ReturnType<typeof openApiWireAuthInput>[];
},
) => Promise<Exit.Exit<{ readonly slug: string }, unknown>>;
}
/** The complete quick-add operation, pure of React: everything between the
* picker's click and the two API calls. */
export const performQuickAdd = async (
deps: QuickAddDeps,
input: IntegrationQuickAddInput,
): Promise<IntegrationQuickAddResult> => {
const { presets } = deps;
const slug = slugifyNamespace(input.slug ?? input.name);
if (!slug) return { ok: false, reason: "no derivable slug" };
// Registry overrides arrive as untyped JSON; a malformed patch goes to
// the configuration screen (its editor renders the parse error) rather
// than being silently dropped from a "successful" quick add.
const specOverrides = input.specOverrides
? decodeOpenApiSpecOverrides(input.specOverrides)
: undefined;
if (input.specOverrides && input.specOverrides.length > 0 && specOverrides === undefined) {
return { ok: false, reason: "unparseable spec overrides" };
}
const preset = presetForSpecUrl(presets, input.url);
const registryPlacement = input.authHeader ? placementFromHeaderPattern(input.authHeader) : null;
const presetMethods = (preset?.authTemplate ?? []).flatMap((template) =>
template.kind === "oauth2"
? [
openApiWireAuthInput({
...template,
slug: AuthTemplateSlug.make(template.slug),
resource: template.resource ?? undefined,
}),
]
: [],
);
// ONE spec plan for both preview and add: deriving auth from a
// different effective document than the one stored is how a preset's
// scope overrides got silently dropped.
const specPlan = quickAddSpecPlan(preset, specOverrides);
const requests = quickAddRequestPayloads(
{ url: input.url, name: input.name, ...(input.domain ? { domain: input.domain } : {}) },
slug,
specPlan,
);
let preview: Parameters<typeof composeQuickAddAuth>[2] = null;
if (presetMethods.length === 0 && registryPlacement) {
// Composing with spec knowledge needs the spec: one preview call,
// only on this path (a plain registry row with no auth facts still
// adds with zero extra round trips).
const previewExit = await deps.preview(requests.preview);
if (Exit.isFailure(previewExit)) return { ok: false, reason: "preview failed" };
preview = previewExit.value;
}
const authenticationTemplate = composeQuickAddAuth(presetMethods, registryPlacement, preview);
const exit = await deps.add({
...requests.add,
...(preset?.family ? { family: preset.family } : {}),
...(preset?.healthCheck ? { healthCheck: preset.healthCheck } : {}),
...(authenticationTemplate.length > 0
? { authenticationTemplate: [...authenticationTemplate] }
: {}),
});
if (Exit.isFailure(exit)) return { ok: false, reason: "add failed" };
return { ok: true, slug: String(exit.value.slug) };
};
function makeUseQuickAdd(presets: readonly IntegrationPreset[]) {
return function useOpenApiQuickAdd(): (
input: IntegrationQuickAddInput,
) => Promise<IntegrationQuickAddResult> {
const doAdd = useAtomSet(addOpenApiSpec, { mode: "promiseExit" });
const doPreview = useAtomSet(previewOpenApiSpec, { mode: "promiseExit" });
return useCallback(
(input) =>
performQuickAdd(
{
presets,
preview: (payload) => doPreview({ payload }),
add: (payload) => doAdd({ payload, reactivityKeys: integrationWriteKeys }),
},
input,
),
[doAdd, doPreview],
);
};
}
const importAdd = () => import("./AddOpenApiIntegration");
const importEditSheet = () => import("./UpdateSpecSection");
const importAccounts = () => import("./OpenApiAccountsPanel");
export interface OpenApiClientConfig {
readonly presets?: readonly IntegrationPreset[];
}
export const createOpenApiIntegrationPlugin = (config?: OpenApiClientConfig): IntegrationPlugin => {
// Built-ins are registry-listed (the picker shows the registry's card);
// a deployment's custom presets are not, and keep their own cards.
const presets: readonly IntegrationPreset[] = [
...openApiPresets.map((preset) => ({ ...preset, registryListed: true })),
...(config?.presets ?? []),
];
return {
key: "openapi",
label: "OpenAPI",
add: lazy(importAdd),
editSheet: lazy(importEditSheet),
accounts: lazy(importAccounts),
presets,
preload: () => {
void importAdd();
void importEditSheet();
void importAccounts();
},
useQuickAdd: makeUseQuickAdd(presets),
};
};
export const openApiIntegrationPlugin: IntegrationPlugin = createOpenApiIntegrationPlugin();