History

feat: end-to-end source add flow + remove plugin-specific list/remove

- Wire OpenAPI plugin into server (executor config + API handlers) - AddOpenApiSource calls previewSpec + addSpec APIs through the flow - Sources page uses core toolsAtom() grouped by namespace tag - Remove listSpecs/removeSpec from OpenAPI API (core tools handles it) - Remove listNamespaces from operation store, listSpecs from extension - Sources page refreshes tool list on add complete

Rhys Sullivan committed 7 months ago

Commit 2d91da11 parent 4a39f4f16 files · +327 −101Browse files

Unified · Split

16 files
  1. .gitignore +2 −1
  2. apps/server/package.json +1 −0
  3. apps/server/src/handlers/openapi.ts +17 −0
  4. apps/server/src/main.ts +11 −3
  5. apps/server/src/services/executor.ts +12 −3
  6. apps/web/package.json +1 −0
  7. apps/web/src/pages/sources.tsx +85 −1
  8. bun.lock +4 −0
  9. packages/plugins/openapi/package.json +2 −0
  10. packages/plugins/openapi/src/api/group.ts +0 −17
  11. packages/plugins/openapi/src/api/handlers.ts +1 −11
  12. packages/plugins/openapi/src/react/AddOpenApiSource.tsx +187 −40
  13. packages/plugins/openapi/src/react/atoms.ts +0 −23
  14. packages/plugins/openapi/src/react/index.ts +0 −2
  15. packages/plugins/openapi/src/sdk/operation-store.ts +2 −0
  16. packages/plugins/openapi/src/sdk/plugin.ts +2 −0
.gitignore+2−1
@@ -32,4 +32,5 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json · 31 unmodified lines
32 32
33# Finder (MacOS) folder config33# Finder (MacOS) folder config
34.DS_Store34.DS_Store
35personal-notes/35personal-notes/
36*.har
apps/server/package.json+1−0
@@ -10,6 +10,7 @@ · 9 unmodified lines
10 "@effect/platform": "catalog:",10 "@effect/platform": "catalog:",
11 11
12 "@executor/api": "workspace:*",12 "@executor/api": "workspace:*",
13 "@executor/plugin-openapi": "workspace:*",
13 "@executor/sdk": "workspace:*",14 "@executor/sdk": "workspace:*",
14 "effect": "catalog:"15 "effect": "catalog:"
15 },16 },
apps/server/src/handlers/openapi.ts+17−0
@@ -0,0 +1,17 @@
1import { Effect, Layer } from "effect";
2import {
3 OpenApiHandlers,
4 OpenApiExtensionService,
5} from "@executor/plugin-openapi/api";
6import { ExecutorService } from "../services/executor";
7
8// Wire OpenApiExtensionService from the executor's openapi extension
9const OpenApiExtensionLive = Layer.effect(
10 OpenApiExtensionService,
11 Effect.map(ExecutorService, (executor) => executor.openapi),
12);
13
14export const OpenApiHandlersLive = Layer.provide(
15 OpenApiHandlers,
16 OpenApiExtensionLive,
17);
apps/server/src/main.ts+11−3
@@ -6,17 +6,25 @@ import { · 5 unmodified lines
6} from "@effect/platform";6} from "@effect/platform";
7import { Layer } from "effect";7import { Layer } from "effect";
8 8
9import { ExecutorApi } from "@executor/api";9import { addGroup } from "@executor/api";
10import { OpenApiGroup } from "@executor/plugin-openapi/api";
10import { ToolsHandlers } from "./handlers/tools";11import { ToolsHandlers } from "./handlers/tools";
11import { SecretsHandlers } from "./handlers/secrets";12import { SecretsHandlers } from "./handlers/secrets";
13import { OpenApiHandlersLive } from "./handlers/openapi";
12import { ExecutorServiceLive } from "./services/executor";14import { ExecutorServiceLive } from "./services/executor";
13 15
16// ---------------------------------------------------------------------------
17// Composed API — core + plugin groups
18// ---------------------------------------------------------------------------
19
20const ExecutorApiWithPlugins = addGroup(OpenApiGroup);
21
14// ---------------------------------------------------------------------------22// ---------------------------------------------------------------------------
15// API layer — wire handlers23// API layer — wire handlers
16// ---------------------------------------------------------------------------24// ---------------------------------------------------------------------------
17 25
18const ApiLive = HttpApiBuilder.api(ExecutorApi).pipe(26const ApiLive = HttpApiBuilder.api(ExecutorApiWithPlugins).pipe(
19 Layer.provide([ToolsHandlers, SecretsHandlers]),27 Layer.provide([ToolsHandlers, SecretsHandlers, OpenApiHandlersLive]),
20 Layer.provide(ExecutorServiceLive),28 Layer.provide(ExecutorServiceLive),
21);29);
22 30
apps/server/src/services/executor.ts+12−3
@@ -1,7 +1,12 @@
1import { Context, Effect, Layer } from "effect";1import { Context, Effect, Layer } from "effect";
2import { createExecutor, makeTestConfig } from "@executor/sdk";2import { createExecutor, makeTestConfig } from "@executor/sdk";
3import { openApiPlugin, type OpenApiPluginExtension } from "@executor/plugin-openapi";
3 4
4import type { Executor } from "@executor/sdk";5import type { Executor } from "@executor/sdk";
6import type { ExecutorPlugin } from "@executor/sdk";
7
8type ServerPlugins = readonly [ExecutorPlugin<"openapi", OpenApiPluginExtension>];
9type ServerExecutor = Executor<ServerPlugins>;
5 10
6// ---------------------------------------------------------------------------11// ---------------------------------------------------------------------------
7// Service tag — provides the executor instance to HTTP handlers12// Service tag — provides the executor instance to HTTP handlers
@@ -9,14 +14,18 @@ import type { Executor } from "@executor/sdk"; · 1 unmodified line
9 14
10export class ExecutorService extends Context.Tag("ExecutorService")<15export class ExecutorService extends Context.Tag("ExecutorService")<
11 ExecutorService,16 ExecutorService,
12 Executor17 ServerExecutor
13>() {}18>() {}
14 19
15// ---------------------------------------------------------------------------20// ---------------------------------------------------------------------------
16// Default layer — creates an in-memory executor21// Default layer — creates an in-memory executor with plugins
17// ---------------------------------------------------------------------------22// ---------------------------------------------------------------------------
18 23
19export const ExecutorServiceLive = Layer.effect(24export const ExecutorServiceLive = Layer.effect(
20 ExecutorService,25 ExecutorService,
21 createExecutor(makeTestConfig()),26 createExecutor(
27 makeTestConfig({
28 plugins: [openApiPlugin()] as const,
29 }),
30 ),
22);31);
apps/web/package.json+1−0
@@ -4,6 +4,7 @@ · 3 unmodified lines
4 "private": true,4 "private": true,
5 "type": "module",5 "type": "module",
6 "dependencies": {6 "dependencies": {
7 "@executor/plugin-openapi": "workspace:*",
7 "@executor/react": "workspace:*",8 "@executor/react": "workspace:*",
8 "@executor/server": "workspace:*",9 "@executor/server": "workspace:*",
9 "@tanstack/react-router": "^1.168.10",10 "@tanstack/react-router": "^1.168.10",
apps/web/src/pages/sources.tsx+85−1
@@ -1,8 +1,92 @@
1import { useState, Suspense, useMemo } from "react";
2import { Result, useAtomValue, useAtomRefresh, toolsAtom } from "@executor/react";
3import type { SourcePlugin } from "@executor/react";
4import { openApiSourcePlugin } from "@executor/plugin-openapi/react";
5
6// ---------------------------------------------------------------------------
7// Registered source plugins
8// ---------------------------------------------------------------------------
9
10const sourcePlugins: SourcePlugin[] = [openApiSourcePlugin];
11
12// ---------------------------------------------------------------------------
13// Page
14// ---------------------------------------------------------------------------
15
1export function SourcesPage() {16export function SourcesPage() {
17 const [adding, setAdding] = useState<string | null>(null);
18 const tools = useAtomValue(toolsAtom());
19 const refreshTools = useAtomRefresh(toolsAtom());
20
21 // Group tools by namespace (second tag after "openapi")
22 const sources = useMemo(() => {
23 if (tools._tag !== "Success") return [];
24 const namespaces = new Map<string, number>();
25 for (const tool of tools.value) {
26 // Tools are tagged ["openapi", namespace, ...]
27 const ns = tool.tags.find(
28 (t) => t !== "openapi" && !sourcePlugins.some((p) => p.key === t),
29 );
30 if (ns) {
31 namespaces.set(ns, (namespaces.get(ns) ?? 0) + 1);
32 }
33 }
34 return [...namespaces.entries()].map(([namespace, toolCount]) => ({
35 namespace,
36 toolCount,
37 }));
38 }, [tools]);
39
40 const plugin = adding
41 ? sourcePlugins.find((p) => p.key === adding)
42 : undefined;
43
44 if (plugin) {
45 const AddComponent = plugin.add;
46 return (
47 <Suspense fallback={<p>Loading…</p>}>
48 <AddComponent
49 onComplete={() => {
50 setAdding(null);
51 refreshTools();
52 }}
53 onCancel={() => setAdding(null)}
54 />
55 </Suspense>
56 );
57 }
58
2 return (59 return (
3 <div>60 <div>
4 <h2>Sources</h2>61 <h2>Sources</h2>
5 <p style={{ color: "#888" }}>No sources configured yet.</p>62
63 {Result.match(tools, {
64 onInitial: () => <p>Loading…</p>,
65 onFailure: () => <p style={{ color: "red" }}>Failed to load sources</p>,
66 onSuccess: () =>
67 sources.length === 0 ? (
68 <p style={{ color: "#888" }}>No sources configured yet.</p>
69 ) : (
70 <ul>
71 {sources.map((s) => (
72 <li key={s.namespace}>
73 <strong>{s.namespace}</strong> — {s.toolCount} tools
74 </li>
75 ))}
76 </ul>
77 ),
78 })}
79
80 <div style={{ marginTop: "1rem" }}>
81 <h3>Add a source</h3>
82 <div style={{ display: "flex", gap: "0.5rem" }}>
83 {sourcePlugins.map((p) => (
84 <button key={p.key} onClick={() => setAdding(p.key)}>
85 {p.label}
86 </button>
87 ))}
88 </div>
89 </div>
6 </div>90 </div>
7 );91 );
8}92}
bun.lock+4−0
@@ -17,6 +17,7 @@ · 16 unmodified lines
17 "dependencies": {17 "dependencies": {
18 "@effect/platform": "catalog:",18 "@effect/platform": "catalog:",
19 "@executor/api": "workspace:*",19 "@executor/api": "workspace:*",
20 "@executor/plugin-openapi": "workspace:*",
20 "@executor/sdk": "workspace:*",21 "@executor/sdk": "workspace:*",
21 "effect": "catalog:",22 "effect": "catalog:",
22 },23 },
@@ -30,6 +31,7 @@ · 7 unmodified lines
30 "name": "@executor/web",31 "name": "@executor/web",
31 "version": "0.0.0",32 "version": "0.0.0",
32 "dependencies": {33 "dependencies": {
34 "@executor/plugin-openapi": "workspace:*",
33 "@executor/react": "workspace:*",35 "@executor/react": "workspace:*",
34 "@executor/server": "workspace:*",36 "@executor/server": "workspace:*",
35 "@tanstack/react-router": "^1.168.10",37 "@tanstack/react-router": "^1.168.10",
@@ -175,12 +177,14 @@ · 139 unmodified lines
175 "@effect-atom/atom-react": "^0.5.0",177 "@effect-atom/atom-react": "^0.5.0",
176 "@executor/api": "workspace:*",178 "@executor/api": "workspace:*",
177 "@executor/react": "workspace:*",179 "@executor/react": "workspace:*",
180 "@tanstack/react-router": "^1.168.10",
178 "react": "^19.1.0",181 "react": "^19.1.0",
179 },182 },
180 "optionalPeers": [183 "optionalPeers": [
181 "@effect-atom/atom-react",184 "@effect-atom/atom-react",
182 "@executor/api",185 "@executor/api",
183 "@executor/react",186 "@executor/react",
187 "@tanstack/react-router",
184 "react",188 "react",
185 ],189 ],
186 },190 },
packages/plugins/openapi/package.json+2−0
@@ -25,12 +25,14 @@ · 24 unmodified lines
25 "peerDependencies": {25 "peerDependencies": {
26 "react": "^19.1.0",26 "react": "^19.1.0",
27 "@effect-atom/atom-react": "^0.5.0",27 "@effect-atom/atom-react": "^0.5.0",
28 "@tanstack/react-router": "^1.168.10",
28 "@executor/api": "workspace:*",29 "@executor/api": "workspace:*",
29 "@executor/react": "workspace:*"30 "@executor/react": "workspace:*"
30 },31 },
31 "peerDependenciesMeta": {32 "peerDependenciesMeta": {
32 "react": { "optional": true },33 "react": { "optional": true },
33 "@effect-atom/atom-react": { "optional": true },34 "@effect-atom/atom-react": { "optional": true },
35 "@tanstack/react-router": { "optional": true },
34 "@executor/api": { "optional": true },36 "@executor/api": { "optional": true },
35 "@executor/react": { "optional": true }37 "@executor/react": { "optional": true }
36 },38 },
packages/plugins/openapi/src/api/group.ts+0−17
@@ -40,13 +40,6 @@ const AddSpecResponse = Schema.Struct({ · 39 unmodified lines
40 namespace: Schema.String,40 namespace: Schema.String,
41});41});
42 42
43const ListSpecsResponse = Schema.Array(
44 Schema.Struct({
45 namespace: Schema.String,
46 toolCount: Schema.Number,
47 }),
48);
49
50// ---------------------------------------------------------------------------43// ---------------------------------------------------------------------------
51// Errors with HTTP status44// Errors with HTTP status
52// ---------------------------------------------------------------------------45// ---------------------------------------------------------------------------
@@ -62,8 +55,6 @@ const ExtractionError = OpenApiExtractionError.annotations( · 9 unmodified lines
62// Group55// Group
63// ---------------------------------------------------------------------------56// ---------------------------------------------------------------------------
64 57
65const namespaceParam = HttpApiSchema.param("namespace", Schema.String);
66
67export class OpenApiGroup extends HttpApiGroup.make("openapi")58export class OpenApiGroup extends HttpApiGroup.make("openapi")
68 .add(59 .add(
69 HttpApiEndpoint.post("previewSpec")`/scopes/${scopeIdParam}/openapi/preview`60 HttpApiEndpoint.post("previewSpec")`/scopes/${scopeIdParam}/openapi/preview`
@@ -79,12 +70,4 @@ export class OpenApiGroup extends HttpApiGroup.make("openapi") · 9 unmodified lines
79 .addError(ParseError)70 .addError(ParseError)
80 .addError(ExtractionError),71 .addError(ExtractionError),
81 )72 )
82 .add(
83 HttpApiEndpoint.get("listSpecs")`/scopes/${scopeIdParam}/openapi/specs`
84 .addSuccess(ListSpecsResponse),
85 )
86 .add(
87 HttpApiEndpoint.del("removeSpec")`/scopes/${scopeIdParam}/openapi/specs/${namespaceParam}`
88 .addSuccess(Schema.Struct({ removed: Schema.Boolean })),
89 )
90 .prefix("/v1") {}73 .prefix("/v1") {}
packages/plugins/openapi/src/api/handlers.ts+1−11
@@ -49,15 +49,5 @@ export const OpenApiHandlers = HttpApiBuilder.group( · 48 unmodified lines
49 };49 };
50 }).pipe(Effect.orDie),50 }).pipe(Effect.orDie),
51 )51 )
52 .handle("listSpecs", () =>52 ,
53 // TODO: wire to operation store listByNamespace
54 Effect.succeed([] as { namespace: string; toolCount: number }[]),
55 )
56 .handle("removeSpec", ({ path }) =>
57 Effect.gen(function* () {
58 const ext = yield* OpenApiExtensionService;
59 yield* ext.removeSpec(path.namespace);
60 return { removed: true };
61 }),
62 ),
63);53);
packages/plugins/openapi/src/react/AddOpenApiSource.tsx+187−40
@@ -1,55 +1,202 @@
1import { useState } from "react";1import { useState, useMemo, createContext, useContext } from "react";
2import {
3 createMemoryHistory,
4 createRootRoute,
5 createRoute,
6 createRouter,
7 Outlet,
8 RouterProvider,
9} from "@tanstack/react-router";
10import { useAtomSet, Result } from "@effect-atom/atom-react";
11import { Option } from "effect";
2 12
3export default function AddOpenApiSource(props: {13import { previewOpenApiSpec, addOpenApiSpec } from "./atoms";
14import type { SpecPreview } from "../sdk/preview";
15
16// ---------------------------------------------------------------------------
17// Shared state via context
18// ---------------------------------------------------------------------------
19
20type Navigate = (to: string) => void;
21const NavContext = createContext<Navigate>(() => {});
22const useNav = () => useContext(NavContext);
23
24interface AddState {
25 specUrl: string;
26 setSpecUrl: (v: string) => void;
27 preview: Result.Result<SpecPreview, unknown> | null;
28 doPreview: (spec: string) => void;
29 doAdd: (spec: string, baseUrl?: string, headers?: Record<string, unknown>) => void;
4 onComplete: () => void;30 onComplete: () => void;
5 onCancel: () => void;31 onCancel: () => void;
6}) {32}
7 const [step, setStep] = useState<"url" | "auth" | "confirm">("url");33
8 const [specUrl, setSpecUrl] = useState("");34const AddStateContext = createContext<AddState>(null!);
35const useAddState = () => useContext(AddStateContext);
36
37// ---------------------------------------------------------------------------
38// Steps
39// ---------------------------------------------------------------------------
40
41function UrlStep() {
42 const { specUrl, setSpecUrl, onCancel, doPreview } = useAddState();
43 const nav = useNav();
44
45 const handleNext = () => {
46 doPreview(specUrl);
47 nav("/auth");
48 };
9 49
10 return (50 return (
11 <div>51 <div>
12 <h3>Add OpenAPI Source</h3>52 <label>
53 Spec URL or paste JSON/YAML:
54 <textarea
55 value={specUrl}
56 onChange={(e) => setSpecUrl((e.target as HTMLTextAreaElement).value)}
57 placeholder={'https://api.example.com/openapi.json\n\nor paste spec content here...'}
58 rows={6}
59 style={{ width: "100%", fontFamily: "monospace", fontSize: "0.85rem" }}
60 />
61 </label>
62 <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}>
63 <button onClick={onCancel}>Cancel</button>
64 <button disabled={!specUrl.trim()} onClick={handleNext}>
65 Next
66 </button>
67 </div>
68 </div>
69 );
70}
13 71
14 {step === "url" && (72function AuthStep() {
15 <div>73 const { preview } = useAddState();
16 <label>74 const nav = useNav();
17 Spec URL or paste content:
18 <input
19 type="text"
20 value={specUrl}
21 onChange={(e) => setSpecUrl((e.target as HTMLInputElement).value)}
22 placeholder="https://api.example.com/openapi.json"
23 />
24 </label>
25 <div>
26 <button onClick={props.onCancel}>Cancel</button>
27 <button onClick={() => setStep("auth")} disabled={!specUrl}>
28 Next
29 </button>
30 </div>
31 </div>
32 )}
33 75
34 {step === "auth" && (76 return (
35 <div>77 <div>
36 <p>Configure authentication headers…</p>78 {preview && Result.match(preview, {
79 onInitial: () => <p>Analyzing spec…</p>,
80 onSuccess: ({ value }) => (
37 <div>81 <div>
38 <button onClick={() => setStep("url")}>Back</button>82 <h4>{Option.getOrElse(value.title, () => "API")} {Option.getOrElse(value.version, () => "")}</h4>
39 <button onClick={() => setStep("confirm")}>Next</button>83 <p>{value.operationCount} operations found</p>
84 {value.headerPresets.length > 0 && (
85 <div>
86 <p>Authentication options:</p>
87 <ul>
88 {value.headerPresets.map((p, i) => (
89 <li key={i}>{p.label}</li>
90 ))}
91 </ul>
92 </div>
93 )}
40 </div>94 </div>
41 </div>95 ),
42 )}96 onFailure: () => <p style={{ color: "red" }}>Failed to parse spec</p>,
97 })}
98 <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}>
99 <button onClick={() => nav("/")}>Back</button>
100 <button onClick={() => nav("/confirm")}>Next</button>
101 </div>
102 </div>
103 );
104}
43 105
44 {step === "confirm" && (106function ConfirmStep() {
107 const { specUrl, doAdd, onComplete } = useAddState();
108 const [adding, setAdding] = useState(false);
109 const [error, setError] = useState<string | null>(null);
110 const nav = useNav();
111
112 const handleAdd = async () => {
113 setAdding(true);
114 setError(null);
115 try {
116 doAdd(specUrl);
117 onComplete();
118 } catch (e) {
119 setError(e instanceof Error ? e.message : String(e));
120 setAdding(false);
121 }
122 };
123
124 return (
125 <div>
126 <p>Ready to add spec.</p>
127 {error && <p style={{ color: "red" }}>{error}</p>}
128 <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}>
129 <button onClick={() => nav("/auth")} disabled={adding}>Back</button>
130 <button onClick={handleAdd} disabled={adding}>
131 {adding ? "Adding…" : "Add Source"}
132 </button>
133 </div>
134 </div>
135 );
136}
137
138// ---------------------------------------------------------------------------
139// Component
140// ---------------------------------------------------------------------------
141
142export default function AddOpenApiSource(props: {
143 onComplete: () => void;
144 onCancel: () => void;
145}) {
146 const [specUrl, setSpecUrl] = useState("");
147 const [preview] = useState<Result.Result<SpecPreview, unknown> | null>(null);
148
149 const doPreviewMutation = useAtomSet(previewOpenApiSpec, { mode: "promise" });
150 const doAddMutation = useAtomSet(addOpenApiSpec, { mode: "promise" });
151
152 const router = useMemo(() => {
153 const rootRoute = createRootRoute({
154 component: () => (
45 <div>155 <div>
46 <p>Ready to add spec from: {specUrl}</p>156 <h3>Add OpenAPI Source</h3>
47 <div>157 <Outlet />
48 <button onClick={() => setStep("auth")}>Back</button>
49 <button onClick={props.onComplete}>Add Source</button>
50 </div>
51 </div>158 </div>
52 )}159 ),
53 </div>160 });
161
162 const routeTree = rootRoute.addChildren([
163 createRoute({ getParentRoute: () => rootRoute, path: "/", component: UrlStep }),
164 createRoute({ getParentRoute: () => rootRoute, path: "/auth", component: AuthStep }),
165 createRoute({ getParentRoute: () => rootRoute, path: "/confirm", component: ConfirmStep }),
166 ]);
167
168 return createRouter({
169 routeTree,
170 history: createMemoryHistory({ initialEntries: ["/"] }),
171 });
172 }, []);
173
174 const navigate: Navigate = (to) => {
175 void router.navigate({ to });
176 };
177
178 const state: AddState = {
179 specUrl,
180 setSpecUrl,
181 preview,
182 doPreview: (spec) => {
183 void doPreviewMutation({ path: { scopeId: "default" as never }, payload: { spec } });
184 },
185 doAdd: (spec, baseUrl, headers) => {
186 void doAddMutation({
187 path: { scopeId: "default" as never },
188 payload: { spec, baseUrl, headers },
189 });
190 },
191 onComplete: props.onComplete,
192 onCancel: props.onCancel,
193 };
194
195 return (
196 <AddStateContext.Provider value={state}>
197 <NavContext.Provider value={navigate}>
198 <RouterProvider router={router} />
199 </NavContext.Provider>
200 </AddStateContext.Provider>
54 );201 );
55}202}
packages/plugins/openapi/src/react/atoms.ts+0−23
@@ -1,23 +1,5 @@
1import { ScopeId } from "@executor/sdk";
2
3import { OpenApiClient } from "./client";1import { OpenApiClient } from "./client";
4 2
5// ---------------------------------------------------------------------------
6// Default scope
7// ---------------------------------------------------------------------------
8
9const DEFAULT_SCOPE = ScopeId.make("default");
10
11// ---------------------------------------------------------------------------
12// Query atoms
13// ---------------------------------------------------------------------------
14
15export const openApiSpecsAtom = (scopeId: ScopeId = DEFAULT_SCOPE) =>
16 OpenApiClient.query("openapi", "listSpecs", {
17 path: { scopeId },
18 timeToLive: "30 seconds",
19 });
20
21// ---------------------------------------------------------------------------3// ---------------------------------------------------------------------------
22// Mutation atoms4// Mutation atoms
23// ---------------------------------------------------------------------------5// ---------------------------------------------------------------------------
@@ -28,8 +10,3 @@ export const previewOpenApiSpec = OpenApiClient.mutation( · 4 unmodified lines
28);10);
29 11
30export const addOpenApiSpec = OpenApiClient.mutation("openapi", "addSpec");12export const addOpenApiSpec = OpenApiClient.mutation("openapi", "addSpec");
31
32export const removeOpenApiSpec = OpenApiClient.mutation(
33 "openapi",
34 "removeSpec",
35);
packages/plugins/openapi/src/react/index.ts+0−2
@@ -1,8 +1,6 @@
1export { openApiSourcePlugin } from "./source-plugin";1export { openApiSourcePlugin } from "./source-plugin";
2export { OpenApiClient } from "./client";2export { OpenApiClient } from "./client";
3export {3export {
4 openApiSpecsAtom,
5 previewOpenApiSpec,4 previewOpenApiSpec,
6 addOpenApiSpec,5 addOpenApiSpec,
7 removeOpenApiSpec,
8} from "./atoms";6} from "./atoms";
packages/plugins/openapi/src/sdk/operation-store.ts+2−0
@@ -26,6 +26,7 @@ export interface OpenApiOperationStore { · 25 unmodified lines
26 26
27 /** Remove all entries for a namespace */27 /** Remove all entries for a namespace */
28 readonly removeByNamespace: (namespace: string) => Effect.Effect<readonly ToolId[]>;28 readonly removeByNamespace: (namespace: string) => Effect.Effect<readonly ToolId[]>;
29
29}30}
30 31
31// ---------------------------------------------------------------------------32// ---------------------------------------------------------------------------
@@ -73,5 +74,6 @@ export const makeInMemoryOperationStore = (): OpenApiOperationStore => { · 41 unmodified lines
73 }74 }
74 return ids;75 return ids;
75 }),76 }),
77
76 };78 };
77};79};
packages/plugins/openapi/src/sdk/plugin.ts+2−0
@@ -57,6 +57,7 @@ export interface OpenApiPluginExtension { · 56 unmodified lines
57 57
58 /** Remove all tools from a previously added spec by namespace */58 /** Remove all tools from a previously added spec by namespace */
59 readonly removeSpec: (namespace: string) => Effect.Effect<void>;59 readonly removeSpec: (namespace: string) => Effect.Effect<void>;
60
60}61}
61 62
62// ---------------------------------------------------------------------------63// ---------------------------------------------------------------------------
@@ -168,6 +169,7 @@ export const openApiPlugin = (options?: { · 105 unmodified lines
168 yield* ctx.tools.unregister(toolIds);169 yield* ctx.tools.unregister(toolIds);
169 }170 }
170 }),171 }),
172
171 },173 },
172 174
173 close: () =>175 close: () =>